Bagaimana saya bisa menghindari menjalankan panggilan balik ActiveRecord?

140

Saya memiliki beberapa model yang memiliki panggilan balik after_save. Biasanya itu bagus, tetapi dalam beberapa situasi, seperti ketika membuat data pengembangan, saya ingin menyimpan model tanpa menjalankan callback. Apakah ada cara sederhana untuk melakukan itu? Sesuatu yang mirip ...

Person#save( :run_callbacks => false )

atau

Person#save_without_callbacks

Saya mencari di Rails docs dan tidak menemukan apa pun. Namun dalam pengalaman saya Rails docs tidak selalu menceritakan keseluruhan cerita.

MEMPERBARUI

Saya menemukan posting blog yang menjelaskan bagaimana Anda dapat menghapus callback dari model seperti ini:

Foo.after_save.clear

Saya tidak dapat menemukan di mana metode itu didokumentasikan tetapi tampaknya berhasil.

Ethan
sumber
8
Jika Anda melakukan sesuatu yang merusak atau mahal (seperti mengirim email) dalam panggilan balik, saya sarankan untuk memindahkannya dan memicunya secara terpisah dari pengontrol atau di tempat lain. Dengan cara ini Anda tidak akan "sengaja" memicu dalam pengembangan, dll
ryanb
2
solusi yang Anda terima tidak bekerja untuk saya. Saya menggunakan rel 3. Saya mendapatkan kesalahan seperti ini: - metode yang tidak ditentukan `update_without_callbacks 'untuk # <Pengguna: 0x10ae9b848>
Mohit Jain
yaa posting blog itu berhasil ....
Mohit Jain
1
Pertanyaan terkait: stackoverflow.com/questions/19449019/…
Allerin
Tidak akan Foo.after_save.clearmenghapus callback untuk seluruh model? Lalu bagaimana Anda mengusulkan untuk mengembalikannya?
Joshua Pinter

Jawaban:

72

Solusi ini hanya untuk Rails 2.

Saya baru saja menyelidiki ini dan saya pikir saya punya solusi. Ada dua metode pribadi ActiveRecord yang dapat Anda gunakan:

update_without_callbacks
create_without_callbacks

Anda harus menggunakan kirim untuk memanggil metode ini. contoh:

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

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

Ini jelas sesuatu yang hanya ingin Anda gunakan di konsol atau saat melakukan beberapa tes acak. Semoga ini membantu!

efalcao
sumber
7
ini tidak bekerja untuk saya. Saya menggunakan rel 3. Saya mendapatkan kesalahan seperti ini: - metode yang tidak ditentukan `update_without_callbacks 'untuk # <Pengguna: 0x10ae9b848>
Mohit Jain
Saran Anda tidak berfungsi tetapi posting blog yang disebutkan di bagian pembaruan berfungsi ..
Mohit Jain
Ini juga akan melewati validasi.
Daniel Pietzsch
Saya punya solusi lain untuk versi Rails apa pun. Ini bekerja dengan baik untuk kita. Lihat di posting blog saya: railsguides.net/2014/03/25/skip-callbacks-in-tests
ka8725
224

Gunakan update_column(Rails> = v3.1) atau update_columns(Rails> = 4.0) untuk melewati panggilan balik dan validasi. Juga dengan metode ini, updated_atadalah tidak diperbarui.

