tags:

views:

179

answers:

5

I'm new to ruby and feel that I still do a lot of things in C sharpish way :-). Suppose you have an array of objects (Question :has_many=>:answers). I want to iterate over all answers and if some answers meet a criteria, change answer attribute. Currently I'm doing it as follows:

def iterate_and_do_stuff
   for (a in self.answers)
      if(a.somecriteria==true)
         a.some_attr=some_val
      end
   end
end

What are other ways of doing this? Blocks, loops, etc?

Please adivce.

Thank you.

+12  A: 

Use Array#each:

self.answers.each {|a| a.some_attr = some_val if a.some_criteria}

Pesto
wow, 6 lines cut, thanks
Valentin Vasiliev
It's also going to be more efficient than using #select followed by #each because you'll only have to iterate over the array once.
Pesto
+1  A: 

Just for fun an alternative

self.answers.select{|a| a.some_criteria}.each{|a| a.some_attr = some_val}
DanSingerman
what about converting this to do..end blocks?
Valentin Vasiliev
Beat me by 2 minutes. Damn having to look up the documentation.
Chris Doggett
SO always seems like a race :-)
Valentin Vasiliev
+2  A: 
self.answers.select{|a| a.somecriteria}.each{|a| a.some_attr = some_val}

OR

self.answers.find_all{|a| a.somecriteria}.each{|a| a.some_attr = some_val}

Both are less efficient than your original code, though.

Chris Doggett
+4  A: 

I prefer map! or collect! in this case, because you can use the ! method to semantically represent you are changing the array in place.

self.answers.map!{ |a| 
   a.some_criteria ? (a.some_attr = some_val) : a.some_attr  
}

or

self.answers.collect!{ |a| 
   a.some_criteria ? (a.some_attr = some_val) : a.some_attr
}

This way it is very clear that you intend to change the array.

berlin.ab
+1  A: 

If you're working with ActiveRecord models, don't forget that you can do your selection at the database level, rather than in memory. To rephrase, you can retrieve from the database only the values that you want into your array, and then simply change those.

Here's an example using custom finders (sort of an older way to do it):

class Question < ActiveRecord::Base
  has_many :answers do
    def accepted
      find :all, :conditions => { :accepted => true }
    end
  end
end

class Answer < ActiveRecord::Base
  belongs_to :question
end

q = Question.find :first
q.answers.accepted.each { |a| a.do_something! }

Or you could do it with another association:

class Question < ActiveRecord::Base
  has_many :answers
  has_many :accepted_answers, :class_name => "Answer", :conditions => { :accepted => true }
end

class Answer < ActiveRecord::Base
  belongs_to :question
end

q = Question.find :first
q.accepted_answers.each { |a| a.do_something! }

Here's another example using named scopes (a little newer, and preferred in my opinion) on your child class:

class Question < ActiveRecord::Base
  has_many :answers
end

class Answer < ActiveRecord::Base
  belongs_to :question
  named_scope :accepted, :conditions => { :accepted => true }
end

q = Question.find :first
q.answers.accepted.each { |a| a.do_something! }

But in any of the cases, you've abstracted the "selection", which has a few benefits:

  1. Faster in cases of large collections
  2. You've abstracted the lower level selection criteria to something with higher level semantic meaning, which makes your code easier to read and maintain
Ian Terrell