web-dev-qa-db-fra.com

Plusieurs objets sous une forme Rails

Je souhaite modifier plusieurs éléments de ma photo modèle dans un seul formulaire. Je ne sais pas comment présenter correctement et POST ceci avec un formulaire, ainsi que comment rassembler les éléments dans l'action de mise à jour dans le contrôleur.

C'est ce que je veux:

<form>
<input name="photos[1][title]" value="Photo with id 1" />
<input name="photos[2][title]" value="Photo with id 2" />
<input name="photos[3][title]" value="Custom title" />
</form>

Les paramètres ne sont qu'un exemple, comme je l'ai dit ci-dessus: je ne suis pas sûr de la meilleure façon de POST ces valeurs dans ce formulaire.

Dans le contrôleur, je veux quelque chose comme ça:

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

Dans Rails 4, juste ceci

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

[~ # ~] mise à jour [~ # ~] : cette réponse s'applique à Rails 2, ou si vous avez contraintes spéciales qui nécessitent une logique personnalisée. Les cas simples sont bien traités en utilisant champs_for comme discuté ailleurs.

Rails ne vous aidera pas beaucoup à le faire. Cela va à l'encontre des conventions de vue standard, vous devrez donc contourner la vue, le contrôleur et même les itinéraires. Ce n'est pas amusant.

Les ressources clés sur le traitement des modèles multiples sont la Rails est la série params-foo de Stephen Ch , ou si vous êtes sur Rails 2.3, consultez Formes d'objets imbriqués

Cela devient beaucoup plus facile si vous définissez une sorte de ressource singulière que vous modifiez, comme un Photoset. Un ensemble de photos peut être un véritable modèle de type ActiveRecord ou il peut simplement s'agir d'une façade qui accepte des données et génère des erreurs comme s'il s'agissait d'un modèle ActiveRecord.

Vous pouvez maintenant écrire un formulaire de vue un peu comme ceci:

<%= 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 %>

Votre modèle doit valider chaque photo enfant qui arrive et regrouper ses erreurs. Vous voudrez peut-être vérifier n bon article sur la façon d'inclure des validations dans n'importe quelle classe . Cela pourrait ressembler à ceci:

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

En utilisant un modèle de façade pour le Photoset, vous pouvez garder votre contrôleur et afficher la logique simple et directe, en réservant le code le plus complexe pour un modèle dédié. Ce code ne sortira probablement pas de la boîte, mais j'espère qu'il vous donnera quelques idées et vous indiquera la bonne direction pour résoudre votre question.

48
austinfromboston

Rails a un moyen de le faire - je ne sais pas quand il a été introduit, mais il est essentiellement décrit ici: http://guides.rubyonrails.org/form_helpers.html#using-form-helpers =

Il a fallu un peu de temps pour modifier correctement la configuration dans le cas où il n'y a pas d'objet parent, mais cela semble être correct (c'est essentiellement la même chose que la réponse de gamov, mais plus propre et ne permet pas de "nouveaux" enregistrements mélangés avec les enregistrements "mise à jour"):

<%= 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 %>

Dans votre contrôleur, vous vous retrouverez avec un hachage dans params[:photos], où les clés sont des identifiants photo et les valeurs sont des hachages d'attribut.

18
mltsy

Vous pouvez utiliser la syntaxe "nom du modèle []" pour représenter plusieurs objets.

En vue, utilisez "photo []" comme nom de modèle.

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

Cela remplira les champs de saisie comme vous l'avez décrit.

Dans votre contrôleur, vous pouvez effectuer des mises à jour groupées.

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

En effet, comme Turadg l'a mentionné, Rack (Rails 3.0.5) échoue si vous mélangez de nouveaux enregistrements existants dans la réponse de Glen. Vous pouvez contourner ce problème en faisant en sorte que fields_for fonctionne manuellement:

<%= 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 %>

C'est assez moche si vous me demandez, mais c'est le seul moyen que j'ai trouvé pour éditer plusieurs enregistrements tout en mélangeant des enregistrements nouveaux et existants. L'astuce ici est qu'au lieu d'avoir un tableau d'enregistrements, le hachage params obtient un tableau de hachages (numéroté avec i, 0,1,2, etc.) ET l'identifiant dans le hachage d'enregistrement. Rails mettra à jour les enregistrements existants en conséquence et en créera de nouveaux.

Encore une remarque: vous devez toujours traiter les enregistrements nouveaux et existants dans le contrôleur séparément (vérifiez si: id.present?)

8
gamov