Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Rails, what is the best way to update a record or create a new one if it doesn't exist?

I have a create statement for some models, but it’s creating a record within a join table regardless of whether the record already exists.

Here is what my code looks like:

@user = User.find(current_user) @event = Event.find(params[:id]) for interest in @event.interests  @user.choices.create(:interest => interest, :score => 4) end 

The problem is that it creates records no matter what. I would like it to create a record only if no record already exists; if a record does exist, I would like it to take the attribute of the found record and add or subtract 1.

I’ve been looking around have seen something called find_or_create_by. What does this do when it finds a record? I would like it to take the current :score attribute and add 1.

Is it possible to find or create by id? I’m not sure what attribute I would find by, since the model I’m looking at is a join model which only has id foreign keys and the score attribute.

I tried

@user.choices.find_or_create_by_user(:user => @user.id, :interest => interest, :score => 4) 

but got

undefined method find_by_user

What should I do?

like image 949
ChrisWesAllen Avatar asked Jan 06 '11 23:01

ChrisWesAllen


People also ask

What is the difference between Create and new in Rails?

Basically the new method creates an object instance and the create method additionally tries to save it to the database if it is possible. Check the ActiveRecord::Base documentation: create method Creates an object (or multiple objects) and saves it to the database, if validations pass.

Does update call Save rails?

update!(attributes) LinkUpdates its receiver just like update but calls save! instead of save, so an exception is raised if the record is invalid. Also aliased as: update_attributes!

Does Update_attributes call save?

update_attribute uses save(false) while update_attributes uses save (which means save(true)). If perform_validation is false while calling save then it skips validation, and it also means that all the before_* callbacks associated with save.


2 Answers

my_class = ClassName.find_or_initialize_by_name(name)  my_class.update_attributes({    :street_address => self.street_address,    :city_name => self.city_name,    :zip_code => self.zip_code }) 
like image 71
krunal shah Avatar answered Sep 21 '22 13:09

krunal shah


Assuming that the Choice model has a user_id (to associate with a user) and an interest_id (to associate with an interest), something like this should do the trick:

@user = User.find(current_user) @event = Event.find(params[:id])  @event.interests.each do |interest|   choice = @user.choices.find_or_initialize_by_interest_id(interest.id) do |c|     c.score = 0 # Or whatever you want the initial value to be - 1   end    choice.score += 1   choice.save! end 

Some notes:

  1. You don't need to include the user_id column in the find_or_*_by_*, as you've already instructed Rails to only fetch choices belonging to @user.
  2. I'm using find_or_initialize_by_*, which is essentially the same as find_or_create_by_*, with the one key difference being that initialize doesn't actually create the record. This would be similar to Model.new as opposed to Model.create.
  3. The block that sets c.score = 0 is only executed if the record does not exist.
  4. choice.score += 1 will update the score value for the record, regardless if it exists or not. Hence, the default score c.score = 0 should be the initial value minus one.
  5. Finally, choice.save! will either update the record (if it already existed) or create the initiated record (if it didn't).
like image 20
vonconrad Avatar answered Sep 23 '22 13:09

vonconrad