Rails STI usando um formulário
-
24-09-2019 - |
Pergunta
Eu tenho um formulário que me permite adicionar arquivos de formatos diferentes a um fluxo. Portanto, um fluxo é composto de muitos arquivos, esses arquivos são arquivos XML, mas basicamente têm esquemas diferentes. Eu tenho um formulário que permite ao usuário adicionar o arquivo que desejar, estou usando o STI (que funciona muito bem quando os dados já estão na tabela), meu problema é adicionar os dados à tabela.
O formulário possui 1 campo de entrada, apenas um FILE_FIELD que permite ao usuário selecionar o arquivo que deseja fazer o upload. Como tenho apenas um formulário, não sou capaz de instanciar o objeto correto, tenho que fazê -lo programaticamente .. e não tenho certeza de como fazê -lo.
Eu apenas (ou posso) adicionar uma suspensão com os tipos possíveis e chamar esse tipo de 'tipo' para que, quando o formulário seja enviado, os trilhos instanciem o tipo de gravação de objeto porque o atributo de tipo é fornecido?
Qual é a melhor prática para isso. Estou executando o Rails 2.3.4.
Solução
Eu encontrei uma solução em http://coderrr.wordpress.com/2008/04/22/building-the-tlight-class-with-sti-in-lails/#comment-1826
class GenericClass < ActiveRecord::Base
class << self
def new_with_cast(*a, &b)
if (h = a.first).is_a? Hash and (type = h[:type] || h['type']) and (klass = type.constantize) != self
raise "wtF hax!!" unless klass < self # klass should be a descendant of us
return klass.new(*a, &b)
end
new_without_cast(*a, &b)
end
alias_method_chain :new, :cast
end
end
O que funcionou muito bem para mim com código mínimo - não sei se é hackish ou não, mas funciona e é bastante limpo. Gostei do fato de que suas apenas 10 linhas de código.
Outras dicas
Não sei quantos tipos você tem, mas simplesmente usei controladores e visualizações separados para os diferentes tipos no passado. Dessa forma, você não cria um novo objeto da classe base e tenta definir o tipo, basta usar o modelo que herda da classe base. Cada página nova/editar para seus recursos pode renderizar um parcial compartilhado no bloco form_for. O parcial conteria seu arquivo_field.
Dessa forma, quando o formulário for enviado, ele será para o controlador correto, chamando o recurso correto.New e tudo está ok.
A desvantagem, é claro, são mais arquivos e, qualquer que seja a página que você esteja vinculando a "Adicionar novo arquivo", você precisa adicionar vários links como "Adicionar novo esse tipo de arquivo", "Adicionar novo esse tipo de arquivo" etc.
Quanto a definir o tipo no formulário, não tenho certeza se isso funciona, duvido, mas tente (informe -nos). Você pode fazer com que esse tipo suspense um select_tag e, quando alterado, use JavaScript para alterar o local da ação no formulário.
Editado e adicionado de trabalho básico em torno
Não que eu goste dessa solução e duvido que não seja o melhor, mas se você realmente não quer controladores separados e precisa fazer isso funcionar, poderá fazer algo assim:
class XmlFile < ActiveRecord::Base
end
class XmlFileTypeA < XmlFile
end
class XmlFileTypeB < XmlFile
end
def create
# Leaving this case statement in the controller for simplicity but you probably want to move this to the model
case params[:chosen_xml_type]
when "file_type_a"
@item = XmlFileTypeA.new(params)
when "file_type_b"
@item = XmlFileTypeB.new(params)
else
raise "Unknown file type!"
etc
end
end