ActiveRecordコールバックの実行を回避するにはどうすればよいですか?


140

after_saveコールバックを持つモデルがいくつかあります。通常は問題ありませんが、開発データの作成時など、コールバックを実行せずにモデルを保存したい場合があります。それを行う簡単な方法はありますか?のようなもの...

Person#save( :run_callbacks => false )

または

Person#save_without_callbacks

Railsのドキュメントを調べても何も見つかりませんでした。ただし、私の経験では、Railsのドキュメントが常にすべての話を伝えるとは限りません。

更新

次のようなモデルからコールバックを削除する方法を説明するブログ投稿を見つけまし

Foo.after_save.clear

その方法が文書化されている場所を見つけることができませんでしたが、うまくいくようです。


8
コールバックで破壊的または費用のかかる(メールの送信など)何かをしている場合は、これを外して、コントローラーまたは他の場所とは別にトリガーすることをお勧めします。これにより、開発などで「誤って」トリガーされなくなります
ryanb

2
あなたが受け入れた解決策は私のために働いていません。私はこのようなエラーになっています。3.レールを使用しています: -未定義のメソッド`update_without_callbacks'#のために<ユーザー:0x10ae9b848>
Mohitジャイナ

yaaそのブログ投稿はうまくいきました...
Mohit Jain


Foo.after_save.clearモデル全体のコールバックを削除しませんか?そして、どのようにそれらを復元することを提案しますか?
Joshua Pinter

回答:


72

このソリューションはRails 2のみです。

これを調査したところ、解決策はあると思います。使用できる2つのActiveRecordプライベートメソッドがあります。

update_without_callbacks
create_without_callbacks

これらのメソッドを呼び出すには、sendを使用する必要があります。例:

p = Person.new(:name => 'foo')
p.send(:create_without_callbacks)

p = Person.find(1)
p.send(:update_without_callbacks)

これは間違いなく、コンソールで、またはいくつかのランダムテストを実行しているときにのみ使用したいものです。お役に立てれば!


7
それは私のために働いていません。私はこのようなエラーになっています。3.レールを使用しています: -未定義のメソッド`update_without_callbacks'#のために<ユーザー:0x10ae9b848>
Mohitジャイナ

あなたの提案が動作していませんが、更新部分に言及したブログ記事が働いている...
Mohitジャイナ

これも検証をスキップします。
ダニエルピエツシュ

Railsのどのバージョンにも別のソリューションがあります。うまくいきます。私のブログ投稿で確認してください:railsguides.net/2014/03/25/skip-callbacks-in-tests
ka8725

224

使用update_column(Railsの> = V3.1)またはupdate_columnsコールバックおよび検証をスキップする(Railsの> = 4.0)。これらのメソッドでupdated_at更新されません

#Rails >= v3.1 only
@person.update_column(:some_attribute, 'value')
#Rails >= v4.0 only
@person.update_columns(attributes)

http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update_column

#2:オブジェクトの作成中にも機能するコールバックをスキップする

class Person < ActiveRecord::Base
  attr_accessor :skip_some_callbacks

  before_validation :do_something
  after_validation :do_something_else

  skip_callback :validation, :before, :do_something, if: :skip_some_callbacks
  skip_callback :validation, :after, :do_something_else, if: :skip_some_callbacks
end

person = Person.new(person_params)
person.skip_some_callbacks = true
person.save

2
2.xでも動作するように見え、同様に動作する他の多くのメソッドがあります。guides.rubyonrails.org
rogerdpack

