【问题标题】:find_or_create_by in Rails 3 and updating for creating recordsRails 3 中的 find_or_create_by 和更新以创建记录
【发布时间】:2011-04-07 09:08:40
【问题描述】:

我不确定我是否应该以这种方式更新记录,或者我是否遗漏了什么。

我有一个包含 5 列(不包括时间戳和 id)的表,其中 3 列是不同的,2 列将得到更新。我将找到或创建的 3 个不同之处是 room_id、日期和来源。其他 2 个是可用的价格和位置(每小时、每天等变化)

我的问题是,我应该先查找或创建记录,然后更新(或创建)价格和位置,还是我可以一次完成所有操作?你可以看到我现在做的两种方式,我不确定它是否真的达到了我的预期。

另外,像这样进行 find_and_create_by 有什么缺点吗?

谢谢

  private

  def self.parse_data(params,data)
    data.beds.each do |bed|
      room = Room.find_or_create_room(bed.title, params[:id])

      #find clones somehow
      #puts bed.nights.first.price
      bed.nights.each_with_index do |night,index|
        available = Available.find_or_create_by_room_id_and_bookdate_and_source(
          :room_id => room.id, 
          :bookdate => (params[:date].to_date)+index, 
          :source => data.class.to_s#,
          #:price => night.price
        )
        #available.price = night.price
        #available.spots = night.spots
        #available.save
      end

    end

【问题讨论】:

标签: ruby-on-rails ruby-on-rails-3 activerecord dynamic-finders


【解决方案1】:

实际上,有一种方法无需任何黑客攻击。 您可以使用 find_or_initialize_by 而不是 find_or_create_by 并通过点击设置更新的属性

Available.find_or_initialize_by_room_id_and_bookdate_and_source(
  room.id, 
  (params[:date].to_date)+index, 
  data.class.to_s#
).tap do |a|
  a.price = night.price
  a.spots = night.spots
end.save!

最初,这个罐子看起来很杂乱,但它完全按照您的要求做。 找到记录,如果没有找到则实例化它并更新属性。 这可以称为“find_and_update_or_create_by”,幸运的是没有人这样做。 ;) 希望对您有所帮助。

【讨论】:

  • 这不是保存新记录!?
  • 你在 do 块之前有点击吗?
【解决方案2】:

这里有两种方法。

First 你可以用你需要的确切方法扩展Available

def self.find_or_create_by_room_id_and_bookdate_and_source(room_id, bookdate, source, &block)
  obj = self.find_by_room_id_and_bookdate_and_source( room_id, bookdate, source ) || self.new(:room_id => room_id, :bookdate => bookdate, :source => source)
  yield obj
  obj.save
end

用法

Available.find_or_create_by_room_id_and_bookdate_and_source(room.id, (params[:date].to_date)+index, data.class.to_s) do |c|
  c.price = night.price
  c.spots = night.spots
end

这很尴尬。因此,为了更加灵活,您可以使用 method_missing 魔术为 ActiveRecord 创建 update_or_create_by... 方法:

class ActiveRecord::Base
  def self.method_missing(method_id, *args, &block)
    method_name = method_id.to_s
    if method_name =~ /^update_or_create_by_(.+)$/
      update_or_create($1, *args, &block)
    else
      super
    end
  end
  def self.update_or_create(search, *args, &block)
    parameters = search.split("_and_")
    params = Hash[ parameters.zip(args) ]
    obj = where(params).first || self.new(params)
    yield obj
    obj.save
    obj
  end
end

所以现在你可以使用它了:

Available.update_or_create_by_id_and_source(20, "my_source") do |a|
  a.whatever = "coooool"
end

【讨论】:

  • 这是个好主意;但我得到这个错误:NoMethodError(未定义的方法find_by_id' for #<Class:0x9aaad6c>): config/initializers/active_record_monkey_patch.rb:7:in method_missing'
  • 你不应该! :) 看起来你的班级没有id 字段。你能写一个测试吗?
  • 漂亮的解决方案!不过,在同一文件中使用另一个动态方法(或间接使用使用动态方法的 gem)时,我得到了与 @DavidRyder 相同的错误。例如, find_or_create_by_* 给出未定义的方法错误。所以我怀疑super 部分不起作用。
  • 在@David Ryder 的案例中,它从超级方法(第 7 行)引发了一个错误,因此这意味着 AR method_missing 中有问题。
  • @fl00r 这也是我遇到的错误。尝试运行正常的动态查找器之一(例如 Available.find_or_create_by_room_id )时,您不会收到错误吗?在另一个类上怎么样,比如 Room.find_or_create_by_room_id ? (顺便说一句:我将 ActiveRecord 类代码插入到我进行所有调用的同一文件中......)。
【解决方案3】:

我认为最简单的方法是使用 Ruby 的 tap 方法,像这样:

def self.parse_data(params,data)
  data.beds.each do |bed|
    room = Room.find_or_create_room(bed.title, params[:id])

    bed.nights.each_with_index do |night,index|
      Available.find_or_initialize_by(room_id: room.id).tap do |available|
        available.bookdate = (params[:date].to_date) + index
        available.source = data.class.to_s
        available.price = night.price
        available.save
      end
    end
  end
end

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-13
    • 2013-09-26
    • 1970-01-01
    相关资源
    最近更新 更多