How to iterate through instance attribute names (attr_accessor)

M

me

Say, I have a class:

class Event
attr_accessor :a,
:b,
:c,
:d

def initialize()
end

def validate
# validate all attributes at once here
end

protected

def validate_a
# a can be any number from 1-10 if b = 'Y' and c is blank
end

def validate_b
# b can be 'Y' or 'N'
end

def validate_c
end

def validate_d
end

end

And in method validate, I'd like to iterate through all the attributes
and run their respective validating method. Is there a way for me to
be able to iterate through my list of attr_accessor symbols and
reference their names to know which method to call?

Thanks,

-Al
 
S

Stefano Crocco

Say, I have a class:

class Event
attr_accessor :a,

:b,
:c,
:d

def initialize()
end

def validate
# validate all attributes at once here
end

protected

def validate_a
# a can be any number from 1-10 if b = 'Y' and c is blank
end

def validate_b
# b can be 'Y' or 'N'
end

def validate_c
end

def validate_d
end

end

And in method validate, I'd like to iterate through all the attributes
and run their respective validating method. Is there a way for me to
be able to iterate through my list of attr_accessor symbols and
reference their names to know which method to call?

Thanks,

-Al

You must understand that all attr_accessor does is create a couple of methods:
a setter and a getter. For example

attr_accessor :a

creates the two methods a and a=, which are more or less equivalent to the
following hand-written methods:

def a
@a
end

def a= value
@a = value
end

Once defined, the methods created by attr_accessor can't be distinguished from
the other instance methods of the class. This means that you can't iterate on
the methods themselves. What you can do is iterate on the instance variables
methods created this way refer to. For example, your 'validate' method could
be something like:

def validate
instance_variables.each{|v| send "validate_#{v.sub('@','')}"}
end

instance_variables is a method which returns an array with the names of all
the instance variables of the object. The method send, instead, calls the
method with the name passed as its first argument (in this case, 'validate_a',
'validate_b' and so on. v.sub is needed because the names of the instance
variables contain the initial '@').

The code above works provided there's a validate_ method for each instance
variable. If this is not the case, you can enclose the call to send in a
begin/rescue block:

def validate
instance_variables.each do |v|
begin send "validate_#{v.sub('@','')}"
rescue NoMethodError
end
end
end

Of course, you can also keep an array of the names of those instance variables
for which a validation method exists and use the following code:

def validate
[:a, :b, :c, :d].each{|v| send "validate_#{v}"}
end

I hope this helps

Stefano
 
M

Mark Bush

me said:
And in method validate, I'd like to iterate through all the attributes
and run their respective validating method. Is there a way for me to
be able to iterate through my list of attr_accessor symbols and
reference their names to know which method to call?

If you just want to run the validates for the variables specified in the
"attr_accessor" methods and not for other instance variables, then you
could do something like this:

class Event
@@attributes = [:a, :b, :c, :d]
@@attributes.each {|attr| attr_accessor attr}

def validate
@@attributes.each {|attr| send "validate_#{attr.to_s}" }
end
 
M

me

If you just want to run the validates for the variables specified in the
"attr_accessor" methods and not for other instance variables, then you
could do something like this:

class Event
  @@attributes = [:a, :b, :c, :d]
  @@attributes.each {|attr| attr_accessor attr}

  def validate
    @@attributes.each {|attr| send "validate_#{attr.to_s}" }
  end

This is also very helpful. Thanks for the swift responses!
 
F

F. Senault

Le 15 mars 2008 à 20:21, Stefano Crocco a écrit :
Of course, you can also keep an array of the names of those instance variables
for which a validation method exists and use the following code:

def validate
[:a, :b, :c, :d].each{|v| send "validate_#{v}"}
end

You can metaprogram your way around it too. Full example (that can
surely be improved) :

module Kernel
def attr_with_validation(*atts)
unless method_defined? :validate
class_eval <<-_EOE
@_validations = []
def self.validations
@_validations
end
def validate
self.class.validations.each do |v|
self.send("validate_\#{v}")
end
end
_EOE
end
atts.each do |att|
class_eval <<-_EOE
@_validations << :#{att}
def #{att}
@#{att}
end
def #{att}=(v)
@#{att} = v
end
_EOE
end
end
end

class Toto
attr_with_validation :a, :b
attr_with_validation :c

def validate_a() ; raise if @a.nil? ; end
def validate_b() ; raise if @b.nil? ; end
def validate_c() ; raise if @c.nil? ; end

def initialize(a)
@a = a
end
end

t = Toto.new("I'm a")
t.b = "Hoy b"

puts t.a
puts t.b

t.validate

Result is :

I'm a
Hoy b
../validate.rb:38:in `validate_c': unhandled exception
from (eval):7:in `send'
from (eval):7:in `validate'
from (eval):6:in `each'
from (eval):6:in `validate'
from ./validate.rb:51
 
A

ara howard

Say, I have a class:

class Event
attr_accessor :a,
:b,
:c,
:d

def initialize()
end

def validate
# validate all attributes at once here
end


# gem install fattr
# http://codeforpeople.com/lib/ruby/fattr/fattr-1.0.3/README
require 'rubygems'
require 'fattr'

class Event

fattrs :a, :b, :c, :d

def initialize
fattrs.each do |fattr|
name, value = fattr, send(fattr)
validate name, value
end

def validate name, value
case name
....


also see the traits lib, more heavyweight than fatter, but also
included built-in validations

http://codeforpeople.com/lib/ruby/traits/traits-0.9.2/README

regards.


a @ http://codeforpeople.com/
 
Y

yermej

me said:
And in method validate, I'd like to iterate through all the attributes
and run their respective validating method. Is there a way for me to
be able to iterate through my list of attr_accessor symbols and
reference their names to know which method to call?

If you just want to run the validates for the variables specified in the
"attr_accessor" methods and not for other instance variables, then you
could do something like this:

class Event
@@attributes = [:a, :b, :c, :d]
@@attributes.each {|attr| attr_accessor attr}

This line can also be written as:
attr_accessor *@@attributes
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top