Dúvida em Ruby on Rails.. bug ou lambança minha?

0 respostas
bzanchet

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

Funciona perfeitamente. Quando o pagamento não é preenchido, o erro é mostrado e tudo corre normalmente.

Mas deixando a coisa um pouco mais complexa:
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

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?

Criado 3 de setembro de 2006
Respostas 0
Participantes 1