Ruby-on-rails Rails 创建或更新魔法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18747062/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Rails create or update magic?
提问by kid_drew
I have a class called CachedObjectthat stores generic serialised objects indexed by key. I want this class to implement a create_or_updatemethod. If an object is found it will update it, otherwise it will create a new one.
我有一个名为的类CachedObject,用于存储按键索引的通用序列化对象。我希望这个类实现一个create_or_update方法。如果找到一个对象,它将更新它,否则它将创建一个新对象。
Is there a way to do this in Rails or do I have to write my own method?
有没有办法在 Rails 中做到这一点,还是我必须编写自己的方法?
回答by Mohamad
Rails 6
导轨 6
Rails 6 added an upsertand upsert_allmethods that deliver this functionality.
Rails 6 添加了提供此功能的upsert和upsert_all方法。
Model.upsert(column_name: value)
[upsert] It does not instantiate any models nor does it trigger Active Record callbacks or validations.
[upsert] 它不会实例化任何模型,也不会触发 Active Record 回调或验证。
Rails 5, 4, and 3
轨道 5、4 和 3
Not if you are looking for an "upsert" (where the database executes an update or an insert statement in the same operation) type of statement. Out of the box, Rails and ActiveRecord have no such feature. You can use the upsertgem, however.
如果您正在寻找“upsert”(数据库在同一操作中执行更新或插入语句)类型的语句,则不会。开箱即用,Rails 和 ActiveRecord 没有这样的功能。但是,您可以使用upsertgem。
Otherwise, you can use: find_or_initialize_byor find_or_create_by, which offer similar functionality, albeit at the cost of an additional database hit, which, in most cases, is hardly an issue at all. So unless you have serious performance concerns, I would not use the gem.
否则,您可以使用: find_or_initialize_byor find_or_create_by,它们提供类似的功能,尽管需要额外的数据库访问,在大多数情况下,这根本不是问题。因此,除非您有严重的性能问题,否则我不会使用 gem。
For example, if no user is found with the name "Roger", a new user instance is instantiated with its nameset to "Roger".
例如,如果没有找到名称为“Roger”的用户,则会实例化一个新用户实例并将其name设置为“Roger”。
user = User.where(name: "Roger").first_or_initialize
user.email = "[email protected]"
user.save
Alternatively, you can use find_or_initialize_by.
或者,您可以使用find_or_initialize_by.
user = User.find_or_initialize_by(name: "Roger")
In Rails 3.
在 Rails 3 中。
user = User.find_or_initialize_by_name("Roger")
user.email = "[email protected]"
user.save
You can use a block, but the block only runs if the record is new.
您可以使用块,但块仅在记录为新时运行。
User.where(name: "Roger").first_or_initialize do |user|
# this won't run if a user with name "Roger" is found
user.save
end
User.find_or_initialize_by(name: "Roger") do |user|
# this also won't run if a user with name "Roger" is found
user.save
end
If you want to use a block regardless of the record's persistence, use tapon the result:
如果您想使用块而不考虑记录的持久性,请tap在结果上使用:
User.where(name: "Roger").first_or_initialize.tap do |user|
user.email = "[email protected]"
user.save
end
回答by montrealmike
In Rails 4 you can add to a specific model:
在 Rails 4 中,您可以添加到特定模型:
def self.update_or_create(attributes)
assign_or_new(attributes).save
end
def self.assign_or_new(attributes)
obj = first || new
obj.assign_attributes(attributes)
obj
end
and use it like
并像使用它一样
User.where(email: "[email protected]").update_or_create(name: "Mr A Bbb")
Or if you'd prefer to add these methods to all models put in an initializer:
或者,如果您希望将这些方法添加到放入初始化程序的所有模型中:
module ActiveRecordExtras
module Relation
extend ActiveSupport::Concern
module ClassMethods
def update_or_create(attributes)
assign_or_new(attributes).save
end
def update_or_create!(attributes)
assign_or_new(attributes).save!
end
def assign_or_new(attributes)
obj = first || new
obj.assign_attributes(attributes)
obj
end
end
end
end
ActiveRecord::Base.send :include, ActiveRecordExtras::Relation
回答by Karen
Add this to your model:
将此添加到您的模型中:
def self.update_or_create_by(args, attributes)
obj = self.find_or_create_by(args)
obj.update(attributes)
return obj
end
With that, you can:
有了它,你可以:
User.update_or_create_by({name: 'Joe'}, attributes)
回答by Imran Ahmad
The magic you have been looking for has been added in Rails 6Now you can upsert(update or insert).
For single record use:
您一直在寻找的魔法已添加到Rails 6现在您可以插入(更新或插入)。单条记录使用:
Model.upsert(column_name: value)
For multiple records use upsert_all:
对于多条记录,使用upsert_all:
Model.upsert_all(column_name: value, unique_by: :column_name)
Note:
注意:
- Both methods do not trigger Active Record callbacks or validations
- unique_by => PostgreSQL and SQLite only
- 这两种方法都不会触发 Active Record 回调或验证
- unique_by => 仅限 PostgreSQL 和 SQLite
回答by Aeramor
Old question but throwing my solution into the ring for completeness. I needed this when I needed a specific find but a different create if it doesn't exist.
老问题,但为了完整性将我的解决方案扔进了戒指。当我需要一个特定的发现时,我需要这个,但如果它不存在,则需要一个不同的创建。
def self.find_by_or_create_with(args, attributes) # READ CAREFULLY! args for finding, attributes for creating!
obj = self.find_or_initialize_by(args)
return obj if obj.persisted?
return obj if obj.update_attributes(attributes)
end
回答by KurtPreston
The sequelgem adds an update_or_createmethod which seems to do what you're looking for.
在续集宝石增加了一个update_or_create这似乎是你要寻找的方法。
回答by Rajesh Kolappakam
You can do it in one statement like this:
你可以在这样的一个语句中做到这一点:
CachedObject.where(key: "the given key").first_or_create! do |cached|
cached.attribute1 = 'attribute value'
cached.attribute2 = 'attribute value'
end

