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




ruby on rails migrate (20)

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.

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.


Nenhum desses pontos para o plugin without_callbacks que faz exatamente o que você precisa ...

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 funciona com o Rails 2.x


Uma solução que deve funcionar em todas as versões do Rails sem o uso de uma gem ou plug-in é simplesmente emitir instruções de atualização diretamente. por exemplo

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

Isso pode (ou não) ser uma opção, dependendo da complexidade da sua atualização. Isso funciona bem para, por exemplo, atualizar sinalizadores em um registro de dentro de um retorno de chamada after_save (sem reativar o retorno 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.


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ê pode usar a gem sneaky-save: https://rubygems.org/gems/sneaky-save .

Observe que isso não pode ajudar a salvar associações sem validações. Ele lança o erro "created_at não pode ser nulo", pois insere diretamente a consulta sql, ao contrário de um modelo. Para implementar isso, precisamos atualizar todas as colunas geradas automaticamente do db.


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

https://gist.github.com/576546

apenas despeje este patch-macaco em config / initializers / skip_callbacks.rb

então

Project.skip_callbacks { @project.save }

ou semelhante.

todo o crédito ao autor


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.


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)

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)

trilhos 3:

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

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


# 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

Parece que uma maneira de lidar com isso no Rails 2.3 (desde que update_without_callbacks acabou, etc.) seria usar update_all, que é um dos métodos que ignora os retornos de chamada conforme a guides.rubyonrails.org/… .

Além disso, observe que se você estiver fazendo algo em seu after_ callback, que faz um cálculo baseado em muitas associações (ex: has_many assoc, onde você também aceita_nests_attributes_for), você precisará recarregar a associação, caso faça parte do save , um de seus membros foi excluído.


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!


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'
  ...

Algo que deve funcionar com todas as versões do ActiveRecord sem depender de opções ou métodos de registro de ativação que podem ou não existir.

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 um "modelo de registro de atividade diferente" sobre a mesma tabela


Se o objetivo é simplesmente inserir um registro sem callbacks ou validações, e você gostaria de fazê-lo sem recorrer a gemas adicionais, adicionando verificações condicionais, usando RAW SQL, ou usando o seu código existente de alguma forma, considere o uso de uma "sombra objeto "apontando para sua tabela de banco de dados existente. Igual a:

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

Isso funciona com todas as versões do Rails, é threadsafe e elimina completamente todas as validações e retornos de chamada sem modificações no código existente. Você pode simplesmente lançar essa declaração de classe logo antes da sua importação real, e você deve estar pronto para ir. Apenas lembre-se de usar sua nova classe para inserir o objeto, como:

ImportedPerson.new( person_attributes )




rails-activerecord