ActiveRecord geri çağrılarını çalıştırmayı nasıl önleyebilirim?


140

After_save geri çağrıları olan bazı modellerim var. Genellikle bu iyidir, ancak bazı durumlarda, geliştirme verileri oluştururken olduğu gibi, geri çağrıları çalıştırmadan modelleri kaydetmek istiyorum. Bunu yapmanın basit bir yolu var mı? Benzer bir şey ...

Person#save( :run_callbacks => false )

veya

Person#save_without_callbacks

Rails belgelerine baktım ve hiçbir şey bulamadım. Ancak deneyimlerime göre Rails belgeleri her zaman tüm hikayeyi anlatmıyor.

GÜNCELLEME

Böyle bir modelden geri aramaları nasıl kaldırabileceğinizi açıklayan bir blog yazısı buldum :

Foo.after_save.clear

Bu yöntemin nerede belgelendiğini bulamadım ama işe yarıyor gibi görünüyor.


8
Geri aramada yıkıcı veya pahalı bir şey (e-posta göndermek gibi) yapıyorsanız, bunu dışarı çıkarmanızı ve denetleyiciden veya başka bir yerden ayrı olarak tetiklemenizi öneririz. Bu şekilde "yanlışlıkla" geliştirme, vb.
Tetiklemeyeceksiniz

2
kabul ettiğiniz çözüm benim için çalışmıyor. Ben raylar 3 kullanıyorum. - # <Kullanıcı: 0x10ae9b848> için tanımsız yöntem `` update_without_callbacks ''
Mohit Jain

yaa blog yazısı çalıştı ....
Mohit Jain


Olmaz Foo.after_save.cleartüm model için geri aramalar kaldırmak? Ve sonra onları nasıl geri yüklemeyi öneriyorsunuz?
Joshua Pinter

Yanıtlar:


72

Bu çözüm yalnızca Rails 2'dir.

Sadece araştırdım ve bir çözüm bulduğumu düşünüyorum. Kullanabileceğiniz iki ActiveRecord özel yöntemi vardır:

update_without_callbacks
create_without_callbacks

Bu yöntemleri çağırmak için send'i kullanmanız gerekecek. örnekler:

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

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

Bu kesinlikle sadece konsolda veya bazı rastgele testler yaparken kullanmak isteyeceğiniz bir şeydir. Bu yardımcı olur umarım!


7
benim için çalışmıyor. Ben raylar 3 kullanıyorum. - # <Kullanıcı: 0x10ae9b848> için tanımsız yöntem `` update_without_callbacks ''
Mohit Jain

Öneriniz çalışmıyor ancak güncelleme bölümünde adı geçen blog yazısı çalışıyor ..
Mohit Jain

Bu da doğrulamaları atlayacaktır.
Daniel Pietzsch

Rails'in herhangi bir sürümü için başka bir çözümüm var. Bizim için iyi çalışıyor. Blog gönderime bir göz at: railsguides.net/2014/03/25/skip-callbacks-in-tests
ka8725

224

Geri aramaları ve doğrulamaları atlamak için update_column(Rails> = v3.1) veya update_columns(Rails> = 4.0) tuşlarını kullanın . Ayrıca bu yöntemlerle, updated_atbir değil güncellendi.

#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: Nesne oluştururken de çalışan geri çağrıları atlama

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 ile de çalışıyor gibi görünüyor ve benzer şekilde çalışan başka yöntemler de var: guides.rubyonrails.org/…
rogerdpack

