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?
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:
- 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
.
- 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
.
- The block that sets
c.score = 0
is only executed if the record does not exist.
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.
- Finally,
choice.save!
will either update the record (if it already existed) or create the initiated record (if it didn't).
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
})
find_or_create_by_user_id
sounds better
Also, in Rails 3 you can do:
@user.choices.where(:user => @user.id, :interest => interest, :score => 4).first_or_create
If you're using rails 4 I don't think it creates the finder methods like it used to, so find_or_create_by_user isn't created for you. Instead you'd do it like this:
@user = User.find(current_user)
@event = Event.find(params[:id])
for interest in @event.interests
@user.choices.find_or_create_by(:interest => interest) do |c|
c.score ||= 0
c.score += 1
end
end
In Rails 4
You can use find_or_create_by
to get an object(if not exist,it will create), then use update
to save or update the record, the update method will persist record if it is not exist, otherwise update record.
For example
@edu = current_user.member_edu_basics.find_or_create_by(params.require(:member).permit(:school))
if @edu.update(params.require(:member).permit(:school, :majoy, :started, :ended))