【问题标题】:ActiveRecord: Update a record if exists else create?ActiveRecord:如果存在则更新记录,否则创建?
【发布时间】:2013-01-30 07:59:02
【问题描述】:

如果不存在则尝试在 Active Record 中执行创建,否则更新记录。

目前使用:

@student = Student.where(:user_id => current_user.id).first

if @student
    Student.destroy_all(:user_id => current_user.id)
end

Student = Student.new(:user_id => current_user.id, :department => 1
)
Student.save!

如果记录存在或创建它,更新记录的正确方法是什么?

【问题讨论】:

    标签: ruby-on-rails-3 activerecord


    【解决方案1】:

    在 Rails 4 中

    Student.
      find_or_initialize_by(:user_id => current_user.id).
      update_attributes!(:department => 1)
    

    【讨论】:

    • find_or_initialize_by + update_attributes! 可以解决问题
    • update_attributes! 的返回值是布尔值,而不是实际记录。你可以使用Student.find_or_initialize_by(...).tap { |e| e.update!(...) }
    • 有几个问题 - 首先,您无法“仅在创建时设置值”,其次,它查看表列而不是模型方法。例如,User.where(email: email).find_or_initialize_by(password: password) 会给你一个错误columns users.password does not exist(假设你是明智的并且没有在数据库中存储普通密码)。
    • 不错的解决方案。在 Rails 4 中,我只会将 .update_attributes 更改为 .update。这是惯用的做法,源代码显示它与 .update_attributes 相同! (它只会分配属性并调用.save ...检查apidock.com/rails/ActiveRecord/Persistence/updateapidock.com/rails/v3.2.3/ActiveRecord/Persistence/…)。
    • 请记住,find_or_initialize_by 可能不会触发任何验证
    【解决方案2】:

    您可能正在寻找first_or_create 或类似的东西:

    http://guides.rubyonrails.org/v3.2.17/active_record_querying.html#first_or_create

    【讨论】:

    • first_or_create 不会更新记录,因此不会让代码变得更好。
    • 但它确实返回找到或创建的记录,因此您可以将update 调用链接到它。请参阅此页面上的my answer
    • 这会导致创建验证出现问题并导致 2 个单独的数据库事务。 @Zorayr 的答案更可取,因为它尊重验证。
    • 确实如此。要通过验证,我认为您可以使用 first_or_initializefirst_or_create 完全相同的方式;但您必须在结果上调用saveupdate 以使其持久化。
    • @rune-schjellerup-philosof 但您可以在此之后更新它.first_or_create(user_id: current_user.id, department: 1).update(department: 1)
    【解决方案3】:

    ::first_or_create(自 v3.2.1 起可用)按照包装盒上的说明进行操作。

    Model.where(find: 'find_value').
      first_or_create(create: 'create_value')
    
    # If a record with {find: 'find_value'} already exists:
    before #=> #<Model id: 1, find: "find_value", create: nil>
    after  #=> #<Model id: 1, find: "find_value", create: nil>
    
    # Otherwise:
    before #=> nil
    after  #=> #<Model id: 2, find: "find_value", create: "create_value">
    

    如果您还希望它更新一个已经存在的记录,请尝试:

    Model.where(find: 'find_value').
      first_or_create(create: 'create_value').
      update(update: 'update_value')
    
    # If one already exists:
    before #=> #<Model id: 1, find: "find_value", create: nil, update: nil>
    after  #=> #<Model id: 1, find: "find_value", create: nil, update: "update_value">
    
    # If it already matches, no UPDATE statement will be run:
    before #=> #<Model id: 1, find: "find_value", create: nil, update: "update_value">
    after  #=> #<Model id: 1, find: "find_value", create: nil, update: "update_value">
    
    # Otherwise:
    before #=> nil
    after  #=> #<Model id: 2, find: "find_value", create: 'create_value', update: "update_value">
    

    编辑 2016-03-08:根据Dougcomment,如果您的验证在#create#update 调用之间失败,或者您想最小化数据库调用,您可以使用::first_or_initialize相反,以避免在第一次调用时保留记录。但是,您必须确保在之后调用 #save#update 以保留记录(我不确定 #update 是否适用于尚未保留的记录):

    Model.validates :update, presence: true # The create call would fail this
    
    Model.where(find: 'find_value').
      first_or_initialize(create: 'create_value'). # doesn't call validations
      update(update: 'update_value')
    

    (注意。一个名为#create_or_update的方法,但不要被您在Google上找到的任何documentation所迷惑;这只是#save使用的私有方法。 )

    【讨论】:

      【解决方案4】:
      @student = Student.where(user_id: current_user.id).first
      @student ||= Student.new(user_id: current_user.id)
      @student.department_id = 1
      @student.save
      

      如果您在用户和学生之间有关联,这会更漂亮。类似于

      @student = current_user.student || current_user.build_student
      @student.department_id = 1
      @student.save
      

      编辑:

      您也可以使用由sevenseacat 回答的http://guides.rubyonrails.org/active_record_querying.html#first_or_create,但您仍然需要处理不同的情况,例如更新学生的部门ID。

      更新:

      你可以使用find_or_create_by

      @student = Student.find_or_create_by(user_id: current_user.id) do |student|
        student.department_id = 1
      end
      

      【讨论】:

        【解决方案5】:

        在 Rails 5 中

        Student.
          find_or_initialize_by(:user_id => current_user.id).
          update(:department => 1)
        

        (所有功劳均来自@Zorayr 的回答)。

        【讨论】:

          【解决方案6】:

          不幸的是,我认为最干净的方法是:

          Student.where(user_id: id).first_or_create(age: 16).update_attribute(:age, 16)
          

          【讨论】:

            【解决方案7】:

            它是 find_or_create_by 而不是 first_or_create
            前任: Client.find_or_create_by(first_name: 'Andy')

            【讨论】:

            • first_or_create 确实存在,(甚至在一年前),我不确定你想用这个答案说什么,但这听起来像是对另一篇帖子的评论。
            • 两者的工作方式也略有不同。 Model.where(find: 'find_value').first_or_create(create: 'create_value') 等价于 Model.find_or_create_by(find: 'find_value') { |r| r.create = 'create_value' }
            猜你喜欢
            • 2016-01-13
            • 1970-01-01
            • 1970-01-01
            • 2010-12-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-10-09
            相关资源
            最近更新 更多