I'm new to Ruby and trying to do a bit of metaprogramming. I want to declare some "properties" on a class, describing some field names and their access privileges:
class Resource
# my_property(name, privilege => level)
my_property :id, :read => :admin # :write => :none
my_property :name, :read => :all, :write => :owner
my_property :pin, :read => :owner, :write => :owner
# ...
end
Where privileges are various symbols and levels are [:all, :owner, :admin]
, and higher level implies all lower levels.
Later, I want to retreive the information in this way:
res.readable_properties(:admin) # => [:id, :name, :pin]
res.writeable_properties(:admin) # => [:name, :pin]
res.writeable_properties(:all) # => []
Where method names are based on privilege names plus "able_properties" suffix.
Obviously, those declarations are on class level, so everything should be done only once when class is declared, and not on every instance creation.
I've tried to implement, but while it works fine, I suspect that my code is ugly and kludgy. Could someone please give me a hints on how to improve it? Here's what I came with:
require 'set'
class ResourceBase
class << self
def my_property(name, access)
access_levels = %w[all owner admin].map {|s| s.to_sym}
puts "#{name}: #{access}"
@permissions ||= {}
permit = {}
access_levels.each do |level|
access.each do |permission, min_level|
@permissions[permission] ||= {}
@permissions[permission][level] ||= []
permit[permission] = true if level == min_level
@permissions[permission][level].push(name) if permit[permission]
end
end
@perm_methods ||= Set.new()
@permissions.keys.each do |permission|
next if @perm_methods.include? permission
@perm_methods.add permission
define_method "#{permission}able_properties".to_sym, do |level|
self.class.permissions[permission][level]
end
end
end
def permissions
@permissions
end
end
end
class Resource < ResourceBase
# Levels range: all < owner < admin
# Higher levels imply lower ones (i.e. admin always have owner access)
my_property :id, :read => :admin #, :write => :none
my_property :name, :read => :all, :write => :owner
my_property :pin, :read => :owner, :write => :owner
end
resource = Resource.new
puts "Readable: " + resource.readable_properties(:admin).to_s
puts "Writeable: " + resource.writeable_properties(:admin).to_s
I seek for any suggestions which could be phrased as "Rather than [how OP did it] it's better to [how it should be done]."