ruby on rails - update - Como posso evitar a execução de retornos de chamada do ActiveRecord?




ruby on rails migrate (17)

Eu tenho alguns modelos que têm callbacks after_save. Normalmente, tudo bem, mas em algumas situações, como ao criar dados de desenvolvimento, eu quero salvar os modelos sem que os retornos de chamada sejam executados. Existe uma maneira simples de fazer isso? Algo parecido com ...

Person#save( :run_callbacks => false )

ou

Person#save_without_callbacks

Eu olhei nos documentos do Rails e não encontrei nada. No entanto, na minha experiência, os documentos do Rails nem sempre contam toda a história.

ATUALIZAR

Eu encontrei uma postagem no blog que explica como você pode remover retornos de chamada de um modelo como este:

Foo.after_save.clear

Não consegui encontrar onde esse método está documentado, mas parece funcionar.


A única maneira de evitar todos os retornos de chamadas after_save é fazer com que o primeiro retorne false.

Talvez você possa tentar algo como (não testado):

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

A resposta mais up-voted pode parecer confusa em alguns casos.

Você pode usar apenas um simples if verificar se você gostaria de pular um retorno de chamada, como este:

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

Atualizada:

A solução de @Vikrant Chaudhary parece melhor:

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

Minha resposta original:

Veja este link: Como pular os retornos de chamada do ActiveRecord?

no Rails3,

Vamos supor que temos uma definição de classe:

class User < ActiveRecord::Base
  after_save :generate_nick_name
end 

Abordagem 1:

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

Approach2: Quando você quiser pular em seus arquivos rspec ou qualquer outra coisa, tente isto:

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

NOTA: uma vez feito isso, se você não estiver no ambiente rspec, você deve redefinir os retornos de chamada:

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

funciona bem para mim nos trilhos 3.0.5


Esta solução é somente o Rails 2.

Eu apenas investiguei isso e acho que tenho uma solução. Existem dois métodos privados do ActiveRecord que você pode usar:

update_without_callbacks
create_without_callbacks

Você terá que usar o send para chamar esses métodos. exemplos:

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

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

Isso é definitivamente algo que você só quer usar no console ou ao fazer alguns testes aleatórios. Espero que isto ajude!


Eu precisava de uma solução para o Rails 4, então descobri isso:

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

em qualquer modelo:

include SaveWithoutCallbacks

então você pode:

record.save_without_callbacks

ou

Model::WithoutCallbacks.create(attributes)

Não é a maneira mais limpa, mas você pode incluir o código de retorno de chamada em uma condição que verifica o ambiente Rails.

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

Outra maneira seria usar ganchos de validação em vez de retornos de chamada. Por exemplo:

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

Dessa forma, você pode obter o do_something por padrão, mas pode facilmente substituí-lo por:

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

Para criar dados de teste no Rails você usa este hack:

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

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


Por que você quer ser capaz de fazer isso no desenvolvimento? Certamente, isso significará que você está criando seu aplicativo com dados inválidos e, como tal, se comportará de maneira estranha e não como esperado na produção.

Se você quiser preencher seu banco de dados com dados, uma abordagem melhor seria construir uma tarefa rake que usasse a gem faker para construir dados válidos e importá-los para o banco de dados, criando tantos ou poucos registros quantos desejasse, mas se você estivesse no salto. Eu acredito que update_without_callbacks e create_without_callbacks funcionará bem, mas quando você estiver tentando dobrar trilhos à sua vontade, pergunte a si mesmo se você tem uma boa razão e se o que você está fazendo é realmente uma boa idéia.


Quando preciso de controle total sobre o retorno de chamada, crio outro atributo que é usado como um comutador. Simples e eficaz:

Modelo:

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

Teste:

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

Se você estiver usando o Rails 2. Você poderia usar a consulta SQL para atualizar sua coluna sem executar retornos de chamada e validações.

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

Eu acho que deveria funcionar em qualquer versão de trilhos.


Uma opção é ter um modelo separado para tais manipulações, usando a mesma tabela:

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

(A mesma abordagem pode facilitar as coisas para contornar as validações)

Stephan


Use update_column (Rails> = v3.1) ou update_columns (Rails> = 4.0) para pular callbacks e validações. Além disso, com esses métodos, o updated_at não é atualizado.

#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: ignorando retornos de chamada que também funcionam ao criar um objeto

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

Você pode usar update_columns :

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

Atualiza os atributos fornecidos de um objeto, sem chamar salvar, portanto, ignorando validações e retornos de chamada.


Você poderia tentar algo assim em seu modelo de pessoa:

after_save :something_cool, :unless => :skip_callbacks

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

EDIT: after_save não é um símbolo, mas é pelo menos a milésima vez que eu tentei torná-lo um.


trilhos 3:

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

# 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




rails-activerecord