patternrubyMinor
Ruby metaprogramming - declaring "properties" on a class
Viewed 0 times
propertiesmetaprogrammingdeclaringrubyclass
Problem
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:
Where privileges are various symbols and levels are
Later, I want to retreive the information in this way:
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:
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]."
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
# ...
endWhere 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 :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_sI seek for any suggestions which could be phrased as "Rather than [how OP did it] it's better to [how it should be done]."
Solution
Rather than access the possibly uninitialized
That way you can simply call
without bothering if the intermediate hashes and sets have been initialized
Some other thoughts:
-
The
After cleaning this up a bit, I came around with the following (untested) code:
@permissions instance variable inside my_property, use your wrapper method permissions and put the initialization logic in there. Also use Hash.new to save you the ||= statements. Also, the name list should be a Set, IMHO:def permissions
@permissions ||= Hash.new { |h,k| h[k] = Hash.new { |h,k| h[k] = Set.new }}
endThat way you can simply call
permissions[permission][level].addwithout bothering if the intermediate hashes and sets have been initialized
Some other thoughts:
- The second half of your
my_propertymethod is only about updating the access methods. This should be a separate method.
- You don't use
@perm_methodsat all, you can remove it.
- Use
map(&:to_sym)(or even better, don't create the temporary string array in the first place, it's not shorter!).
- Don't use
to_symon the first argument todefine_method, it shouldn't be necessary.
-
The
min_level part can be improved in style and efficiency. You have at least two options here to get all the affected levels:- Use array slicing:
access_levels[access_levels.index(level)..-1]
- Use
drop_while:access_levels.drop_while { |level| level != min_level }
After cleaning this up a bit, I came around with the following (untested) code:
require 'set'
class ResourceBase
class :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_sCode Snippets
def permissions
@permissions ||= Hash.new { |h,k| h[k] = Hash.new { |h,k| h[k] = Set.new }}
endpermissions[permission][level].addrequire 'set'
class ResourceBase
class << self
def my_property(name, access)
access_levels = [ :all, :owner, :admin ]
puts "#{name}: #{access}"
access.each do |perm, min_level|
access_levels.drop_while { |level| level != min_level }.each do |level|
permissions[perm][level].add name
end
end
update_permission_methods
end
def update_permission_methods
permissions.each do |name, perm|
define_method("#{name}able_properties") { |level| perm[level] }
end
end
def permissions
@permissions ||= Hash.new { |h,k| h[k] = Hash.new { |h,k| h[k] = Set.new }}
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_sContext
StackExchange Code Review Q#8765, answer score: 3
Revisions (0)
No revisions yet.