Jak uniknąć wywołania zwrotnego ActiveRecord?

Mam kilka modeli, które mają wywołania after_save. Zazwyczaj jest to w porządku, ale w niektórych sytuacjach, na przykład podczas tworzenia danych programistycznych, chcę zapisać modele bez uruchamiania wywołań zwrotnych. Czy jest na to prosty sposób? Coś podobnego...

Person#save( :run_callbacks => false )

Lub

Person#save_without_callbacks

Zajrzałem do rails docs i nic nie znalazłem. Jednak z mojego doświadczenia wynika, że rails docs nie zawsze opowiadają całą historię.

UPDATE

Znalazłem wpis na blogu który wyjaśnia jak można usunąć wywołania zwrotne z takiego modelu:

Foo.after_save.clear

Nie mogłem znaleźć, gdzie ta metoda jest udokumentowana, ale wydaje się, że działa.

Author: user2262149, 2009-03-11

25 answers

To rozwiązanie jest tylko Rails 2.

Właśnie to zbadałem i myślę, że mam rozwiązanie. Istnieją dwie prywatne metody ActiveRecord, których możesz użyć:
update_without_callbacks
create_without_callbacks

Będziesz musiał użyć send, aby wywołać te metody. przykłady:

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

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

Jest to zdecydowanie coś, co naprawdę chcesz używać tylko w konsoli lub podczas wykonywania losowych testów. Mam nadzieję, że to pomoże!

 69
Author: efalcao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-03 04:01:55

Użyj update_column (Rails >= v3.1) lub update_columns (Rails > = 4.0), aby pominąć wywołania zwrotne i walidacje. Również z tych metod, updated_at jest nie zaktualizowane.

#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: pomijanie wywołań zwrotnych, które działają również podczas tworzenia obiektu

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
 213
Author: Vikrant Chaudhary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-20 04:22:56

Aktualizacja:

@ Vikrant Chaudhary rozwiązanie wydaje się lepsze:

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

Moja oryginalna ODPOWIEDŹ:

Zobacz ten link: Jak pominąć wywołania zwrotne ActiveRecord?

W Kolejach3,

Załóżmy, że mamy definicję klasy:

class User < ActiveRecord::Base
  after_save :generate_nick_name
end 

Approach1:

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

Approach2: Jeśli chcesz pominąć je w plikach rspec lub cokolwiek innego, spróbuj tego:

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

Uwaga: gdy to zrobisz, jeśli nie jesteś w środowisku rspec, powinieneś zresetować wywołania:

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

Działa mi dobrze on rails 3.0.5

 26
Author: Siwei Shen申思维,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:15

Rails 3:

MyModel.send("_#{symbol}_callbacks") # list  
MyModel.reset_callbacks symbol # reset
 19
Author: guai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-26 08:03:40

Możesz spróbować czegoś takiego w swoim modelu osoby:

after_save :something_cool, :unless => :skip_callbacks

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

EDIT: after_save nie jest symbolem, ale to przynajmniej 1000-ty raz próbowałem go zrobić.

 15
Author: Sarah Mei,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-27 14:32:24

Jeśli celem jest po prostu wstawienie rekordu bez wywołania zwrotnego lub walidacji, a chciałbyś to zrobić bez uciekania się do dodatkowych klejnotów, dodawania warunkowych sprawdzeń, używania surowego SQL lub futzing z kodem wychodzącym w jakikolwiek sposób, rozważ użycie "obiektu cienia" wskazującego na istniejącą tabelę db. TAK:

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

To działa z każdą wersją Rails, jest threadsafe i całkowicie eliminuje wszystkie walidacje i wywołania zwrotne bez modyfikacji istniejącego kodu. Możesz po prostu wrzuć deklarację klasy tuż przed importem i powinieneś być gotowy. Pamiętaj tylko, aby użyć nowej klasy do wstawienia obiektu, na przykład:

ImportedPerson.new( person_attributes )
 12
Author: Brad Werth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-09 22:52:14

Możesz użyć update_columns:

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

Aktualizuje podane atrybuty obiektu, bez wywoływania save, co powoduje pominięcie walidacji i wywołań zwrotnych.

 8
Author: Luís Ramalho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-01 18:51:05

Jedynym sposobem na uniknięcie wszystkich wywołań zwrotnych after_save jest zwrócenie pierwszego false.

Być może mógłbyś spróbować czegoś w stylu (untested):

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
 5
Author: rfunduk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-21 13:41:53

Wygląda na to, że można to zrobić w Rails 2.3 (ponieważ update_without_callbacks zniknął, itd.), byłoby użycie update_all, która jest jedną z metod, które pomijają wywołania zwrotne zgodnie z Sekcja 12 Przewodnika po walidacjach i wywołaniach zwrotnych.

Zauważ również, że jeśli robisz coś w swoim after_ callback, który wykonuje obliczenia oparte na wielu asocjacjach (np. has_many assoc, gdzie również wykonujesz accepts_nested_attributes_for), będziesz musiał ponownie załadować plik stowarzyszenie, w przypadku gdy jest częścią save, jeden z jego członków został usunięty.

 5
Author: chrisrbailey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-24 07:39:29

Https://gist.github.com/576546

Po prostu wrzuć tę łatkę do config/initializers / skip_callbacks.rb

