Sequelize findOrCreate returns SequelizeUniqueContraintError on a model with an additional unique keys
I was able to resolve this by passing the unique key to the 'findOrCreate' method of the model in the 'where' clause and setting values for all other table attributes in the 'defaults' clause.
For Eg. - Employee.findOrCreate({where: {employeeId: 'ABCD1234'}, defaults: {role: 'Analyst'}});
Where employeeId is a Unique Key. (I assume that the primary key 'id' field is set to Auto Increment)
Please refer to the example provided in the official tutorial. here
Sailsjs what attribute does findOrCreate use to find if a record exists?
findOrCreate
takes 2 parameters and a callback
You should use first to determine whether record exists using unique identifying keys.
E.g.
var findCriteria = { id: 1, customer_email: 'abc@example.com' };
var recordToCreate = { id: 1, customer_email: 'abc@example.com', name: 'ABC' };
Model.findOrCreate(findCriteria, recordToCreate, console.log);
findOrCreate
Checks for the existence of the record in the first parameter. If it
can't be found, the record in the second parameter is created. If no
parameters are passed, it will return the first record that exists. If
no record is provided, it will either find a record with matching
criteria or create the record if the object can not be found. Eg.
Model.findOrCreate( findCriteria , recordToCreate , [callback] )
Sequelize MySQL Model extended as class findOrCreate method fails to create record
Default values don't work in your code because you indicated incorrect option for that. Replace default
with defaultValue
and it does the trick:
GuildPremium: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
DiscordGuildPrefix: {
type: DataTypes.TEXT,
allowNull: false,
defaultValue: process.env.DefaultPrefix
},
GuildActive: {
type: DataTypes.BOOLEAN,
defaultValue: true,
comment: "Bot active in Guild?"
}
See Default values in the official documentation.
Create or Update Sequelize
From the docs, you don't need to query where
to perform the update once you have the object. Also, the use of promise should simplify callbacks:
Implementation
function upsert(values, condition) {
return Model
.findOne({ where: condition })
.then(function(obj) {
// update
if(obj)
return obj.update(values);
// insert
return Model.create(values);
})
}
Usage
upsert({ first_name: 'Taku' }, { id: 1234 }).then(function(result){
res.status(200).send({success: true});
});
Note
- This operation is not atomic.
- Creates 2 network calls.
which means it is advisable to re-think the approach and probably just update values in one network call and either:
- Look at the value returned (i.e. rows_affected) and decide what to do.
- Return success if update operation succeeds. This is because whether the resource exists is not within this service's responsibility.
Sails findOrCreate returns record already exists if running asynchronously
I have experienced the same issue with a postgres adapter and believe the cause is a race condition. This seems to be a known problem: Waterline Docs: findOrCreate broken with high frequency calls #929
findOrCreate
doesn't appear to be atomic, so I think what happens is that the first call executes a find
query which returns no results before executing the create operation. If the second call executes its find
before the create
of the first call has completed, it also return an empty result which triggers another create
.
The problem occurs if the create
data contains unique values. As the same data will be added twice because two create
operations are executed, the second one fails because the unique value already exists in the database. In case there are no values which are marked as unique
the result is a duplicate.
A way to address this problem is to catch the error and in the catch block do a find
query because we know that the entry exists now, followed by any operations you intended to execute in the first place. Unfortunately though, this approach does become pretty ugly in your example, because of the nested findOrCreate
calls which require their own catch blocks each.
Find or Create with Eloquent
Below is the original accepted answer for: Laravel-4
There is already a method findOrFail
available in Laravel
and when this method is used it throws ModelNotFoundException
on fail but in your case you can do it by creating a method in your model, for example, if you have a User
model then you just put this function in the model
// Put this in any model and use
// Modelname::findOrCreate($id);
public static function findOrCreate($id)
{
$obj = static::find($id);
return $obj ?: new static;
}
From your controller, you can use
$user = User::findOrCreate(5);
$user->first_name = 'John';
$user->last_name = 'Doe';
$user->save();
If a user with id
of 5
exists, then it'll be updated, otherwise a new user will be created but the id
will be last_user_id + 1
(auto incremented).
This is another way to do the same thing:
public function scopeFindOrCreate($query, $id)
{
$obj = $query->find($id);
return $obj ?: new static;
}
Instead of creating a static method, you can use a scope
in the Model, so the method in the Model
will be scopeMethodName
and call Model::methodName()
, same as you did in the static method, for example
$user = User::findOrCreate(5);
Update:
The firstOrCreate
is available in Laravel 5x
, the answer is too old and it was given for Laravel-4.0
in 2013
.
In Laravel 5.3, the firstOrCreate
method has the following declaration:
public function firstOrCreate(array $attributes, array $values = [])
Which means you can use it like this:
User::firstOrCreate(['email' => $email], ['name' => $name]);
User's existence will be only checked via email, but when created, the new record will save both email and name.
API Docs
Rails find_or_create_by more than one attribute?
Multiple attributes can be connected with an and
:
GroupMember.find_or_create_by_member_id_and_group_id(4, 7)
(use find_or_initialize_by
if you don't want to save the record right away)
Edit: The above method is deprecated in Rails 4. The new way to do it will be:
GroupMember.where(:member_id => 4, :group_id => 7).first_or_create
and
GroupMember.where(:member_id => 4, :group_id => 7).first_or_initialize
Edit 2: Not all of these were factored out of rails just the attribute specific ones.
https://github.com/rails/rails/blob/4-2-stable/guides/source/active_record_querying.md
Example
GroupMember.find_or_create_by_member_id_and_group_id(4, 7)
became
GroupMember.find_or_create_by(member_id: 4, group_id: 7)
Rails - How to use Find Or Create
Related topic:
find_or_create_by in Rails 3 and updating for creating records
You can extend ActiveRecord with your own update_or_create
method (see related topic) and then you can use this
@permission = Permission.update_or_create_by_user_id_and_role_id_and_creator_id(@user.id, 2, current_user.id) do |p|
p.group_id = @group.id
end
Or you can use find_or_create_by...
method:
@permission = Permission.find_or_create_by_user_id_and_role_id_and_creator_id(@user.id, 2, current_user.id)
@permission.group = @group
@permission.save
Related Topics
Include Erb Delimiters Inside of a String in an Erb Block
Pg::Error: Error: Invalid Byte Sequence for Encoding "Utf8": 0Xfc
Selenium Webdriver Getting a Cookie Value
When to Use Keyword Arguments Aka Named Parameters in Ruby
Unexpected Keyword_End, Expecting $End (Syntaxerror)
Problem Installing Ruby 1.9.2 on MAC Os Lion
Encrypting/Decrypting 3Des in Ruby
Vagrant Ssh Not Working in Mobaxterm on Windows
Ruby Converting Utc to User's Time Zone
Net-Ssh and Remote Environment
Using Delta Indexes for Associations in Thinking Sphinx
How to Create an Operator for Deep Copy/Cloning of Objects in Ruby
Ruby on Rails: Converting "Somewordhere" to "Some Word Here"
Rails S Return: [Bug] Segmentation Fault
If 'Self' Is Always the Implied Receiver in Ruby, Why Doesn't 'Self.Puts' Work