Vários objects em um formulário Rails

Eu quero editar vários itens da minha foto modelo em um formulário. Não tenho certeza de como apresentar corretamente e POST isso com um formulário, bem como coletar os itens na ação de atualização no controlador.

É isso que eu quero:

     

Os parâmetros são apenas um exemplo, como afirmei acima: Não tenho certeza da melhor maneira de colocar esses valores neste formulário.

No controlador eu quero algo assim:

 @photos = Photo.find( params[photos] ) @photos.each do |photo| photo.update_attributes!(params[:photos][photo] ) end 

No Rails 4, apenas isso

 < %= form_tag photos_update_path do %> < % @photos.each do |photo| %> < %= fields_for "photos[]", photo do |pf| %> < %= pf.text_field :caption %> ... other photo fields 

ATUALIZAÇÃO : Esta resposta se aplica ao Rails 2, ou se você tiver restrições especiais que exigem lógica customizada. Os casos fáceis são bem tratados usando fields_for como discutido em outro lugar.

Rails não vai te ajudar muito a fazer isso. Isso vai contra as convenções de visualização padrão, portanto, você terá que fazer soluções alternativas na visualização, no controlador e até nas rotas. Isso não é divertido.

Os principais resources para lidar com os modelos multi-modelados da maneira Rails são a série params-foo de Stephen Chu , ou se você estiver no Rails 2.3, confira Nested Object Forms

Torna-se muito mais fácil se você definir algum tipo de recurso singular que esteja editando, como um Photoset. Um Photoset pode ser um modelo de modelo real do ActiveRecord ou pode ser apenas uma fachada que aceita dados e gera erros como se fosse um modelo ActiveRecord.

Agora você pode escrever um formulário de visualização da seguinte forma:

 < %= form_for :photoset do |f|%> < % f.object.photos.each do |photo| %> < %= f.fields_for photo do |photo_form| %> < %= photo_form.text_field :caption %> < %= photo_form.label :caption %> < %= photo_form.file_field :attached %> < % end %> < % end %> < % end %> 

Seu modelo deve validar cada criança Foto que vem e agrega seus erros. Você pode querer verificar um bom artigo sobre como include validações em qualquer class . Pode parecer algo assim:

 class Photoset include ActiveRecord::Validations attr_accessor :photos validate :all_photos_okay def all_photos_okay photos.each do |photo| errors.add photo.errors unless photo.valid? end end def save photos.all?(&:save) end def photos=(incoming_data) incoming_data.each do |incoming| if incoming.respond_to? :attributes @photos < < incoming unless @photos.include? incoming else if incoming[:id] target = @photos.select { |t| t.id == incoming[:id] } end if target target.attributes = incoming else @photos << Photo.new incoming end end end end def photos # your photo-find logic here @photos || Photo.find :all end end 

Ao usar um modelo de fachada para o Photoset, você pode manter seu controlador e a lógica de visualização simples e direta, reservando o código mais complexo para um modelo dedicado. Este código provavelmente não ficará fora da checkbox, mas esperamos que ele lhe dê algumas idéias e aponte-o na direção certa para resolver sua questão.

Rails tem uma maneira de fazer isso – eu não sei quando foi introduzido, mas é basicamente descrito aqui: http://guides.rubyonrails.org/form_helpers.html#using-form-helpers

Demorou um pouco para alterar a configuração corretamente para o caso em que não há nenhum object pai, mas isso parece estar correto (é basicamente o mesmo que a resposta do gamov, mas mais limpo e não permite “novos” registros misturados com os registros de “atualização”):

 < %= form_tag photos_update_path do %> < % @photos.each do |photo| %> < %= fields_for "photos[#{photo.id}]", photo do |pf| %> < %= pf.text_field :caption %> ... [other fields] < % end %> < % end %> < % end %> 

Em seu controlador, você terminará com um hash em params[:photos] , em que as chaves são IDs de fotos e os valores são hashes de atributos.

Você pode usar a syntax “nome do modelo []” para representar vários objects.

Em vista, use “photo []” como nome do modelo.

 < % form_for "photo[]", :url => photos_update_path do |f| %> < % for @photo in @photos %> < %= render :partial => "photo_form", :locals => {f => f} %> < %= submit_tag "Save"%> < % end %> < % end %> 

Isso preencherá campos de input como você descreveu.

No seu controlador, você pode fazer atualizações em massa.

 def update Photo.update(params[:photo].keys, params[:photo].values) ... end 

De fato, como Turadg mencionou, o Rack (Rails 3.0.5) falha se você misturar novos e existentes registros na resposta de Glen. Você pode contornar isso fazendo com que fields_for trabalhe manualmente:

 < %= form_tag photos_update_path do %> < % @photos.each_with_index do |photo,i| %> < %= fields_for 'photos[#{i}]', photo do |pf| %> < %= pf.hidden_field :id %> ... [other photo fields] < % end %> < % end %> 

Isso é muito feio se você me perguntar, mas é a única maneira que encontrei para editar vários registros enquanto misturava registros novos e existentes. O truque aqui é que, em vez de ter uma matriz de registros, o hash params recebe uma matriz de hashes (numerados com i, 0,1,2, etc) E o id no hash de registro. O Rails atualizará os registros existentes de acordo e criará os novos registros.

Mais uma observação: Você ainda precisa processar os registros novos e existentes no controlador separadamente (verifique se: id.present?)