Then

Project.skip_callbacks { @project.save }

Lub tym podobne.

Wszystkie podziękowania dla autora

 4
Author: fringd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-24 23:26:42

Najbardziej up-voted ODPOWIEDŹ może wydawać się myląca w niektórych przypadkach.

Możesz użyć tylko prostego if sprawdź, czy chcesz pominąć wywołanie zwrotne, jak to:

after_save :set_title, if: -> { !new_record? && self.name_changed? }
 3
Author: Aleks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-23 09:57:23

Rozwiązaniem, które powinno działać we wszystkich wersjach Rails bez użycia gem lub wtyczki, jest po prostu bezpośrednie wydanie instrukcji aktualizacji. eg

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

Może to być (lub nie) opcja w zależności od złożoności aktualizacji. Działa to dobrze np. przy aktualizacji FLAG na płycie z wewnątrz wywołanie zwrotne after_save (bez ponownego uruchamiania wywołania zwrotnego).

 2
Author: Dave Smylie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-08 18:02:40
# 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
Author: Sasha Alexandrov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-17 07:21:17

Żaden z tych punktów nie wskazuje na wtyczkę without_callbacks, która po prostu robi to, czego potrzebujesz ...

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 działa z Rails 2.x

 1
Author: kares,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-01 19:27:15

Napisałem wtyczkę, która implementuje update_without_callbacks w Rails 3:

Http://github.com/dball/skip_activerecord_callbacks

Właściwym rozwiązaniem, myślę, jest przepisanie modeli, aby uniknąć wywołań zwrotnych w pierwszej kolejności, ale jeśli jest to niepraktyczne w najbliższej przyszłości, ta wtyczka może pomóc.

 1
Author: Donald ball,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-13 14:59:38

Jeśli używasz Rails 2. Możesz użyć zapytania SQL do aktualizacji kolumny bez uruchamiania wywołań zwrotnych i walidacji.

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

Myślę, że powinno działać w każdej wersji rails.

 1
Author: oivoodoo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-31 11:31:13

Kiedy potrzebuję pełnej kontroli nad wywołaniem zwrotnym, tworzę inny atrybut, który jest używany jako przełącznik. Proste i skuteczne:

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

Test:

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
Author: tothemario,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-14 08:39:12

Do tworzenia danych testowych w Rails używasz tego hacka:

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

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

 1
Author: Wojtek Kruszewski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-29 16:15:54

Możesz użyć klejnotu sneaky-save: https://rubygems.org/gems/sneaky-save .

Uwaga to nie może pomóc w zapisywaniu skojarzeń bez walidacji. Wyświetla błąd "created_at nie może być null", ponieważ bezpośrednio wstawia zapytanie sql w przeciwieństwie do modelu. Aby to zaimplementować, musimy zaktualizować wszystkie automatycznie wygenerowane kolumny db.

 1
Author: Zinin Serge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-14 13:16:17

Potrzebowałem rozwiązania dla Rails 4, więc wpadłem na to:

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

W dowolnym modelu:

include SaveWithoutCallbacks

Wtedy możesz:

record.save_without_callbacks

Lub

Model::WithoutCallbacks.create(attributes)
 1
Author: Steve Friedman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-12 15:05:02

Dlaczego chcesz być w stanie to zrobić w rozwoju? Z pewnością będzie to oznaczać, że budujesz swoją aplikację z nieprawidłowymi danymi i jako takie będzie zachowywać się dziwnie, a nie tak, jak oczekujesz w produkcji.

Jeśli chcesz zapełnić swój dev db danymi, lepszym rozwiązaniem byłoby zbudowanie zadania rake, które wykorzystało klejnot fakera do zbudowania poprawnych danych i zaimportowania ich do db, tworząc tyle lub kilka rekordów, ile chcesz, ale jeśli jesteś na nim wygięty i masz dobry powód, myślę, że to update_without_callbacks i create_without_callbacks będą działać dobrze, ale kiedy próbujesz zginać szyny do swojej woli, zadaj sobie pytanie, Czy masz dobry powód i czy to, co robisz, jest naprawdę dobrym pomysłem.

 0
Author: nitecoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-11 09:18:13

Jedną z opcji jest posiadanie oddzielnego modelu dla takich manipulacji, używając tej samej tabeli:

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

(to samo podejście może ułatwić omijanie walidacji)

Stephan

 0
Author: Stephan Wehner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-06 19:00:06

Innym sposobem byłoby użycie haków walidacji zamiast wywołań zwrotnych. Na przykład:

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

W ten sposób można domyślnie uzyskać do_something, ale można go łatwo zastąpić za pomocą:

@person = Person.new
@person.save(false)
 0
Author: BoosterStage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-05 14:50:24

Coś, co powinno działać ze wszystkimi wersjami ActiveRecord bez zależności od opcji lub metod activerecord, które mogą lub nie mogą istnieć.

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: użyj "innego modelu activerecord" w tej samej tabeli

 0
Author: choonkeat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-07 05:17:11

Nie jest to najczystszy sposób, ale możesz zawinąć kod wywołania zwrotnego w taki sposób, aby sprawdzał środowisko Rails.

if Rails.env == 'production'
  ...
 -3
Author: james,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-10 23:59:26