15
これは対応していない:create_without_callbacks:(どのように私はそれに似た何かを実行することができます(Rails3で削除Rails2、で働いていた)?。
nzifnab

@personどこかのコントローラーの変数であると仮定すると、このソリューションは、モデルクラスを読んでいる人がコールバックを理解できないことを意味します。彼らはafter_create :something_cool「作成後、素晴らしい、素晴らしい何かが起こる!」を見て、考えます。モデルクラスを実際に理解するには、すべてのコントローラーをgrepして、ロジックを挿入することにしたすべての小さな場所を探す必要があります。気に入らない> o <;;
ジギー

1
これをafter_createで正しく実行するには、skip_callback ..., if: :skip_some_callbackswith replace を使用after_create ..., unless: :skip_some_callbacksしてください。
sakurashinken

28

更新しました:

@Vikrant Chaudharyのソリューションはより良いようです:

#Rails >= v3.1 only
@person.update_column(:some_attribute, 'value')
#Rails >= v4.0 only
@person.update_columns(attributes)

私の元の答え:

このリンクを参照してください: ActiveRecordコールバックをスキップする方法?

Rails3では、

クラス定義があると仮定します。

class User < ActiveRecord::Base
  after_save :generate_nick_name
end 

アプローチ1:

User.send(:create_without_callbacks)
User.send(:update_without_callbacks)

アプローチ2:rspecファイルなどでスキップする場合は、次のようにしてください。

User.skip_callback(:save, :after, :generate_nick_name)
User.create!()

注:これが完了したら、rspec環境でない場合は、コールバックをリセットする必要があります。

User.set_callback(:save, :after, :generate_nick_name)

Rails 3.0.5では私にとってはうまくいきます


20

レール3:

MyModel.send("_#{symbol}_callbacks") # list  
MyModel.reset_callbacks symbol # reset

11
いいね。またMyModel.skip_callbackはすべてlobangためactivesupportの::コールバック::クラスメソッドのドキュメントを参照正確なコントロールのために...(:my_callback:、後:作成)
tardate

4
有用な情報:の「記号」はでreset_callbacks:after_saveなく:saveです。 apidock.com/rails/v3.0.9/ActiveSupport/Callbacks/ClassMethods/...
nessur

19

コールバックや検証なしで単純にレコードを挿入することが目標であり、追加のgemに頼らずに、条件付きチェックを追加したり、RAW SQLを使用したり、既存のコードと何らかの方法でファッツしたりする場合は、「シャドウ既存のdbテーブルを指すオブジェクト」。そのようです:

class ImportedPerson < ActiveRecord::Base
  self.table_name = 'people'
end

これはRailsのすべてのバージョンで機能し、スレッドセーフであり、既存のコードを変更することなく、すべての検証とコールバックを完全に排除します。実際のインポートの直前にそのクラス宣言を投げるだけでよいので、それで問題ありません。次のように、新しいクラスを使用してオブジェクトを挿入することを忘れないでください。

ImportedPerson.new( person_attributes )

4
これまでで最高のソリューション。エレガントでシンプル!
Rafael Oliveira

1
これは私にとって非常にうまく機能しました。テストでのみ、データベースの「前」の状態をシミュレートし、生​​産モデルオブジェクトを機械で汚染することなくコールバックをスキップできるようにしたかったためです。
ダグラス

1
断然ベストアンサー
robomc

1
既存のレール制約を回避する方法を示し、オブジェクト全体のMVCが実際にどのように機能するかを理解するのに役立つため、賛成です。とてもシンプルできれい。
Michael Schmitz

17

Personモデルで次のようなことを試すことができます。

after_save :something_cool, :unless => :skip_callbacks

def skip_callbacks
  ENV[RAILS_ENV] == 'development' # or something more complicated
end

編集: after_saveはシンボルではありませんが、それを作成しようと試みたのは少なくとも1,000回目です。


1
これがここでの最良の答えです。このようにして、コールバックがスキップされるタイミングを決定するロジックがモデルで使用可能になり、ビジネスロジックをピールバックしたり、でカプセル化を回避したりするどこにもクレイジーなコードフラグメントはありませんsend。KOODOS
ジギー

10

使用できますupdate_columns

User.first.update_columns({:name => "sebastian", :age => 25})

saveを呼び出さずに、オブジェクトの指定された属性を更新します。そのため、検証とコールバックをスキップします。


7

すべてのafter_saveコールバックを防ぐ唯一の方法は、最初のコールバックがfalseを返すようにすることです。

おそらくあなたは(試されていない)のようなものを試すことができます:

class MyModel < ActiveRecord::Base
  attr_accessor :skip_after_save

  def after_save
    return false if @skip_after_save
    ... blah blah ...
  end
end

...

m = MyModel.new # ... etc etc
m.skip_after_save = true
m.save

1
試してみるのが大好きです スリルに乗る。
アダマンティッシュ

テスト済みで動作します。これは非常に優れたクリーンなソリューションだと思います。ありがとう!
カーニフィケーション

5

Rails 2.3でこれを処理する1つの方法のように見えます(update_without_callbacksがなくなったためなど)、updates_allを使用することになります。これは、Rails Guides for validations and callbacksのセクション12のコールバックをスキップするメソッドの1つです。

また、after_コールバックで何かをしている場合、それは多くの関連付けに基づいて計算を行います(つまり、has_many assoc、ここでもaccepts_nested_attributes_forを行います)、保存の一部としての場合に備えて、関連付けを再ロードする必要があります。 、そのメンバーの1つが削除されました。



4

up-voted場合によっては、ほとんどの答えがわかりにくいかもしれません。

次のifように、コールバックをスキップする場合は、単純なチェックを使用できます。

after_save :set_title, if: -> { !new_record? && self.name_changed? }

3

Gemやプラグインを使用せずにRailsのすべてのバージョンで機能するソリューションは、単にupdateステートメントを直接発行することです。例えば

ActiveRecord::Base.connection.execute "update table set foo = bar where id = #{self.id}"

これは、更新がどれほど複雑かによって、オプションになる場合とできない場合があります。これは、(コールバックを再トリガーせずに)after_saveコールバックからレコードのフラグを更新する場合などに役立ちます。


なぜ反対票かはわかりませんが、私はまだ上記の答えは正当だと思います。ActiveRecordの動作に関する問題を回避する最善の方法は、ActiveRecordの使用を回避することです。
Dave Smylie、2014

原則として-1に対抗することに賛成。新しいレコード(更新ではない)を作成する必要があり、コールバックの起動が壊滅的であった生産の問題(その背後に長い話があります)があっただけです。上記の答えはすべて、彼らがそれを認めるかどうかにかかわらずハックであり、DBに行くことが最善の解決策でした。これには正当な条件があります。ただし、SQLインジェクションには注意が必要です。#{...}
sinisterchipmunk 2014

1
# for rails 3
  if !ActiveRecord::Base.private_method_defined? :update_without_callbacks
    def update_without_callbacks
      attributes_with_values = arel_attributes_values(false, false, attribute_names)
      return false if attributes_with_values.empty?
      self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
    end
  end

1

これらのどれも、without_callbacks必要なことだけを行うプラグインを指していません...

class MyModel < ActiveRecord::Base
  before_save :do_something_before_save

  def after_save
    raise RuntimeError, "after_save called"
  end

  def do_something_before_save
    raise RuntimeError, "do_something_before_save called"
  end
end

o = MyModel.new
MyModel.without_callbacks(:before_save, :after_save) do
  o.save # no exceptions raised
end

http://github.com/cjbottaro/without_callbacksはRails 2.xで動作します


1

Rails 3でupdate_without_callbacksを実装するプラグインを作成しました。

http://github.com/dball/skip_activerecord_callbacks

正しい解決策は、そもそもコールバックを回避するようにモデルを書き直すことだと思いますが、短期的にそれが実用的でない場合は、このプラグインが役立つかもしれません。


1

Rails 2を使用している場合、SQLクエリを使用して、コールバックと検証を実行せずに列を更新できます。

YourModel.connection.execute("UPDATE your_models SET your_models.column_name=#{value} WHERE your_models.id=#{ym.id}")

Railsのどのバージョンでも機能するはずです。


1

コールバックを完全に制御する必要がある場合は、スイッチとして使用する別の属性を作成します。シンプルで効果的:

モデル:

class MyModel < ActiveRecord::Base
  before_save :do_stuff, unless: :skip_do_stuff_callback
  attr_accessor :skip_do_stuff_callback

  def do_stuff
    puts 'do stuff callback'
  end
end

テスト:

m = MyModel.new()

# Fire callbacks
m.save

# Without firing callbacks
m.skip_do_stuff_callback = true
m.save

# Fire callbacks again
m.skip_do_stuff_callback = false
m.save


1

sneaky-save gemを使用できます:https : //rubygems.org/gems/sneaky-save

これは、検証なしでは関連付けを保存するのに役立ちません。モデルとは異なり、SQLクエリを直接挿入するため、「created_atをnullにすることはできません」というエラーがスローされます。これを実装するには、自動生成されたdbのすべての列を更新する必要があります。


1

Rails 4のソリューションが必要だったので、これを思いつきました。

app / models / concerns / save_without_callbacks.rb

module SaveWithoutCallbacks

  def self.included(base)
    base.const_set(:WithoutCallbacks,
      Class.new(ActiveRecord::Base) do
        self.table_name = base.table_name
      end
      )
  end

  def save_without_callbacks
    new_record? ? create_without_callbacks : update_without_callbacks
  end

  def create_without_callbacks
    plain_model = self.class.const_get(:WithoutCallbacks)
    plain_record = plain_model.create(self.attributes)
    self.id = plain_record.id
    self.created_at = Time.zone.now
    self.updated_at = Time.zone.now
    @new_record = false
    true
  end

  def update_without_callbacks
    update_attributes = attributes.except(self.class.primary_key)
    update_attributes['created_at'] = Time.zone.now
    update_attributes['updated_at'] = Time.zone.now
    update_columns update_attributes
  end

end

どのモデルでも:

include SaveWithoutCallbacks

その後、次のことができます。

record.save_without_callbacks

または

Model::WithoutCallbacks.create(attributes)

0

開発でこれを実行できるようにしたいのはなぜですか?確かに、これは無効なデータを使用してアプリケーションを構築していることを意味します。そのため、本番環境では予期しない動作をします。

開発データベースにデータを入力する場合、より良いアプローチは、有効なデータを構築するためにfaker gemを使用するレーキタスクを構築し、それをdbにインポートして、必要な数のレコードを作成することですが、必要に応じてそれを曲げて、十分な理由があります.update_without_callbacksとcreate_without_callbacksは正常に機能すると思いますが、レールを自分の意志に曲げようとしているときは、十分な理由があるかどうか、自分がしていることが本当に良い考えかどうかを自問してください。


検証なしで、コールバックなしで保存しようとはしていません。私のアプリは、コールバックを使用して、静的HTMLをファイルシステム(CMSのようなもの)に書き込みます。開発データをロードしている間、私はそれをしたくありません。
イーサン、

単なる考えでしたが、過去にこの種の質問を見たときはいつでも、それが悪い理由で何かを回避しようとしているのだと思います。
nitecoder、2009年

0

1つのオプションは、同じテーブルを使用して、そのような操作のための個別のモデルを用意することです。

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

(同じアプローチを使用すると、検証をバイパスしやすくなります)

ステファン


0

別の方法は、コールバックの代わりに検証フックを使用することです。例えば:

class Person < ActiveRecord::Base
  validate_on_create :do_something
  def do_something
    "something clever goes here"
  end
end

これにより、デフォルトでdo_somethingを取得できますが、次のように簡単にオーバーライドできます。

@person = Person.new
@person.save(false)

3
これは悪い考えのようです-意図された目的のために物事を使うべきです。あなたが望む最後のものはあなたの検証に副作用があることです。
chug2k 2012年

0

ActiveRecordオプションまたは存在しない可能性のあるactiverecordメソッドに依存せずに、のすべてのバージョンで動作するはずのもの。

module PlainModel
  def self.included(base)
    plainclass = Class.new(ActiveRecord::Base) do
      self.table_name = base.table_name
    end
    base.const_set(:Plain, plainclass)
  end
end


# usage
class User < ActiveRecord::Base
  include PlainModel

  validates_presence_of :email
end

User.create(email: "")        # fail due to validation
User::Plain.create(email: "") # success. no validation, no callbacks

user = User::Plain.find(1)
user.email = ""
user.save

TLDR:同じテーブルで「別のアクティブレコードモデル」を使用する


0

カスタムコールバックの場合は、コールバックでattr_accessorandを使用unlessします。

次のようにモデルを定義します。

class Person << ActiveRecord::Base

  attr_accessor :skip_after_save_callbacks

  after_save :do_something, unless: :skip_after_save_callbacks

end

そして、after_save定義したコールバックをヒットせずにレコードを保存する必要がある場合は、skip_after_save_callbacks仮想属性をに設定しますtrue

person.skip_after_save_callbacks #=> nil
person.save # By default, this *will* call `do_something` after saving.

person.skip_after_save_callbacks = true
person.save # This *will not* call `do_something` after saving.

person.skip_after_save_callbacks = nil # Always good to return this value back to its default so you don't accidentally skip callbacks.

-5

最もクリーンな方法ではありませんが、Rails環境をチェックする条件でコールバックコードをラップできます。

if Rails.env == 'production'
  ...
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.