Rails: build method breaks loop - ruby-on-rails

I'm was trying to add multiple objects like so:
class Person < ActiveRecord::Base
has_many :interests
...
def add_interests(interest_hashes)
interest_hashes.each do |interest|
Rails.logger.debug "person.apply_interests: interest: #{interest.inspect}"
interests.build(:name => interest.name, :category => interest.category)
end
save!
end
...
end
However in the log when calling <some person>.add_interests(<some hashes>) all I see is the first hash - no error or exception. If I remove the build method the loop works as expected.
What is happening when calling the build method?
What's a better way to achieve what I'm trying?
Edit:
interest_hashes.inspect output example:
[{"category"=>"Interest", "name"=>"Formula One"}, {"category"=>"Musical instrument", "name"=>"Guitar"}]

You should get a NoMethodError when calling name and category on interest, since hashes are accessed using the [] method. Replace
interest.name
with
interest["name"]
Or use an Struct, which may be preferable.

Related

How can / should I override the `build` method for a my model?

I am using Ruby on Rails 3.2.13 and, since in my previous question "How should I use the alias_method_chain for the build method?" it seems that any solution has been found, I am considering to override the build method in a my model class in order to get the wanted behavior.
How can / should I override the build method for a my model (even when the build method runs for ActiveRecord associations as-like #article.comments.build(...))?
There is a way to customize your build method , define your build in model relations:
class Article
has_many :comments do
def build(*args, &block)
#TODO your code
super(*args, &block)
end
end
end
If you don't need default params of build method, then you can use your own.
build is an alias for new in the ActiveRecord::Relation class. There is this line in the source code to make the alias:
alias build new
Here is how to override the build method:
# config/initializers/monkey_patch.rb
class ActiveRecord::Relation
def build
"HHHHHIIIII"
end
end
If you call the build method on ActiveRecord::Relation objects, the string "HHHHHIIIII" is returned. For example, if a Course has_many students, this line will return "HHHHHIIIII" in rails console:
Course.first.students.scoped.build
Here is how to add a method to the ActiveRecord::Relation class:
# config/initializers/monkey_patch.rb
class ActiveRecord::Relation
def my_model_build
"Happy happy, joy joy"
end
end

How do you define a class method?

I have a model OutcomeData with controller OutcomeDatas.
In OutcomeData, I have a method as_cleaned_hash that right now, doesn't do a damn thing. Let's just pretend it returns 'hello'
class OutcomeData < ActiveRecord::Base
attr_accessible :key, :outcome_uid, :task_id, :value
belongs_to :task
belongs_to :outcome
def as_cleaned_hash
'hello i am alive'
end
This is the method that as_cleaned_hash is supposed to follow, if it matters:
#outcome_data = OutcomeData.find_all_by_outcome_uid(params[:outcome_uid])
hash = Hash.new
#outcome_data.each do |p|
unless p[:value].blank? || p[:key] == 'raw'
hash[p[:key]] = p[:value]
end
end
This works fine -- right now I'm throwing it into my controller actions, but since it needs to be used throughout my app, I can't let this happen.
So, for whatever reason, I get an undefined method error.
I called OutcomeData.methods to see if the method was even there, and, nope. (see list here: http://pastebin.com/B3y1r2w7)
OutcomeData.respond_to?('as_cleaned_hash') returns false.
There's nothing fancy going on either, so I'm not quite sure what's happening.
Rails 3.2.12 with Ruby 2.0.0-p195
To define a class method, the syntax is
def self.foo
end
You have defined an instance method.

Ruby on Rails: Calling an instance method from another model

I've got a Match model and a Team model.
I want to run an instance method (written inside the Team model) after a Match has been saved. Here's what I've got.
team.rb
def goals_sum
unless goal_count_cache
goal_count = a_goals_sum + b_goals_sum
update_attribute(:goal_count_cache, goal_count)
end
goal_count_cache
end
and it works. Now I need to run this whenever a match gets saved. So I tried this:
match.rb
after_save :Team.goals_sum
after_destroy :Team.goals_sum
And it doesn't work.
I know I'm missing something basic, but I still can't go through with it. Any tips?
You can just define a private method on Match that delegates to the method on Team (otherwise, how would it know which team to run the method on? You say it's an instance method, and I assume a match has teams that are playing it).
after_save :update_teams_goals_sum
after_destroy :update_teams_goals_sum
private
def update_teams_goals_sum
[team_a, team_b].each &:goals_sum
end
after_save :notify_team
after_destroy :notify_team
private
def notify_team
Team.goals_sum
end

Serialize With Options Configuration Blocks

I'm using serialize_with_options ( http://www.viget.com/extend/simple-apis-using-serializewithoptions/ ) in a rails project and have been using named blocks for rendering as per the example on the linked page:
class Speaker < ActiveRecord::Base
# ...
serialize_with_options do
methods :average_rating, :avatar_url
except :email, :claim_code
includes :talks
end
serialize_with_options :with_email do
methods :average_rating, :avatar_url
except :claim_code
includes :talks
end
end
Then I can call the second block configuration with #speaker.to_xml(:with_email). This works well, however, I'd like to figure out how to call this block when I have an array of objects. For example, the following does not work:
#speakers = Speaker.all
#speakers.to_xml(:with_email)
Which returns a "TypeError: can't dup Symbol" error. This makes sense to me since Array hasn't been configured to use serialize_with_options. How can I get this tag to be passed on to the individual speaker objects when running .to_xml and render all speakers :with_email?
In your above example, #speakers is a Array object. You need to implement / override the to_xml there . Then i should work:
class Array
def to_xml (with_email)
self.each do |element|
element.to_xml(with_email)
end
end
end

Rails Validation Error

While trying to add an error message using add_to_base, I am getting an undefined method 'errors' message. I am defining it in my model. Am I supposed to include any other file in order to access the errors variable.
Model File - I am defining it inside a method
self.errors.add_to_base("Invalid Name")
Error Message
undefined method `errors' for #<Class:0x0000010179d7a0>
I tried by calling it as errors.add_to_base("Invalid Name") also but still getting the same error.
Thanks.
you should call it in your callback method, something like following
def validate
if !self.interests.blank? && !self.interests.match("<").nil?
self.errors.add :base, 'Please ensure that Interest field do not contain HTML(< and >) tags'
end
end
I suspect that you have defined your method as a class method, instead of as an instance method.
Class methods look like this on ruby:
def self.checkFoo()
...
end
Instance methods looks like this:
def checkFoo()
...
end
Check that your checkFoo method is an instance method, and then use it like this:
class MyModel < ActiveRecord::Base
validate :foo
private
def checkFoo()
self.errors.add etc..
end
end
Typically used the validation callbacks, model errors are used both to cause the prospective database save to fail and to set up a contextual error messages for the end-user. The add_to_base variant is intended for general, non-specific error conditions (i.e. not associated with a particular model attribute).
class MyModel < ActiveRecord::Base
validate do |my_model|
if my_model.some_attribute.blank? # For example
my_model.errors.add :my_model, "must be filled in"
end
end
end
Subsequently
#my_model = MyModel.create(:some_attribute => "")
would fail and the #my_model.errors.full_messages array would contain
[ ..., "Some_attribute must be filled in", ... ]
There is however a shorthand for the above example as follows
class MyModel < ActiveRecord::Base
validates_presence_of :some_attribute, :msg => "must be filled in"
end
Looks like your 'self.errors.add_to_base("Invalid Name")' doesn't have any problem
But your model should inherit from ActiveRecord::Base
cheers
sameera

Resources