Ruby on rails 查找\u或按\u id创建\u的最佳方法,但如果找到记录,则更新属性

Ruby on rails 查找\u或按\u id创建\u的最佳方法,但如果找到记录,则更新属性,ruby-on-rails,ruby,activerecord,Ruby On Rails,Ruby,Activerecord,我正在寻找一种干净的方法,在记录不存在时创建一个具有一组属性的记录,如果记录确实存在,则更新其属性。我喜欢find_或create_by_id调用中块的语法。这是我的密码: @categories = Highrise::DealCategory.find(:all) @categories.each do |category| puts "Category: #{category.name}" Category.find_or_create_by_id(category.id) d

我正在寻找一种干净的方法,在记录不存在时创建一个具有一组属性的记录,如果记录确实存在,则更新其属性。我喜欢find_或create_by_id调用中块的语法。这是我的密码:

@categories = Highrise::DealCategory.find(:all)

@categories.each do |category|
  puts "Category: #{category.name}"

  Category.find_or_create_by_id(category.id) do |c|
    c.name = category.name
  end
end
这里的问题是,如果记录存在但名称已更改,则不会对其进行更新


寻找一个干净的解决方案来解决这个问题…

我昨天做了这件事,希望有一种方法可以一次性完成

最终使用(使用您的代码):

也许有更好的方法,但这就是我所用的


[编辑:使用initialize而不是create以避免两次命中数据库)

您可以编写自己的方法:

class ActiveRecord::Base
  def self.find_by_id_or_create(id, &block)
    obj = self.find_by_id( id ) || self.new
    yield obj
    obj.save
  end
end
用法

当然,通过这种方式,您应该扩展
method missing
方法,并以与其他
find\u by\u something
方法相同的方式实现此方法。但简而言之,这就足够了。

尝试以下方法:

c = Category.find_or_initialize_by_id(category.id)
c.name = category.name
c.save!

这样,如果调用
find\u或\u create\u by\u id
(假设它是一条新记录),则只保存实例一次,而不是两次。

我喜欢fl00r的答案。但是为什么每次都要保存对象呢? 我们可以检查它是否已经存在于记录中,或者保存它

def self.find_or_create_by_id(id, &block)
    obj = self.find_by_id(id) 
    unless obj
      obj = self.create(id: id)
    end
    obj
end

我已经编写了这个查找程序,可以用于不同的场景

最重要的是,它删除了创建和更新时的参数
:id

使用
:id
创建模型可能会导致
MySql
PostgreSQL
出现问题,因为Rails使用数据库的自动序列号。如果使用
:id
创建新模型实例,则会得到
唯一冲突:错误:重复键值违反唯一约束

# config/initializers/model_finders.rb
class ActiveRecord::Base

  def self.find_by_or_create(attributes, &block)
    self.find_by(attributes) || self.create(attributes.except(:id), &block)
  end


  def self.find_by_or_create!(attributes, &block)
    self.find_by(attributes) || self.create!(attributes.except(:id), &block)
  end


  def self.find_or_create_update_by(attributes, &block)
    self.find_by(attributes).try(:update, attributes.except(:id), &block) || self.create(attributes.except(:id), &block)
  end


  def self.find_or_create_update_by!(attributes, &block)
    self.find_by(attributes).try(:update!, attributes.except(:id), &block) || self.create!(attributes.except(:id), &block)
  end


  def self.find_by_or_initialize(attributes, &block)
    self.find_by(attributes) || new(attributes.except(:id), &block)
  end

end

我一直在使用这种模式来制作种子:

Category.find_or_initialize_by(id: category.id).update |c|
  c.name = category.name
end

它的工作原理与Dale Wijnand和Teoulas的答案相同(只保存实例一次),但使用了与您的问题类似的块。

我认为最简单的方法是使用Ruby的方法,如下所示:

Category.find_or_initialize_by(id: category.id).tap do |c|
  c.name = category.name
  c.save
end
另外,我将
find\u或\u create\u by\u id
更改为
find\u或\u initialize\u by
,否则它将命中数据库两次


find\u或\u initialize\u by
查找或初始化记录,然后返回。然后点击记录,进行更新并将其保存到数据库。

感谢您的快速回复。是的,这是我希望改进的。仅供参考,我非常确定您应该使用find\u或\u initialize\u by\u id来避免两次点击数据库。如果您真的想点击,可以使用tap让它成为一行漂亮的。这正是我所需要的!您可以使用
self.create(id:id)简化它
不是一个好主意。这可能会导致重复键唯一约束错误
错误:重复键值违反唯一约束
。请改用
self.new
。我不知道这是否适用,但在Rails的最新版本中,更新方法没有将块作为参数,因此这不起作用
Category.find_or_initialize_by(id: category.id).update |c|
  c.name = category.name
end
Category.find_or_initialize_by(id: category.id).tap do |c|
  c.name = category.name
  c.save
end