development

ActiveRecord 콜백을 실행하지 않으려면 어떻게해야합니까?

big-blog 2020. 6. 25. 07:33
반응형

ActiveRecord 콜백을 실행하지 않으려면 어떻게해야합니까?


after_save 콜백이있는 모델이 있습니다. 일반적으로 문제는 없지만 개발 데이터를 만들 때와 같은 일부 상황에서는 콜백을 실행하지 않고 모델을 저장하려고합니다. 그렇게하는 간단한 방법이 있습니까? 비슷한 것 ...

Person#save( :run_callbacks => false )

또는

Person#save_without_callbacks

Rails 문서를 보았는데 아무것도 찾지 못했습니다. 그러나 내 경험상 Rails 문서가 항상 전체 이야기를 말하는 것은 아닙니다.

최신 정보

다음 과 같은 모델에서 콜백을 제거하는 방법을 설명하는 블로그 게시물찾았 습니다 .

Foo.after_save.clear

해당 방법이 문서화 된 곳을 찾을 수 없지만 작동하는 것 같습니다.


이 솔루션은 Rails 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)

이것은 확실히 콘솔에서만 또는 임의의 테스트를 수행하는 동안 사용하고 싶은 것입니다. 도움이 되었기를 바랍니다!


사용 update_column(레일> = 3.1) 또는 update_columns(레일> = 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


업데이트 :

@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)

레일 3.0.5에서 잘 작동합니다.


레일 3 :

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

Person 모델에서 다음과 같은 것을 시도해 볼 수 있습니다.

after_save :something_cool, :unless => :skip_callbacks

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

편집 : after_save는 기호가 아니지만 적어도 1,000 번 시도했습니다.


콜백이나 유효성 검사없이 단순히 레코드를 삽입하는 것이 목표이며 추가 gem에 의존하지 않고 조건부 검사를 추가하거나 RAW SQL을 사용하거나 종료 코드를 사용하여 어떤 식 으로든 퍼팅하지 않고 수행하려는 경우 "그림자" 기존 DB 테이블을 가리키는 개체 " 이렇게 :

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

이것은 모든 Rails 버전에서 작동하며 스레드 안전하며 기존 코드를 수정하지 않고도 모든 유효성 검사 및 콜백을 완전히 제거합니다. 실제로 가져 오기 직전에 해당 클래스 선언을 던질 수 있으며 잘 진행해야합니다. 다음과 같이 새 클래스를 사용하여 객체를 삽입하십시오.

ImportedPerson.new( person_attributes )

당신은 사용할 수 있습니다 update_columns:

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

save를 호출하지 않고 객체의 지정된 속성을 업데이트하므로 유효성 검사 및 콜백을 건너 뜁니다.


모든 after_save 콜백을 방지하는 유일한 방법은 첫 번째 콜백이 false를 반환하도록하는 것입니다.

아마도 (unested)와 같은 것을 시도 할 수 있습니다.

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

Rails 2.3에서 이것을 처리하는 한 가지 방법처럼 보입니다 (update_without_callbacks가 없어진 이후 등). update_all을 사용하는 것 입니다. 이것은 레일스 가이드의 섹션 12에 따라 콜백을 건너 뛰는 메소드 중 하나입니다 .

또한 after_ 콜백에서 무언가를 수행하는 경우 많은 연관 (예 : has_many assoc, accepts_nested_attributes_for도 수행)을 기반으로 계산을 수행하는 경우 저장의 일부로 연결을 다시로드해야합니다. , 회원 중 하나가 삭제되었습니다.


https://gist.github.com/576546

이 원숭이 패치를 config / initializers / skip_callbacks.rb에 덤프하십시오.

그때

Project.skip_callbacks { @project.save }

등.

저자에게 모든 신용


gem 또는 플러그인을 사용하지 않고 모든 버전의 Rails에서 작동해야하는 솔루션은 단순히 update 문을 직접 발행하는 것입니다. 예 :

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

업데이트가 얼마나 복잡한 지에 따라 옵션이 될 수도 있고 아닐 수도 있습니다. 예를 들어 after_save 콜백 에서 (콜백을 다시 트리거하지 않고) 레코드의 플래그를 업데이트하는 데 효과적 입니다.


경우에 따라 대부분의 up-voted답변이 혼란스러워 보일 수 있습니다.

if다음과 같이 콜백을 건너 뛰려면 간단한 확인 만하면 됩니다.

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

# 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

이 중 어느 것도 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에서 작동합니다


Rails 3에서 update_without_callbacks를 구현하는 플러그인을 작성했습니다.

http://github.com/dball/skip_activerecord_callbacks

The right solution, I think, is to rewrite your models to avoid callbacks in the first place, but if that's impractical in the near term, this plugin may help.


If you are using Rails 2. You could use SQL query for updating your column without running callbacks and validations.

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

I think it should work in any rails versions.


When I need full control over the callback, I create another attribute that is used as a switch. Simple and effective:

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

For creating test data in Rails you use this hack:

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

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


You can use sneaky-save gem: https://rubygems.org/gems/sneaky-save.

Note this cannot help in saving associations along without validations. It throws error 'created_at cannot be null' as it directly inserts the sql query unlike a model. To implement this, we need to update all auto generated columns of db.


I needed a solution for Rails 4, so I came up with this:

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

in any model:

include SaveWithoutCallbacks

then you can:

record.save_without_callbacks

or

Model::WithoutCallbacks.create(attributes)

Why would you want to be able to do this in development? Surely this will mean you are building your application with invalid data and as such it will behave strangely and not as you expect in production.

If you want to populate your dev db with data a better approach would be to build a rake task that used the faker gem to build valid data and import it into the db creating as many or few records as you desire, but if you are heel bent on it and have a good reason I guess that update_without_callbacks and create_without_callbacks will work fine, but when you are trying to bend rails to your will, ask yourself you have a good reason and if what you are doing is really a good idea.


One option is to have a separate model for such manipulations, using the same table:

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

(Same approach might make things easier for bypassing validations)

Stephan


Another way would be to use validation hooks instead of callbacks. For example:

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

That way you can get the do_something by default, but you can easily override it with:

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

Something that should work with all versions of ActiveRecord without depending on options or activerecord methods that may or may not exist.

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: use a "different activerecord model" over the same table


Not the cleanest way, but you could wrap the callback code in a condition that checks the Rails environment.

if Rails.env == 'production'
  ...

참고URL : https://stackoverflow.com/questions/632742/how-can-i-avoid-running-activerecord-callbacks

반응형