Is there a simple way to list the accessors/readers that have been set in a Ruby Class?
class Test
attr_reader :one, :two
def initialize
# Do something
end
def three
end
end
Test.new
=> [one,two]
What I'm really trying to do is to allow initialize to accept a Hash with any number of attributes in, but only commit the ones that have readers already defined. Something like:
def initialize(opts)
opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val|
eval("@#{opt} = \"#{val}\"")
end
end
Any other suggestions?
You can look to see what methods are defined (with
Object#methods
), and from those identify the setters (the last character of those is=
), but there's no 100% sure way to know that those methods weren't implemented in a non-obvious way that involves different instance variables.Nevertheless
Foo.new.methods.grep(/=$/)
will give you a printable list of property setters. Or, since you have a hash already, you can try:Accessors are just ordinary methods that happen to access some piece of data. Here's code that will do roughly what you want. It checks if there's a method named for the hash key and sets an accompanying instance variable if so:
Note that this will get tripped up if a key has the same name as a method but that method isn't a simple instance variable access (e.g.,
{:object_id => 42}
). But not all accessors will necessarily be defined byattr_accessor
either, so there's not really a better way to tell. I also changed it to useinstance_variable_set
, which is so much more efficient and secure it's ridiculous.You could override attr_reader, attr_writer and attr_accessor to provide some kind of tracking mechanism for your class so you can have better reflection capability such as this.
For example:
These can be demonstrated fairly simply:
This is what I use (I call this idiom hash-init).
If you are on Ruby 1.9 you can do it even cleaner (send allows private methods):
This will raise a NoMethodError if you try to assign to foo and method "foo=" does not exist. If you want to do it clean (assign attrs for which writers exist) you should do a check
however this might lead to situations where you feed your object wrong keys (say from a form) and instead of failing loudly it will just swallow them - painful debugging ahead. So in my book a NoMethodError is a better option (it signifies a contract violation).
If you just want a list of all writers (there is no way to do that for readers) you do
which is "get an array of method names and grep it for entries which end with a single equals sign after a word character".
If you do
and val comes from a web form - congratulations, you just equipped your app with a wide-open exploit.
There's no built-in way to get such a list. The
attr_*
functions essentially just add methods, create an instance variable, and nothing else. You could write wrappers for them to do what you want, but that might be overkill. Depending on your particular circumstances, you might be able to make use ofObject#instance_variable_defined?
andModule#public_method_defined?
.Also, avoid using eval when possible:
Try something like this:
This is basically what Rails does when you pass in a params hash to new. It will ignore all parameters it doesn't know about, and it will allow you to set things that aren't necessarily defined by attr_accessor, but still have an appropriate setter.
The only downside is that this really requires that you have a setter defined (versus just the accessor) which may not be what you're looking for.