Rails first_or_create ActiveRecord method
From the Guides
first_or_create
The first_or_create
method checks whether first
returns nil or not. If it does return nil, then create
is called. This is very powerful when coupled with the where
method. Let’s see an example.
Suppose you want to find a client named ‘Andy’, and if there’s none, create one and additionally set his locked attribute to false. You can do so by running:
Client.where(:first_name => 'Andy').first_or_create(:locked => false)
# => #<Client id: 1, first_name: "Andy", orders_count: 0, locked: false, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">
The SQL generated by this method looks like this:
SELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 1
BEGIN
INSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 0, NULL, '2011-08-30 05:22:57')
COMMIT
first_or_create
returns either the record that already exists or the new record. In our case, we didn’t already have a client named Andy so the record is created and returned.
first_or_create!
You can also use first_or_create!
to raise an exception if the new record is invalid. Validations are not covered on this guide, but let’s assume for a moment that you temporarily add
validates :orders_count, :presence => true
to your Client model. If you try to create a new Client without passing an orders_count, the record will be invalid and an exception will be raised:
Client.where(:first_name => 'Andy').first_or_create!(:locked => false)
# => ActiveRecord::RecordInvalid: Validation failed: Orders count can't be blank
Update value with first_or_create in rails
In you controller, make the changes
def change_like_status
if current_user
status = Like.create_or_change_status(params[:id], current_user.id)
else
return render json: { status: false, msg: "You need to sign in before performing this action." }
end
end
In your model like.rb
file, add a method
def self.create_or_change_status(business_id, user_id)
status = where(business_id: business_id, user_id: user_id).first
if status.nil?
status = create({business_id: business_id, user_id: user_id, liked: 1})
else
status.update_attributes(liked: !status.liked)
end
status
end
Can I override create with first_or_create?
It is possible with monkey patching but it's a very bad idea. You would be asking for major trouble down the road when you or someone else expects the code to behave in a certain default way. What if the requirements change and you remove the validation? You would silently never be able to create multiple records with create
because first_or_create
would always find the existing one.
The best alternative would be to check if the role
already exists in scene.roles
. For example:
scene.roles.include?(role) ? false : scene.roles << role
# does scene.roles include role? if yes: do nothing, if not: add role
Or do something like this.
Difference between two different ways of using first_or_create in ActiveRecord
Actually, there are two methods: find_or_create_by
, add one column and give a set of attributes.
So, for example you can write:
find_or_create_by_name('Willy', time_zone: 'UTC')
The second is first_or_create
, and that works as you were suggested (your second solution) (see the documentation as well).
There was a suggestion to introduce the find_or_create
method, which accepts a hash, but that resulted in the first_or_create
. (see discussion here).
Note that both are also explained well in the relevant rails guide.
[UPDATE 2014-06-30] Note that currently the notation has been changed, we should now write
Organization.find_or_create_by(name: '37 signals') do |organisation|
organisation.time_zone = 'Central'
end
which will try to find the organisation by name, and if not found use the block to create the organisation. Note: the block is only executed if the organisation did not exist!
rails first_or_create does not update existing records
The first_or_create
will only execute the block on creation. This is intentional, it allows you to set certain values initially and not overwrite them when the record already exists.
If you always want to execute the block, you can write it as follows:
Blob.where(user_id: user.id, item_id: item.id).first_or_create.tap do |s|
s.amount += amount
s.save
end
Or, I prefer the little shorter version
Blob.find_or_create_by(user_id: user.id, item_id: item.id).tap do |s|
s.amount += amount
s.save
end
Race conditions in Rails first_or_create
Yes, it's possible.
You can significantly reduce the chance of conflict with either optimistic or pessimistic locking. Of course optimistic locking requires adding a field to the table, and pessimistic locking doesn't scale as well--plus, it depends on your data store's capabilities.
I'm not sure whether you need the extra protection, but it's available.
first_or_create only insert
Use create_with
by following:
c = Country.create_with(status: 'old').find_or_create_by(name: 'test')
This will create Country
with status
'old' and name
'test' only and only if it doesn't find the country with name 'test'. If it does find the country with name 'test', it will not update the status.
Ultimately, it will also return the country in c
, whether after finding or creating.
create_with
only sets attributes when creating new records from a relation object.
first_or_create by email and then save the nested model
first_or_create
accepts a block. So you could do it as follows:
@user = User.where(:email => params[:user][:email]).first_or_create do |user|
# This block is called with a new user object with only :email set
# Customize this object to your will
user.attributes = params[:user]
# After this, first_or_create will call user.create, so you don't have to
end
Related Topics
Has Anyone Figured Out a Way to Run the Same Cucumber Scenario on Multiple Browsers/Web Drivers
How to Embed Dynamic Ruby Code to "Javascript" Section in Slim Templates
Rails: Update Model Attribute Without Invoking Callbacks
Devise Nomethoderror 'For' Parametersanitizer
Examples of 'Things' That Are Not Objects in Ruby
Rails First_Or_Create Activerecord Method
How to Pass an Argument When Calling a View File
Ruby Imap Idle Concurrency - How to Tackle
Run Code Only If Script Called from the Command Line
Architecture for a Modular, Component-Based Sinatra Application
How to Determine Leap Year in Ruby
Sending a Delete Request from Sinatra
Is There a Workaround to Open Urls Containing Underscores in Ruby
Map an Array Modifying Only Elements Matching a Certain Condition