3

我在我的 Rails ActiveRecord 模型之一中添加了 before_create 过滤器,并且在该过滤器中我正在执行一些数据库更新。

有时我从过滤器返回 false 以防止创建目标模型,但这会导致我所做的所有其他数据库更改(在过滤器内)被回滚。

我怎样才能防止这种情况?

更新#1:这是一些解释我的问题的伪代码:

class Widget < ActiveRecord::Base
  before_create :update_instead

  def update_instead
    if some_condition?
      update_some_record_in_same_model # this is getting rolled back
      return false # don't create a new record
    else
      return true # let it continue
    end
  end
end

更新#2:下面有一些很好的答案,但每个都有其缺点。我最终像这样覆盖了 create 方法:

def create
  super unless update_instead? # yes I reversed the return values from above
end
4

5 回答 5

3

我最近不得不这样做。您需要专门从 AR 请求另一个连接。然后在该连接上执行您的更改。这样,如果创建失败并回滚事务,则回调的更改已经在不同的事务中提交。

忽略我上面的回答。您刚刚提供的示例代码非常清楚地说明了事情。

class Foo < ActiveRecord::Base
  before_create :update_instead

  def update_instead
    dbconn = self.class.connection_pool.checkout
    dbconn.transaction do
      dbconn.execute("update foos set name = 'updated'")
    end
    self.class.connection_pool.checkin(dbconn)
    false
  end
end


>> Foo.create(:name => 'sam')
=> #<Foo id: nil, name: "sam", created_at: nil, updated_at: nil>
>> Foo.all
=> [#<Foo id: 2, name: "updated", created_at: "2009-10-21 15:12:55", updated_at: "2009-10-21 15:12:55">]
于 2009-10-20T21:47:29.260 回答
2

在过滤器中使用事务。

于 2009-10-20T21:33:31.487 回答
1

您是否尝试过覆盖创建/保存及其破坏性版本?ActiveRecord::Base.create、ActiveRecord::Base.save 和它们的破坏性版本被包装在一个事务中,它们也是触发回调和验证的原因。如果您要覆盖它,则只有 super 完成的内容才会成为事务的一部分。如果您需要在此之前运行验证,则可以显式调用 valid 来运行它们。

例子:

before_create :before_create_actions_that_can_be_rolled_back

def create
  if valid? && before_create_actions_that_wont_be_rolled_back
    super
  end
end

def before_create_actions_that_wont_be_rolled_back
 # exactly what it sounds like
end

def before_create_actions_that_can_be_rolled_back
 # exactly what it sounds like
end

警告:通过这些修改,方法将按以下顺序调用:

  1. 验证前 (on_create)
  2. 证实
  3. 验证后(on_create)
  4. before_create_actions_that_wont_be_rolled_back
  5. 验证前 (on_create)
  6. 证实
  7. 验证后(on_create)
  8. 在保存回调之前
  9. 在创建回调之前
  10. 记录已创建
  11. 创建回调后
  12. 保存回调后

如果任何验证失败或任何回调在步骤 5-12 中返回 false,则数据库将回滚到步骤 5 之前的状态。

如果有效?失败,或者 before_create_actions_that_wont_be_rolled_back 失败,那么整个链将被停止。

于 2009-10-21T05:54:16.210 回答
0

这些改变可以after_create代替吗?

于 2009-10-20T21:36:59.593 回答
0

这使用与 Rich Cavanaugh 的答案相同的概念。我添加了一个父模型,以便更清楚过滤器在做什么。关键是使用线程+单独连接的自动签出/签入。注意:您应该确保您的 :pool 值在您的连接规范中至少设置为 2,具体取决于您将运行的并发线程数。我认为它默认为5。

class Gadget < ActiveRecord::Base
  has_many :widgets
end

class Widget < ActiveRecord::Base
  belongs_to :gadget
  before_create :update_instead

  def update_some_record_in_same_model
    # the thread forces a new connection to be checked out
    Thread.new do
      ActiveRecord::Base.connection_pool.with_connection do |conn|
        # try this part without the 2 surrounding blocks and it will be rolled back
        gadget.touch_count += 1
        gadget.save!
      end
    end.join
  end
  def some_condition?
    true
  end

  def update_instead
    if some_condition?
      update_some_record_in_same_model # this is getting rolled back
      p [:touch_count_in_filter, gadget.reload.touch_count]
      return false # don't create a new record
    else
      return true # let it continue
    end
  end
end

测试:

  g = Gadget.create(:name => 'g1')
  puts "before:"
  p [:touch_count, g.reload.touch_count]
  p [:widget_count, Widget.count]

  g.widgets.create(:name => 'w1')
  puts "after:"
  # Success means the count stays incremented
  p [:touch_count, g.reload.touch_count]
  p [:widget_count, Widget.count]

进一步阅读:http ://bibwild.wordpress.com/2011/11/14/multi-threading-in-rails-activerecord-3-0-3-1/

于 2012-02-17T18:24:08.583 回答