first_or_create(attributes = nil, &block)public
# File activerecord/lib/active_record/relation.rb, line 131 def first_or_create(attributes = nil, &block) # :nodoc: first || create(attributes, &block) end
Specify the data you’re looking for. If it exists in the table, the first instance will be returned. If not, then create is called.
If a block is provided, that block will be executed only if a new instance is being created. The block is NOT executed on an existing record.
MyStat.where(name: statistic_name).first_or_create do |statistic| statistic.value = calculate_percentage statistic.statistic_type = "percentage" end
This method first searches the table for ANY FIRST RECORD, not the one matching given attributes. If no record is found at all, it creates one using the specified attributes. This might be misunderstood in many cases.
keredson is still a little off on the behavior.
>> Where in reality this would grab the “first” record (say w/ email “email@example.com”), and change its email to “firstname.lastname@example.org”, a fairly surprising action that doesn’t obviously fail, leading to some pretty subtle bugs.
This isn’t right, as if you look at the source it calls: “first || create(attributes, &block)”
So in the example of:
it would find the first user with any email, and return it. And thats it. The attributes passed in as params are only used in the event that first returns no matches, and create is called.
I’m using it in a way similar to:
This will find the first Foo where bar is equal to the bar sent from baz_params. If none is found, it will create it. It’s useful for me when importing large amounts of data where I know there will be duplicate records.
Dino’s comment was almost certainly due to a misunderstanding of the API, a mistake I made myself the first time I used it, and one I’ve witnessed multiple others make as well.
The fact that this method takes an attributes parameter leads one to think the passed params are the criteria for the selection. At first glance it would appear it would be used like this:
Where in reality this would grab the “first” record (say w/ email “email@example.com”), and change its email to “firstname.lastname@example.org”, a fairly surprising action that doesn’t obviously fail, leading to some pretty subtle bugs.
In reality it should be used like this:
And the attributes param. isn’t used in 99% of use cases. (If at all - the provision for the block that executes only on create fully duplicates what the attributes parameter can do in a much more obvious way.)
IMHO this is simply a bad API that one just needs to be aware of. But it’s unfair to knock dino for what’s likely a highly common misreading.
I very much doubt that dino’s comment was ever correct, but it certainly isn’t correct now. The behavior liantics describes is correct.