Forum: Ruby How to iterate through instance attribute names (attr_accessor)

64c2a6af59b9003933906f26b37f4f81?d=identicon&s=25 me (Guest)
on 2008-03-15 20:05
(Received via mailing list)
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
C40020a47c6b625af6422b5b1302abaf?d=identicon&s=25 Stefano Crocco (crocco)
on 2008-03-15 20:30
(Received via mailing list)
On Saturday 15 March 2008, me wrote:
>   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
9a56d606d07692444b1e60635eb22df8?d=identicon&s=25 Mark Bush (bush)
on 2008-03-15 20:38
me wrote:
> 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
64c2a6af59b9003933906f26b37f4f81?d=identicon&s=25 me (Guest)
on 2008-03-15 21:01
(Received via mailing list)
> I hope this helps
>
Yes, it does greatly!  Thanks!
64c2a6af59b9003933906f26b37f4f81?d=identicon&s=25 me (Guest)
on 2008-03-15 21:05
(Received via mailing list)
On Mar 15, 2:38 pm, Mark Bush <m...@bushnet.org> wrote:
>   end
>

This is also very helpful.  Thanks for the swift responses!
B57c5af36f5c1f33243dd8b2dd9043b1?d=identicon&s=25 F. Senault (Guest)
on 2008-03-15 21:36
(Received via mailing list)
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
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 ara howard (Guest)
on 2008-03-16 02:01
(Received via mailing list)
On Mar 15, 2008, at 1:04 PM, me wrote:

>
>  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/
Bc8e3e2f7acb3a7d93c10e235dc0b4e1?d=identicon&s=25 yermej (Guest)
on 2008-03-16 06:35
(Received via mailing list)
On Mar 15, 2:38 pm, Mark Bush <m...@bushnet.org> wrote:
> class Event
>   @@attributes = [:a, :b, :c, :d]

>   @@attributes.each {|attr| attr_accessor attr}

This line can also be written as:
attr_accessor *@@attributes
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.