I have an observer and I register an after_commit
callback. How can I tell whether it was fired after create or update? I can tell an item was destroyed by asking item.destroyed?
but #new_record?
doesn't work since the item was saved.
我有一个观察者,我注册了一个after_commit回调。我如何知道它是在创建或更新后被触发的?我可以通过询问物品来判断物品是否被销毁?但是# new_record呢?由于保存了项目,所以无法工作。
I was going to solve it by adding after_create
/after_update
and do something like @action = :create
inside and check the @action
at after_commit
, but it seems that the observer instance is a singleton and I might just override a value before it gets to the after_commit
. So I solved it in an uglier way, storing the action in a map based on the item.id on after_create/update and checking its value on after_commit. Really ugly.
我将通过添加after_create/after_update来解决这个问题,并做一些类似于@action =:在after_commit中创建并检查@action,但是看起来观察者实例是单例的,我可能只是在它到达after_commit之前重写一个值。所以我以一种更丑陋的方式解决了这个问题,将这个动作存储在一个基于这个项目的地图中。在after_create/update上的id,并在after_commit上检查其值。真的很丑。
Is there any other way?
还有别的办法吗?
Update
As @tardate said, transaction_include_action?
is a good indication, though it's a private method, and in an observer it should be accessed with #send
.
@tardate说过,transaction_include_action吗?这是一个很好的指示,尽管它是一个私有方法,并且在观察者中应该使用#send访问它。
class ProductScoreObserver < ActiveRecord::Observer
observe :product
def after_commit(product)
if product.send(:transaction_include_action?, :destroy)
...
Unfortunately, the :on
option does not work in observers.
不幸的是,on选项在观察者中不起作用。
Just make sure you test the hell of your observers (look for test_after_commit
gem if you use use_transactional_fixtures) so when you upgrade to new Rails version you'll know if it still works.
只要确保您测试了大量的观察者(如果您使用use_transactional_fixtures的话,请查找test_after_commit gem),这样当您升级到新的Rails版本时,您就会知道它是否仍然有效。
(Tested on 3.2.9)
3.2.9(测试)
Update 2
Instead of Observers I now use ActiveSupport::Concern and after_commit :blah, on: :create
works there.
我现在使用ActiveSupport::Concern和after_commit:blah, on: create工作。
9 个解决方案
#1
50
I think transaction_include_action? is what you are after. It gives a reliable indication of the specific transaction in process (verified in 3.0.8).
我认为transaction_include_action吗?是你所追求的。它给出了正在处理的特定事务的可靠指示(在3.0.8中验证)。
Formally, it determines if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
在形式上,它确定事务是否包含用于:create、:update或:destroy的操作。用于过滤回调。
class Item < ActiveRecord::Base
after_commit lambda {
Rails.logger.info "transaction_include_action?(:create): #{transaction_include_action?(:create)}"
Rails.logger.info "transaction_include_action?(:destroy): #{transaction_include_action?(:destroy)}"
Rails.logger.info "transaction_include_action?(:update): #{transaction_include_action?(:update)}"
}
end
Also of interest may be transaction_record_state which can be used to determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
同样值得关注的还有transaction_record_state,它可以用来确定在事务中创建或销毁记录。状态应该是:new_record或:destroy。
Update for Rails 4
Rails更新4
For those seeking to solve the problem in Rails 4, this method is now deprecated, you should use transaction_include_any_action?
which accepts an array
of actions.
对于那些想要解决Rails 4中的问题的人,这个方法现在已经被弃用了,您应该使用transaction_include_any_action?它接受一系列动作。
Usage Example:
使用的例子:
transaction_include_any_action?([:create])
#2
54
I've learned today that you can do something like this:
我今天学到,你可以做这样的事情:
after_commit :do_something, :on => :create
after_commit :do_something, :on => :update
Where do_something is the callback method you want to call on certain actions.
do_something是要调用某些操作的回调方法。
If you want to call the same callback for update and create, but not destroy, you can also use: after_commit :do_something, :if => :persisted?
如果您希望为更新和创建调用相同的回调,但不销毁,您还可以使用:after_commit:do_something,: If =>:persist ?
It's really not documented well and I had a hard time Googling it. Luckily, I know a few brilliant people. Hope it helps!
它没有被很好地记录下来,而且我很难在谷歌上搜索它。幸运的是,我认识一些聪明的人。希望它可以帮助!
#3
7
You can solve by using two techniques.
你可以用两种方法来解决。
-
The approach suggested by @nathanvda i.e. checking the created_at and updated_at. If they are same, the record is newly created, else its an update.
@nathanvda建议的方法,即检查created_at和updated_at。如果它们是相同的,记录是新创建的,否则就是更新。
-
By using virtual attributes in the model. Steps are:
通过在模型中使用虚拟属性。步骤是:
- Add a field in the model with the code
attr_accessor newly_created
- 使用代码attr_accessor新创建的代码在模型中添加一个字段。
-
Update the same in the
before_create
andbefore_update callbacks
as在before_create和before_update回调中更新相同的内容
def before_create (record) record.newly_created = true end def before_update (record) record.newly_created = false end
- Add a field in the model with the code
#4
3
Based on leenasn idea, I created some modules that makes it possible to use after_commit_on_update
and after_commit_on_create
callbacks: https://gist.github.com/2392664
基于leenasn思想,我创建了一些模块,使使用after_commit_on_updateand after_commit_on_create回调成为可能:https://gist.github.com/2392664
Usage:
用法:
class User < ActiveRecord::Base
include AfterCommitCallbacks
after_commit_on_create :foo
def foo
puts "foo"
end
end
class UserObserver < ActiveRecord::Observer
def after_commit_on_create(user)
puts "foo"
end
end
#5
2
Take a look at the test code: https://github.com/rails/rails/blob/master/activerecord/test/cases/transaction_callbacks_test.rb
看看测试代码:https://github.com/rails/rails/blob/master/activerecord/test/cases/transaction_callbacks_test.rb
There you can find:
在那里你可以找到:
after_commit(:on => :create)
after_commit(:on => :update)
after_commit(:on => :destroy)
and
和
after_rollback(:on => :create)
after_rollback(:on => :update)
after_rollback(:on => :destroy)
#6
0
I'm curious to know why you couldn't move your after_commit
logic into after_create
and after_update
. Is there some important state change that happens between the latter 2 calls and after_commit
?
我很想知道为什么不能将after_commit逻辑移动到after_create和after_update中。后两个调用和after_commit之间是否发生了一些重要的状态更改?
If your create and update handling has some overlapping logic, you could just have the latter 2 methods call a third method, passing in the action:
如果您的创建和更新处理有一些重叠的逻辑,您可以让后两个方法调用第三个方法,传递操作:
# Tip: on ruby 1.9 you can use __callee__ to get the current method name, so you don't have to hardcode :create and :update.
class WidgetObserver < ActiveRecord::Observer
def after_create(rec)
# create-specific logic here...
handler(rec, :create)
# create-specific logic here...
end
def after_update(rec)
# update-specific logic here...
handler(rec, :update)
# update-specific logic here...
end
private
def handler(rec, action)
# overlapping logic
end
end
If you still rather use after_commit, you can use thread variables. This won't leak memory as long as dead threads are allowed to be garbage-collected.
如果仍然使用after_commit,则可以使用线程变量。只要允许垃圾收集死线程,就不会泄漏内存。
class WidgetObserver < ActiveRecord::Observer
def after_create(rec)
warn "observer: after_create"
Thread.current[:widget_observer_action] = :create
end
def after_update(rec)
warn "observer: after_update"
Thread.current[:widget_observer_action] = :update
end
# this is needed because after_commit also runs for destroy's.
def after_destroy(rec)
warn "observer: after_destroy"
Thread.current[:widget_observer_action] = :destroy
end
def after_commit(rec)
action = Thread.current[:widget_observer_action]
warn "observer: after_commit: #{action}"
ensure
Thread.current[:widget_observer_action] = nil
end
# isn't strictly necessary, but it's good practice to keep the variable in a proper state.
def after_rollback(rec)
Thread.current[:widget_observer_action] = nil
end
end
#7
-1
This is similar to your 1st approach but it only uses one method (before_save or before_validate to really be safe) and I don't see why this would override any value
这类似于您的第一种方法,但它只使用一个方法(before_save或before_validate以确保安全性),我不明白为什么它会覆盖任何值
class ItemObserver
def before_validation(item) # or before_save
@new_record = item.new_record?
end
def after_commit(item)
@new_record ? do_this : do_that
end
end
Update
This solution doesn't work because as stated by @eleano, ItemObserver is a Singleton, it has only one instance. So if 2 Item are saved at the same time @new_record could take its value from item_1 while after_commit is triggered by item_2. To overcome this problem there should be an item.id
checking/mapping to "post-synchornize" the 2 callback methods : hackish.
这个解决方案不起作用,因为如@eleano所述,ItemObserver是一个单例,它只有一个实例。因此,如果同时保存2项,@new_record可以从item_1获取其值,而item_2触发after_commit。要解决这个问题,应该有一个项目。id检查/映射到“后synchornize”两个回调方法:hackish。
#8
-1
I use the following code to determine whether it is a new record or not:
我使用以下代码来确定它是否是一个新的记录:
previous_changes[:id] && previous_changes[:id][0].nil?
It based on idea that a new record has default id equal to nil and then changes it on save. Of course id changing is not a common case, so in most cases the second condition can be omitted.
它基于一个新记录的默认id为nil,然后在保存时更改它的想法。当然,id更改不是一种常见的情况,因此在大多数情况下可以省略第二个条件。
#9
-4
You can change your event hook from after_commit to after_save, to capture all create and update events. You can then use:
您可以将事件挂钩从after_commit更改为after_save,以捕获所有创建和更新事件。然后,您可以使用:
id_changed?
...helper in the observer. This will be true on create and false on an update.
…辅助的观察者。这在创建时为真,在更新时为假。
#1
50
I think transaction_include_action? is what you are after. It gives a reliable indication of the specific transaction in process (verified in 3.0.8).
我认为transaction_include_action吗?是你所追求的。它给出了正在处理的特定事务的可靠指示(在3.0.8中验证)。
Formally, it determines if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
在形式上,它确定事务是否包含用于:create、:update或:destroy的操作。用于过滤回调。
class Item < ActiveRecord::Base
after_commit lambda {
Rails.logger.info "transaction_include_action?(:create): #{transaction_include_action?(:create)}"
Rails.logger.info "transaction_include_action?(:destroy): #{transaction_include_action?(:destroy)}"
Rails.logger.info "transaction_include_action?(:update): #{transaction_include_action?(:update)}"
}
end
Also of interest may be transaction_record_state which can be used to determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
同样值得关注的还有transaction_record_state,它可以用来确定在事务中创建或销毁记录。状态应该是:new_record或:destroy。
Update for Rails 4
Rails更新4
For those seeking to solve the problem in Rails 4, this method is now deprecated, you should use transaction_include_any_action?
which accepts an array
of actions.
对于那些想要解决Rails 4中的问题的人,这个方法现在已经被弃用了,您应该使用transaction_include_any_action?它接受一系列动作。
Usage Example:
使用的例子:
transaction_include_any_action?([:create])
#2
54
I've learned today that you can do something like this:
我今天学到,你可以做这样的事情:
after_commit :do_something, :on => :create
after_commit :do_something, :on => :update
Where do_something is the callback method you want to call on certain actions.
do_something是要调用某些操作的回调方法。
If you want to call the same callback for update and create, but not destroy, you can also use: after_commit :do_something, :if => :persisted?
如果您希望为更新和创建调用相同的回调,但不销毁,您还可以使用:after_commit:do_something,: If =>:persist ?
It's really not documented well and I had a hard time Googling it. Luckily, I know a few brilliant people. Hope it helps!
它没有被很好地记录下来,而且我很难在谷歌上搜索它。幸运的是,我认识一些聪明的人。希望它可以帮助!
#3
7
You can solve by using two techniques.
你可以用两种方法来解决。
-
The approach suggested by @nathanvda i.e. checking the created_at and updated_at. If they are same, the record is newly created, else its an update.
@nathanvda建议的方法,即检查created_at和updated_at。如果它们是相同的,记录是新创建的,否则就是更新。
-
By using virtual attributes in the model. Steps are:
通过在模型中使用虚拟属性。步骤是:
- Add a field in the model with the code
attr_accessor newly_created
- 使用代码attr_accessor新创建的代码在模型中添加一个字段。
-
Update the same in the
before_create
andbefore_update callbacks
as在before_create和before_update回调中更新相同的内容
def before_create (record) record.newly_created = true end def before_update (record) record.newly_created = false end
- Add a field in the model with the code
#4
3
Based on leenasn idea, I created some modules that makes it possible to use after_commit_on_update
and after_commit_on_create
callbacks: https://gist.github.com/2392664
基于leenasn思想,我创建了一些模块,使使用after_commit_on_updateand after_commit_on_create回调成为可能:https://gist.github.com/2392664
Usage:
用法:
class User < ActiveRecord::Base
include AfterCommitCallbacks
after_commit_on_create :foo
def foo
puts "foo"
end
end
class UserObserver < ActiveRecord::Observer
def after_commit_on_create(user)
puts "foo"
end
end
#5
2
Take a look at the test code: https://github.com/rails/rails/blob/master/activerecord/test/cases/transaction_callbacks_test.rb
看看测试代码:https://github.com/rails/rails/blob/master/activerecord/test/cases/transaction_callbacks_test.rb
There you can find:
在那里你可以找到:
after_commit(:on => :create)
after_commit(:on => :update)
after_commit(:on => :destroy)
and
和
after_rollback(:on => :create)
after_rollback(:on => :update)
after_rollback(:on => :destroy)
#6
0
I'm curious to know why you couldn't move your after_commit
logic into after_create
and after_update
. Is there some important state change that happens between the latter 2 calls and after_commit
?
我很想知道为什么不能将after_commit逻辑移动到after_create和after_update中。后两个调用和after_commit之间是否发生了一些重要的状态更改?
If your create and update handling has some overlapping logic, you could just have the latter 2 methods call a third method, passing in the action:
如果您的创建和更新处理有一些重叠的逻辑,您可以让后两个方法调用第三个方法,传递操作:
# Tip: on ruby 1.9 you can use __callee__ to get the current method name, so you don't have to hardcode :create and :update.
class WidgetObserver < ActiveRecord::Observer
def after_create(rec)
# create-specific logic here...
handler(rec, :create)
# create-specific logic here...
end
def after_update(rec)
# update-specific logic here...
handler(rec, :update)
# update-specific logic here...
end
private
def handler(rec, action)
# overlapping logic
end
end
If you still rather use after_commit, you can use thread variables. This won't leak memory as long as dead threads are allowed to be garbage-collected.
如果仍然使用after_commit,则可以使用线程变量。只要允许垃圾收集死线程,就不会泄漏内存。
class WidgetObserver < ActiveRecord::Observer
def after_create(rec)
warn "observer: after_create"
Thread.current[:widget_observer_action] = :create
end
def after_update(rec)
warn "observer: after_update"
Thread.current[:widget_observer_action] = :update
end
# this is needed because after_commit also runs for destroy's.
def after_destroy(rec)
warn "observer: after_destroy"
Thread.current[:widget_observer_action] = :destroy
end
def after_commit(rec)
action = Thread.current[:widget_observer_action]
warn "observer: after_commit: #{action}"
ensure
Thread.current[:widget_observer_action] = nil
end
# isn't strictly necessary, but it's good practice to keep the variable in a proper state.
def after_rollback(rec)
Thread.current[:widget_observer_action] = nil
end
end
#7
-1
This is similar to your 1st approach but it only uses one method (before_save or before_validate to really be safe) and I don't see why this would override any value
这类似于您的第一种方法,但它只使用一个方法(before_save或before_validate以确保安全性),我不明白为什么它会覆盖任何值
class ItemObserver
def before_validation(item) # or before_save
@new_record = item.new_record?
end
def after_commit(item)
@new_record ? do_this : do_that
end
end
Update
This solution doesn't work because as stated by @eleano, ItemObserver is a Singleton, it has only one instance. So if 2 Item are saved at the same time @new_record could take its value from item_1 while after_commit is triggered by item_2. To overcome this problem there should be an item.id
checking/mapping to "post-synchornize" the 2 callback methods : hackish.
这个解决方案不起作用,因为如@eleano所述,ItemObserver是一个单例,它只有一个实例。因此,如果同时保存2项,@new_record可以从item_1获取其值,而item_2触发after_commit。要解决这个问题,应该有一个项目。id检查/映射到“后synchornize”两个回调方法:hackish。
#8
-1
I use the following code to determine whether it is a new record or not:
我使用以下代码来确定它是否是一个新的记录:
previous_changes[:id] && previous_changes[:id][0].nil?
It based on idea that a new record has default id equal to nil and then changes it on save. Of course id changing is not a common case, so in most cases the second condition can be omitted.
它基于一个新记录的默认id为nil,然后在保存时更改它的想法。当然,id更改不是一种常见的情况,因此在大多数情况下可以省略第二个条件。
#9
-4
You can change your event hook from after_commit to after_save, to capture all create and update events. You can then use:
您可以将事件挂钩从after_commit更改为after_save,以捕获所有创建和更新事件。然后,您可以使用:
id_changed?
...helper in the observer. This will be true on create and false on an update.
…辅助的观察者。这在创建时为真,在更新时为假。