Come fai a evitare modifiche al database all'interno di un filtro before_create Rails ActiveRecord dall'ottenere il rollback quando ritorna falso?

StackOverflow https://stackoverflow.com/questions/1597434

Domanda

Ho aggiunto un filtro before_create ad uno dei miei modelli Rails ActiveRecord e dentro quel filtro che sto facendo alcuni aggiornamenti del database.

A volte mi return false dal filtro per impedire la creazione del modello di destinazione, ma che sta causando tutti i altri modifiche al database che ho fatto (mentre all'interno del filtro) per ottenere il rollback.

Come posso evitare questo?

Aggiornamento # 1 : Ecco alcuni pseudo codice che spiega il mio problema:

class Widget < ActiveRecord::Base
  before_create :update_instead

  def update_instead
    if some_condition?
      update_some_record_in_same_model # this is getting rolled back
      return false # don't create a new record
    else
      return true # let it continue
    end
  end
end

Aggiornamento # 2 : Alcune buone risposte qui sotto, ma ognuno aveva i suoi difetti. Ho finito per overridding il metodo create in questo modo:

def create
  super unless update_instead? # yes I reversed the return values from above
end
È stato utile?

Soluzione

Ho appena avuto a fare questo di recente. È necessario richiedere specificamente un altro collegamento da AR. Quindi eseguire le modifiche a questa connessione. In questo modo, se la creazione fallisce e ripristina la transazione, i cambiamenti del vostro callback sono stati già impegnati in una transazione diversa.

Ignora la mia risposta sopra. Il codice di esempio hai appena dato davvero chiarito le cose.

class Foo < ActiveRecord::Base
  before_create :update_instead

  def update_instead
    dbconn = self.class.connection_pool.checkout
    dbconn.transaction do
      dbconn.execute("update foos set name = 'updated'")
    end
    self.class.connection_pool.checkin(dbconn)
    false
  end
end


>> Foo.create(:name => 'sam')
=> #<Foo id: nil, name: "sam", created_at: nil, updated_at: nil>
>> Foo.all
=> [#<Foo id: 2, name: "updated", created_at: "2009-10-21 15:12:55", updated_at: "2009-10-21 15:12:55">]

Altri suggerimenti

Con una transazione nel filtro.

Hai provato di sovrascrivere creare / salvare e le loro versioni distruttivi? ActiveRecord :: Base.create, ActiveRecord :: Base.save e le loro versioni distruttivi sono avvolti in una transazione, sono anche quello che grilletto callback e validazioni. Se stai ignorando che, solo la roba fatta da super sarà parte di una transazione. Se avete bisogno di anni convalide eseguire prima allora si può chiamare in modo esplicito valida per eseguirli tutti.

Esempio:

before_create :before_create_actions_that_can_be_rolled_back

def create
  if valid? && before_create_actions_that_wont_be_rolled_back
    super
  end
end

def before_create_actions_that_wont_be_rolled_back
 # exactly what it sounds like
end

def before_create_actions_that_can_be_rolled_back
 # exactly what it sounds like
end

Avvertimento: Con queste modifiche i metodi saranno chiamati in questo ordine:

  1. prima della convalida (on_create)
  2. validate
  3. dopo la convalida (on_create)
  4. before_create_actions_that_wont_be_rolled_back
  5. prima della convalida (on_create)
  6. validate
  7. dopo la convalida (on_create)
  8. prima di salvare i callback
  9. prima di creare i callback
  10. si crea record
  11. dopo creare i callback
  12. dopo salvare callback

Se qualsiasi convalide falliscono o se uno di callback restituisce false nei passaggi 5-12 il database verrà ripristinati alla stato in cui era prima del punto 5.

Se valida? fallisce, o before_create_actions_that_wont_be_rolled_back fallisce rispetto verrà interrotta l'intera catena.

Potrebbe tali modifiche essere fatto in un after_create invece?

Questa utilizza lo stesso concetto come la risposta di Rich Cavanaugh. Ho aggiunto un modello di genitore in modo che sia più chiaro ciò che il filtro sta facendo. La chiave è quella di utilizzare un filo + un'automatica partenza / check-in di una connessione separata. Nota: è necessario assicurarsi che il proprio: il valore piscina si trova ad almeno 2 nella vostra specifica connessione, a seconda di quanti thread simultanei si incorrerà. Penso che il valore predefinito è 5.

class Gadget < ActiveRecord::Base
  has_many :widgets
end

class Widget < ActiveRecord::Base
  belongs_to :gadget
  before_create :update_instead

  def update_some_record_in_same_model
    # the thread forces a new connection to be checked out
    Thread.new do
      ActiveRecord::Base.connection_pool.with_connection do |conn|
        # try this part without the 2 surrounding blocks and it will be rolled back
        gadget.touch_count += 1
        gadget.save!
      end
    end.join
  end
  def some_condition?
    true
  end

  def update_instead
    if some_condition?
      update_some_record_in_same_model # this is getting rolled back
      p [:touch_count_in_filter, gadget.reload.touch_count]
      return false # don't create a new record
    else
      return true # let it continue
    end
  end
end

Prova:

  g = Gadget.create(:name => 'g1')
  puts "before:"
  p [:touch_count, g.reload.touch_count]
  p [:widget_count, Widget.count]

  g.widgets.create(:name => 'w1')
  puts "after:"
  # Success means the count stays incremented
  p [:touch_count, g.reload.touch_count]
  p [:widget_count, Widget.count]

Per ulteriori approfondimenti: http://bibwild.wordpress.com/2011/11/14/multi-threading-in-rails-activerecord-3-0-3-1/

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top