Desculpem, eu sei que não é o fórum mais apropriado, mas como existem aqui vários usuários do Rails, inclusive profissionais, e como estamos na seção “Off-topic”, além de não haver nada nas regras proibindo… resolvi tentar (e desde ontem que tento e não consigo me cadastrar no RubyOnBr… o captcha não aparece).
Observem o seguinte trecho de código.
[code]class Pedido < ActiveRecord::Base
belongs_to :pagamento
belongs_to :usuario
has_many :locacoes_midia, :dependent => :destroy
has_many :locacoes_ppv, :dependent => :destroy
validates_presence_of :data, :preco, :endereco, :pagamento, :usuario, :message => “deve ser preenchido”
validates_associated :usuario, :pagamento, :message => “deve ser válido”
end
class PedidosController < ApplicationController
def create
redirect_to :action => ‘show’ if session[:pedido].nil?
@pedido = session[:pedido]
@pagamento = Pagamento.new(params[:pagamento]) do |p|
p.data = DateTime.now
p.valor = @pedido.preco
end
@pedido.data = DateTime.now
@pedido.pagamento = @pagamento
if @pedido.save
flash[:notice] = 'Pedido confirmado com sucesso!'
session[:pedido] = nil
redirect_to :action => 'confirmacao', :id => @pedido
else
render :action => 'fechar'
end
[…]
end[/code]
Funciona perfeitamente. Quando o pagamento não é preenchido, o erro é mostrado e tudo corre normalmente.
Mas deixando a coisa um pouco mais complexa:
[code]class Pedido < ActiveRecord::Base
belongs_to :pagamento
belongs_to :usuario
has_many :locacoes_midia, :dependent => :destroy
has_many :locacoes_ppv, :dependent => :destroy
validates_presence_of :data, :preco, :endereco, :pagamento, :usuario, :message => “deve ser preenchido”
validates_associated :usuario, :pagamento, :message => “deve ser válido”
def save
copias = []
self.locacoes_midia.each { |locacao| copias.push(locacao.copia) }
Copia.transaction(copias) do
Pedido.transaction(self) do
if !super
raise "Houve um erro ao confirmar o pedido"
end
for copia in copias
raise "Cópia indisponível" if copia.disponivel == false
copia.disponivel = false
copia.save
end
end
end
end
end
class PedidosController < ApplicationController
def create
redirect_to :action => ‘show’ if session[:pedido].nil?
@pedido = session[:pedido]
@pagamento = Pagamento.new(params[:pagamento]) do |p|
p.data = DateTime.now
p.valor = @pedido.preco
end
@pedido.data = DateTime.now
@pedido.pagamento = @pagamento
begin
@pedido.save
flash[:notice] = 'Pedido confirmado com sucesso!'
session[:pedido] = nil
redirect_to :action => 'confirmacao', :id => @pedido
rescue RuntimeError => error
render :action => 'fechar'
end
end
[…]
end[/code]
Ocorre um comportamento muito estranho: quando o pagamento é deixado em branco numa primeira tentativa, o erro pego e é mostrado corretamente. Porém, numa tentativa seguinte de fazer o salvamento do pedido, acontece um erro numa das foreign keys: o rails tenta inserir um dos objetos relacionados a “pedido” usando 0 na chave estrangeira, ao invés do id do pedido recém salvo. Não faz sentido.
(fora de tags de codigo pra não estourar horizontalmente)
ActiveRecord::StatementInvalid (Mysql::Error: #23000Cannot add or update a child row: a foreign key constraint fails (locadora_development/locacoes_midia
, CONSTRAINT copia_pedido
FOREIGN KEY (pedido_id
) REFERENCES pedidos
(id
)): INSERT INTO locacoes_midia (devolucao_id
, data_devolucao
, multa
, copia_id
, preco
, pedido_id
) VALUES(NULL, ‘2006-09-04T21:31:21-0300’, 0.25, 50, 1.5, 0)):
Observem: esse erro ocorre apenas quando na primeira tentativa o pagamento é deixado em branco. Se for preenchido corretamente na primeira tentativa, o pedido é salvo corretamente.
Enfim… eu acho estranho colocar apenas o @pedido.save dentro da transação… será que tem a ver com isso?
Em segundo lugar: colocar apenas o laço que marca as cópias como indisponíveis dentro da transação e fazer @pedido.destroy caso a transação falhe… é uma solução… mas é uma gambiarra, não?