#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: Melewati panggilan balik yang juga berfungsi saat membuat objek

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
Vikrant Chaudhary
sumber
2
sepertinya itu berfungsi dengan 2.x juga, dan ada sejumlah metode lain yang beroperasi serupa: guides.rubyonrails.org/…
rogerdpack
15
Ini tidak membahas :create_without_callbacks:( Bagaimana saya bisa menjalankan sesuatu yang mirip dengan itu? (Bekerja di Rails2, dihapus di
Rails3
Dengan asumsi @personadalah variabel di controller di suatu tempat, solusi ini berarti bahwa orang yang membaca kelas model Anda tidak akan dapat memahami callback. Mereka akan melihat after_create :something_cooldan akan berpikir "hebat, sesuatu yang keren terjadi setelah membuat!". Untuk benar-benar memahami kelas model Anda, mereka harus memeriksa semua pengontrol Anda, mencari semua tempat kecil di mana Anda telah memutuskan untuk menyuntikkan logika. Saya tidak suka> o <;;
Ziggy
1
ganti skip_callback ..., if: :skip_some_callbacksdengan after_create ..., unless: :skip_some_callbacksuntuk menjalankan ini dengan benar dengan after_create.
sakurashinken
28

Diperbarui:

Solusi @ Vikrant Chaudhary tampaknya lebih baik:

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

Jawaban asli saya:

lihat tautan ini: Bagaimana cara melewatkan callback ActiveRecord?

dalam Rails3,

anggap kita memiliki definisi kelas:

class User < ActiveRecord::Base
  after_save :generate_nick_name
end 

Pendekatan1:

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

Approach2: Ketika Anda ingin melewatkannya di file rspec Anda atau apa pun, coba ini:

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

CATATAN: setelah ini selesai, jika Anda tidak berada di lingkungan rspec, Anda harus mengatur ulang panggilan balik:

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

berfungsi dengan baik untuk saya di rel 3.0.5

Siwei Shen 申思维
sumber
20

rel 3:

MyModel.send("_#{symbol}_callbacks") # list  
MyModel.reset_callbacks symbol # reset
guai
sumber
11
Bagus. Juga MyModel.skip_callback (: membuat,: setelah,: my_callback) untuk kontrol yang tepat .. lihat ActiveSupport :: Callback :: classmethods docs untuk semua Lobang yang
tardate
4
Info berguna: 'simbol' di reset_callbacksbukan :after_save, melainkan :save. apidock.com/rails/v3.0.9/ActiveSupport/Callbacks/ClassMethods/…
nessur
19

Jika tujuannya adalah hanya menyisipkan catatan tanpa panggilan balik atau validasi, dan Anda ingin melakukannya tanpa menggunakan permata tambahan, menambahkan cek bersyarat, menggunakan RAW SQL, atau menghapus kode yang keluar dengan cara apa pun, pertimbangkan untuk menggunakan bayangan "menunjuk ke tabel db yang ada. Seperti itu:

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

Ini berfungsi dengan setiap versi Rails, aman untuk threads, dan sepenuhnya menghilangkan semua validasi dan panggilan balik tanpa modifikasi pada kode Anda yang ada. Anda bisa melempar deklarasi kelas itu tepat sebelum impor Anda yang sebenarnya, dan Anda harus melakukannya dengan baik. Ingatlah untuk menggunakan kelas baru Anda untuk menyisipkan objek, seperti:

ImportedPerson.new( person_attributes )
Brad Werth
sumber
4
Solusi terbaik yang pernah ada. Elegan dan sederhana!
Rafael Oliveira
1
Ini bekerja dengan sangat baik bagi saya karena itu adalah sesuatu yang ingin saya lakukan hanya dalam pengujian, untuk mensimulasikan basis data "sebelum", tanpa mencemari objek model produksi saya dengan mesin untuk secara opsional melewatkan callback.
Douglas Lovell
1
Sejauh ini jawaban terbaik
robomc
1
Terpilih karena menunjukkan cara mengatasi kendala rel yang ada dan membantu saya memahami cara kerja seluruh objek MVC. Sangat sederhana dan bersih.
Michael Schmitz
17

Anda dapat mencoba sesuatu seperti ini di model Person Anda:

after_save :something_cool, :unless => :skip_callbacks

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

EDIT: after_save bukan simbol, tapi setidaknya 1.000 kali saya mencoba membuatnya.

Sarah Mei
sumber
1
Saya benar-benar berpikir ini adalah jawaban terbaik di sini. Dengan cara ini logika yang menentukan kapan callback dilewati tersedia dalam model, dan Anda tidak memiliki fragmen kode gila di mana-mana mengupas logika bisnis, atau menghindari enkapsulasi dengan send. KOODOS
Ziggy
10

Anda bisa menggunakan update_columns:

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

Memperbarui atribut objek yang diberikan, tanpa memanggil save, karenanya melewatkan validasi dan callback.

Luís Ramalho
sumber
7

Satu-satunya cara untuk mencegah semua panggilan balik after_save adalah membuat yang pertama mengembalikan false.

Mungkin Anda bisa mencoba sesuatu seperti (belum diuji):

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
rfunduk
sumber
1
Saya suka mencoba (belum diuji). Perjalanan yang menyenangkan.
Adamantish
Diuji dan berhasil. Saya pikir ini adalah solusi yang sangat bagus dan bersih, terima kasih!
kernifikasi
5

Sepertinya satu cara untuk menangani hal ini di Rails 2.3 (karena update_without_callbacks tidak ada, dll.), Adalah dengan menggunakan update_all, yang merupakan salah satu metode yang melewatkan callback sesuai dengan bagian 12 dari Panduan Rails untuk validasi dan callback .

Juga, perhatikan bahwa jika Anda melakukan sesuatu di callback after_ Anda, yang melakukan perhitungan berdasarkan banyak asosiasi (mis. Asosiasi has_many, di mana Anda juga melakukan accepts_nested_attributes_for), Anda perlu memuat ulang asosiasi, jika-kalau sebagai bagian dari save , salah satu anggotanya telah dihapus.

chrisrbailey
sumber
4

https://gist.github.com/576546

cukup buang monyet-patch ini ke config / initializers / skip_callbacks.rb

kemudian

Project.skip_callbacks { @project.save }

atau sejenisnya.

semua kredit kepada penulis

fringd
sumber
4

up-votedJawaban yang paling mungkin tampak membingungkan dalam beberapa kasus.

Anda dapat menggunakan hanya ifcek sederhana jika Anda ingin melewatkan panggilan balik, seperti ini:

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

Solusi yang harus bekerja di semua versi Rails tanpa menggunakan permata atau plugin adalah dengan mengeluarkan pernyataan pembaruan secara langsung. misalnya

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

Ini mungkin (atau mungkin tidak) menjadi opsi tergantung pada seberapa kompleksnya pembaruan Anda. Ini bekerja dengan baik untuk misalnya memperbarui bendera pada catatan dari dalam panggilan balik after_save (tanpa retrigger panggilan balik).

Dave Smylie
sumber
Tidak yakin mengapa downvote, tapi saya masih berpikir jawaban di atas sah. Terkadang cara terbaik untuk menghindari masalah dengan perilaku ActiveRecord adalah dengan menghindari menggunakan ActiveRecord.
Dave Smylie
Terpilih pada prinsip untuk melawan -1. Kami baru saja mengalami masalah produksi (dengan cerita panjang di belakangnya) yang mengharuskan kami untuk membuat catatan baru (bukan pembaruan) dan memanggil kembali akan menjadi bencana besar. Semua jawaban di atas adalah retas apakah mereka mengakuinya atau tidak dan pergi ke DB adalah solusi terbaik. ADA syarat yang sah untuk ini. Meskipun orang harus berhati-hati dengan injeksi SQL dengan #{...}.
sinisterchipmunk
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
Sasha Alexandrov
sumber
1

Tak satu pun dari poin ini ke without_callbacksplugin yang hanya melakukan apa yang Anda butuhkan ...

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 berfungsi dengan Rails 2.x

kares
sumber
1

Saya menulis sebuah plugin yang mengimplementasikan update_without_callbacks di Rails 3:

http://github.com/dball/skip_activerecord_callbacks

Solusi yang tepat, saya pikir, adalah menulis ulang model Anda untuk menghindari panggilan balik di tempat pertama, tetapi jika itu tidak praktis dalam waktu dekat, plugin ini dapat membantu.

Donald ball
sumber
1

Jika Anda menggunakan Rails 2. Anda bisa menggunakan query SQL untuk memperbarui kolom Anda tanpa menjalankan panggilan balik dan validasi.

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

Saya pikir itu harus bekerja di versi rel.

oivoodoo
sumber
1

Ketika saya membutuhkan kontrol penuh atas panggilan balik, saya membuat atribut lain yang digunakan sebagai saklar. Sederhana dan efektif:

Model:

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

Uji:

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
tothemario
sumber
1

Untuk membuat data uji di Rails Anda menggunakan peretasan ini:

record = Something.new(attrs)
ActiveRecord::Persistence.instance_method(:create_record).bind(record).call

https://coderwall.com/p/y3yp2q/edit

Wojtek Kruszewski
sumber
1

Anda dapat menggunakan permata licik-simpan: https://rubygems.org/gems/sneaky-save .

Perhatikan ini tidak dapat membantu dalam menyimpan asosiasi tanpa validasi. Itu melempar kesalahan 'Created_at tidak boleh nol' karena langsung memasukkan permintaan sql tidak seperti model. Untuk mengimplementasikan ini, kita perlu memperbarui semua kolom db yang dihasilkan secara otomatis.

Zinin Serge
sumber
1

Saya membutuhkan solusi untuk Rails 4, jadi saya datang dengan ini:

app / model / concern / 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

dalam model apa pun:

include SaveWithoutCallbacks

maka kamu bisa:

record.save_without_callbacks

atau

Model::WithoutCallbacks.create(attributes)
Steve Friedman
sumber
0

Mengapa Anda ingin dapat melakukan ini dalam pengembangan? Tentunya ini berarti Anda sedang membangun aplikasi Anda dengan data yang tidak valid dan karena itu akan berperilaku aneh dan tidak seperti yang Anda harapkan dalam produksi.

Jika Anda ingin mengisi dev db Anda dengan data, pendekatan yang lebih baik adalah membangun tugas menyapu yang menggunakan permata faker untuk membangun data yang valid dan mengimpornya ke db membuat sebanyak atau beberapa catatan yang Anda inginkan, tetapi jika Anda lemah membungkuk dan memiliki alasan yang baik saya kira pembaruan_without_callbacks dan create_without_callbacks tidak akan berfungsi dengan baik, tetapi ketika Anda mencoba membengkokkan rel ke kehendak Anda, tanyakan pada diri sendiri Anda memiliki alasan yang baik dan jika apa yang Anda lakukan benar-benar ide yang bagus.

nitecoder
sumber
Saya tidak mencoba menabung tanpa validasi, hanya tanpa panggilan balik. Aplikasi saya menggunakan panggilan balik untuk menulis beberapa HTML statis ke sistem file (semacam CMS). Saya tidak ingin melakukan itu saat memuat data dev.
Ethan
Itu hanya sebuah pemikiran, saya kira setiap kali di masa lalu saya telah melihat pertanyaan semacam ini mencoba untuk menyelesaikan masalah dengan alasan yang buruk.
nitecoder
0

Salah satu opsi adalah memiliki model terpisah untuk manipulasi seperti itu, menggunakan tabel yang sama:

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

(Pendekatan yang sama mungkin membuat segalanya lebih mudah untuk melewati validasi)

Stephan

Stephan Wehner
sumber
0

Cara lain adalah dengan menggunakan kait validasi alih-alih callback. Sebagai contoh:

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

Dengan begitu Anda bisa mendapatkan do_something secara default, tetapi Anda dapat dengan mudah menimpanya dengan:

@person = Person.new
@person.save(false)
Ryan Crispin Heneise
sumber
3
Ini sepertinya ide yang buruk - Anda harus menggunakan sesuatu untuk tujuan yang dimaksudkan. Hal terakhir yang Anda inginkan adalah validasi Anda memiliki efek samping.
chug2k
0

Sesuatu yang seharusnya bekerja dengan semua versi ActiveRecordtanpa tergantung pada opsi atau metode activerecord yang mungkin ada atau tidak ada.

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: gunakan "model activerecord yang berbeda" di atas tabel yang sama

choonkeat
sumber
0

Untuk panggilan balik khusus, gunakan a attr_accessordan aunless di dalam panggilan balik.

Tentukan model Anda sebagai berikut:

class Person << ActiveRecord::Base

  attr_accessor :skip_after_save_callbacks

  after_save :do_something, unless: :skip_after_save_callbacks

end

Dan kemudian jika Anda perlu menyimpan catatan tanpa menekan after_savecallback yang Anda tetapkan, atur skip_after_save_callbacksatribut virtual ke 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.
Joshua Pinter
sumber
-5

Bukan cara terbersih, tetapi Anda bisa membungkus kode panggilan balik dalam kondisi yang memeriksa lingkungan Rails.

if Rails.env == 'production'
  ...
james
sumber