15
Bu adres :create_without_callbacks:( Buna benzer bir şeyi nasıl çalıştırabilirim? (Rails2'de çalıştı, Rails3'te kaldırıldı).
nzifnab

Bir yerdeki @persondenetleyicideki bir değişken olduğu varsayıldığında , bu çözüm, model sınıfınızı okuyan kişilerin geri çağrıları anlayamayacağı anlamına gelir. Onlar after_create :something_cool"harika, yarattıktan sonra harika bir şey olur!" Görecek ve düşüneceklerdir. Model sınıfınızı gerçekten anlamak için, tüm denetleyicilerinizi gözden geçirmeleri ve mantığı enjekte etmeye karar verdiğiniz tüm küçük yerleri aramaları gerekir. Sevmiyorum> o <;;
Ziggy

1
yerine skip_callback ..., if: :skip_some_callbackssahip after_create ..., unless: :skip_some_callbacksafter_create ile düzgün bu çalıştırmak için.
sakurashinken

28

Güncellenmiş:

@Vikrant Chaudhary'nin çözümü daha iyi görünüyor:

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

Orijinal cevabım:

bu bağlantıya bakın: ActiveRecord geri çağrılarını nasıl atlayabilirim?

Rails3'te,

bir sınıf tanımımız olduğunu varsayalım:

class User < ActiveRecord::Base
  after_save :generate_nick_name
end 

Approach1:

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

Yaklaşım2: rspec dosyalarınızda veya başka bir şeyde atlamak istediğinizde şunu deneyin:

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

NOT: bu yapıldıktan sonra, rspec ortamında değilseniz, geri çağrıları sıfırlamanız gerekir:

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

raylar üzerinde benim için iyi çalışıyor 3.0.5



19

Amaç geri arama veya doğrulama olmadan bir kayıt eklemekse ve ek taşlara başvurmadan, koşullu kontroller eklemeden, RAW SQL kullanarak veya herhangi bir şekilde çıkış kodunuzla fütür etmeden yapmak istiyorsanız, "gölge" mevcut db tablonuzu gösterecektir. Şöyle ki:

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

Bu, Rails'in her sürümü ile çalışır, threadsafe'tir ve mevcut kodunuzda değişiklik yapılmadan tüm doğrulama ve geri çağrıları tamamen ortadan kaldırır. Bu sınıf bildirimini gerçek içe aktarmanızdan hemen önce atabilirsiniz ve gitmek için iyi olmalısınız. Nesneyi eklemek için yeni sınıfınızı kullanmayı unutmayın, örneğin:

ImportedPerson.new( person_attributes )

4
Şimdiye kadar en iyi çözüm. Zarif ve basit!
Rafael Oliveira

1
Bu benim için gerçekten iyi çalıştı çünkü isteğe bağlı olarak geri çağrıları atlamak için üretim modeli nesnesini makine ile kirletmeden, sadece "testte" durumunu simüle etmek, sadece testte yapmak istediğim bir şeydi.
Douglas Lovell

1
Şimdiye kadar en iyi cevap
robomc

1
Mevcut raylar kısıtlamalarının nasıl çözüleceğini gösterdiğinden ve MVC'nin tüm nesnesinin gerçekten nasıl çalıştığını anlamama yardımcı olduğu için seçildi. Çok basit ve temiz.
Michael Schmitz

17

Kişi modelinizde böyle bir şey deneyebilirsiniz:

after_save :something_cool, :unless => :skip_callbacks

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

DÜZENLEME: after_save bir sembol değil, ama en azından 1000'inci kez yapmaya çalıştım.


1
Gerçekten bu sorunun en iyi cevap olduğunu düşünüyorum. Bu şekilde, geri aramanın ne zaman atlanacağını belirleyen mantık modelde kullanılabilir ve her yerde iş mantığını geri püskürten veya enkapsülasyonu atlatan çılgın kod parçalarınız yoktur send. KOODOS
Ziggy

10

Şunları kullanabilirsiniz update_columns:

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

Bir nesnenin verilen özniteliklerini, kaydet çağrısı yapmadan günceller, böylece doğrulamaları ve geri aramaları atlar.


7

Tüm after_save geri çağrılarını engellemenin tek yolu, ilki yanlış döndürmektir.

Belki (denenmemiş) gibi bir şey deneyebilirsiniz:

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
Denemeyi seviyorum (denenmemiş). Heyecan yolculuğu.
Adamantish

Test edildi ve çalışıyor. Bence bu çok iyi ve temiz bir çözüm, teşekkürler!
kernification

5

Bunu Rails 2.3'te ele almanın bir yolu gibi görünüyor (update_without_callbacks gittiğinden, vb.), Doğrulamalar ve geri aramalar için Rails Rehberinin 12. bölümüne göre geri çağrıları atlayan yöntemlerden biri olan update_all kullanmak olacaktır .

Ayrıca, after_ geri çağrınızda bir şey yapıyorsanız, bunun birçok ilişkilendirmeye (yani has_many assoc, burada kabul etmeyi kabul ettiğiniz_veya_atışmalar_for) dayalı bir hesaplama yaparsa, kaydetmenin bir parçası olması durumunda ilişkilendirmeyi yeniden yüklemeniz gerektiğini unutmayın , üyelerinden biri silindi.


4

https://gist.github.com/576546

bu maymun yamasını config / initializers / skip_callbacks.rb dosyasına atmanız yeterli

sonra

Project.skip_callbacks { @project.save }

veya benzeri.

yazara verilen tüm krediler


4

En çok up-votedcevap bazı durumlarda kafa karıştırıcı görünebilir.

ifBir geri aramayı atlamak istiyorsanız, basit bir kontrolü kullanabilirsiniz , örneğin:

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

3

Bir mücevher veya eklenti kullanmadan Rails'in tüm sürümlerinde çalışması gereken bir çözüm, doğrudan güncelleme bildirimlerini yayınlamaktır. Örneğin

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

Bu, güncellemenizin ne kadar karmaşık olduğuna bağlı olarak bir seçenek olabilir (veya olmayabilir). Bu, örneğin bir kayıt sonrası geri çağrının içinden (geri çağrıyı geri almadan) bayrakların güncellenmesi için iyi çalışır .


Neden aşağı oy emin değilim, ama yine de yukarıdaki cevabın meşru olduğunu düşünüyorum. Bazen ActiveRecord davranışı ile ilgili sorunları önlemenin en iyi yolu ActiveRecord kullanmaktan kaçınmaktır.
Dave Smylie

-1'e karşı prensipte oy verildi. Sadece yeni bir kayıt (güncelleme değil) oluşturmamızı gerektiren bir üretim sorunumuz vardı (arkasında uzun bir hikaye vardı) ve geri çağrıları tetiklemek felaket olurdu. Yukarıdaki yanıtların tümü, ister itiraf olsun ister olmasın ve DB'ye gitmek en iyi çözümdü. Bunun için meşru koşullar var. Gerçi biri ile SQL enjeksiyon dikkat etmelidir #{...}.
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

1

Bu noktaların hiçbiri without_callbackssadece ihtiyacınız olanı yapan eklentiye işaret ediyor ...

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 ile çalışır



1

Rails 2 kullanıyorsanız. Geri arama ve doğrulama çalıştırmadan sütununuzu güncellemek için SQL sorgusunu kullanabilirsiniz.

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

Ray sürümlerinde çalışması gerektiğini düşünüyorum.


1

Geri arama üzerinde tam kontrole ihtiyaç duyduğumda, anahtar olarak kullanılan başka bir özellik oluşturuyorum. Basit ve etkili:

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

Ölçek:

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

Sinsi tasarruflu taş kullanabilirsiniz: https://rubygems.org/gems/sneaky-save .

Bunun, doğrulama olmadan ilişkilendirmelerin kaydedilmesine yardımcı olamayacağını unutmayın. Bir modelden farklı olarak sql sorgusunu doğrudan eklediğinden 'created_at null olamaz' hatası veriyor. Bunu uygulamak için tüm otomatik oluşturulan db sütunlarını güncellememiz gerekir.


1

Rails 4 için bir çözüme ihtiyacım vardı, bu yüzden bunu buldum:

Uygulamanın / modeller / kaygılar / 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

herhangi bir modelde:

include SaveWithoutCallbacks

o zaman yapabilirsin:

record.save_without_callbacks

veya

Model::WithoutCallbacks.create(attributes)

0

Bunu neden geliştirme aşamasında yapmak istesin ki? Elbette bu, uygulamanızı geçersiz verilerle oluşturduğunuz anlamına gelir ve bu nedenle, üretimde beklediğiniz gibi değil, garip davranacaktır.

Dev db'nizi verilerle doldurmak istiyorsanız, daha iyi bir yaklaşım, geçerli verileri oluşturmak ve istediğiniz kadar çok veya az kayıt oluşturarak db'ye aktarmak için faker gem'i kullanan bir komisyon görevi oluşturmak olacaktır. üzerinde eğildi ve iyi bir neden var Ben update_without_callbacks ve create_without_callbacks iyi çalışacağını tahmin, ama irade rayları bükmek için çalışırken, kendinize iyi bir neden var ve gerçekten iyi bir fikir olup olmadığını sormak.


Doğrulama olmadan, sadece geri aramalar olmadan kaydetmeye çalışmıyorum. Uygulamam dosya sistemine statik bir HTML yazmak için geri aramalar kullanıyor (bir çeşit CMS gibi). Geliştirme verilerini yüklerken bunu yapmak istemiyorum.
Ethan

Sadece bir düşünceliydi, sanırım geçmişte bu tür bir soru gördüğümde, kötü nedenlerden ötürü dolaşmaya çalışıyor.
nitecoder

0

Bir seçenek, aynı tabloyu kullanarak bu tür manipülasyonlar için ayrı bir modele sahip olmaktır:

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

(Aynı yaklaşım, onaylamaları atlamak için işleri kolaylaştırabilir)

Stephan


0

Başka bir yol, geri aramalar yerine doğrulama kancalarını kullanmak olabilir. Örneğin:

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

Bu şekilde do_something'i varsayılan olarak alabilirsiniz, ancak aşağıdakilerle kolayca geçersiz kılabilirsiniz:

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

3
Bu kötü bir fikir gibi görünüyor - işleri amaçlanan amaç için kullanmalısınız. İstediğiniz son şey, doğrulamalarınızın yan etkilere sahip olmasıdır.
chug2k

0

Var olan ActiveRecordveya bulunmayan seçeneklere veya etkin kayıt yöntemlerine bağlı kalmadan tüm sürümleriyle çalışması gereken bir şey .

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: aynı tablo üzerinde "farklı bir aktüatör modeli" kullanın


0

Özel geri aramalar için a attr_accessorve anunless geri arama.

Modelinizi aşağıdaki gibi tanımlayın:

class Person << ActiveRecord::Base

  attr_accessor :skip_after_save_callbacks

  after_save :do_something, unless: :skip_after_save_callbacks

end

Ardından after_save, tanımladığınız geri aramalara basmadan kaydı kaydetmeniz gerekiyorsa , skip_after_save_callbackssanal özelliği olarak ayarlayın 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

En temiz yol değil, ancak geri arama kodunu Rails ortamını kontrol eden bir koşulda sarabilirsiniz.

if Rails.env == 'production'
  ...
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.