find_or_create_by_id的最佳方法,但如果找到记录则更新属性

时间:2011-03-01 20:40:10

标签: ruby-on-rails ruby activerecord

如果记录不存在,并且 - 如果记录确实存在 - 更新其属性,我正在寻找一种干净的方法来创建具有一组属性的记录。我喜欢find_or_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

这里的问题是,如果记录存在但名称已更改,则不会更新。

寻找这个问题的干净解决方案......

7 个答案:

答案 0 :(得分:4)

您可以编写自己的方法:

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

使用

 Category.find_by_id_or_create(10) do |c|
   c.name = "My new name"
 end

当然,通过这种方式,您应该扩展method missing方法,并以与其他find_by_something方法相同的方式实现此方法。但是,为了做空,这就足够了。

答案 1 :(得分:1)

我编写了这个可用于不同场景的查找器。

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

使用:id创建模型可能会导致MySqlPostgreSQL出现问题,因为Rails使用数据库的自动序列号。如果您使用:id创建新模型实例,则可以获得UniqueViolation: ERROR: duplicate key value violates unique constraint

# 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

答案 2 :(得分:1)

我一直在使用这个模式种子:

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

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

答案 3 :(得分:0)

我昨天做了这个,希望有一种方法可以用一个班轮做。

结束(使用你的代码):

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

也许有一种更好的方式,但这就是我使用的方式。

[编辑:使用初始化而不是创建,以避免两次击中数据库)

答案 4 :(得分:0)

试试这个:

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

这样,如果您调用find_or_create_by_id(假设它是新记录),则只保存一次实例,而不是两次。

答案 5 :(得分:0)

我喜欢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

答案 6 :(得分:0)

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

SELECT orderNumber FROM orders EXCEPT
SELECT DISTINCT orderdetails.orderNumber FROM orderdetails JOIN products
ON orderdetails.productCode == products.productCode
WHERE products.productLine == "Planes";

此外,我将Category.find_or_initialize_by(id: category.id).tap do |c| c.name = category.name c.save end 更改为find_or_create_by_id,否则它将两次访问数据库。

find_or_initialize_by查找或初始化记录,然后将其返回。然后,我们点击它,进行更新并将其保存到数据库中。