La gestion des formulaires sous rails est tout simplement magique. Mais qu'en est-il lorsque nous avons une liaison avec un autre modèle ?

Dans cet article, nous allons voir comment gérer en un seul formulaire plusieurs modèles. Nous allons nous atteler à la création d'un questionnaire (Survey).

Déclarations de nos modèles et accès aux attributs

Afin d'éviter d'avoir à trop tenir compte dans notre contrôleur SurveysController des sous-modèles (Questions, Answers) contenus dans notre modèle principal (Survey), nous allons utiliser les NestedAttributes d'ActiveRecord.

# app/models/participant.rb
class Participant < ActiveRecord::Base
  has_many :answers
  has_many :questions, through: :answers
end
# app/models/survey.rb
class Survey < ActiveRecord::Base
  has_many :questions

  accepts_nested_attributes_for :questions, :reject_if => :all_blank, :allow_destroy => true
end
# app/models/question.rb
class Question < ActiveRecord::Base
  belongs_to :survey
  has_many :answers
  has_many :participants, through: :answers

  accepts_nested_attributes_for :answers
end
# app/models/answer.rb
class Answer < ActiveRecord::Base
  belongs_to :participant
  belongs_to :question
end

Les paramètres reject_if et allow_destroy vont nous garantir qu'une question de serra pas créée si ses informations sont vides et pourra être supprimée par le même biais qu'elle a été créée. si nous n'utilisions pas les NestedAttributes, l'action create devrait au moins avoir une ligne comme celle-ci pour créer une question.

# app/controllers/surveys_controller.rb
def create
  ...
  @question = @survey.questions.build(params[:question])
  ...
end

Nous allons modifier notre contrôleur de la façon suivante : ajouter les attributs dans la whitelist et créer l'action answers (notre model de jonction). ```ruby

app/controllers/projects_controller.rb

beforeaction :setsurvey, only: [:show, :edit, :update, :destroy, :answers]

def answers @participants = Participant.all @questions = @survey.questions end

private # whitelist def surveyparams params.require(:survey).permit(:name, :questionsattributes => [:id, :content, :answersattributes => [:id, :content, :participantid] ]) end ```

Notez dans la whitelist le questions_attributes qui contient le answers_attributes car nous passons par le model Answer pour trouver nos Questions ou nos Participants (le through).

La route

Vous avez remarqué que notre contrôleur possède une fonction answers qui appèle set_survey. Ceci n'est pas une action RESTful et ne fonctionne pas comme ça. Nous devons donc la définir comme tel dans nos routes gràce à on: :member

# config/routes.rb

resources :surveys do
  get 'answers', on: :member
end
resources :participants

Les vues

Une vue simple mais qui explique bien comment tout ceci fonctionne de base serrait :

# app/views/surveys/_form.html.haml

= form_for(@survey) do |f| %>
  = @participants.each do |participant|
    %h3= participant.name
    = @questions.each do |question|
      = question.content
      = f.fields_for :questions, question do |q|
        = q.fields_for :answers, question.answers.find_or_initialize_by(participant: participant) do |a|
          = a.text_area :content
          = a.hidden_field :participant_id, participant.id
  = f.submit

Pour une interaction plus active

Cocoon nous livre deux méthodes link_to_add_association et link_to_remove_association qui permettent d'ajouter et de supprimer un sous-modèle. Pour fonctionner, cette Gem a besoin d'un Partial nommé _[sousModel]_fields.html.haml pour afficher les sous-modèles.

Ce qui donne dans notre cas les Partials suivants. Je vous réfère à la documentation pour connaître les détails des paramètres que l'on peut passer aux méthodes linktoadd_association et linktoremove_association.

# app/views/surveys/_form.html.haml

= form_for(@survey) do |f| %>
  = @participants.each do |participant|
    %h3= participant.name
    = @questions.each do |question|
      = question.content
      = f.fields_for :questions, question do |q|
        = q.fields_for :answers, question.answers.find_or_initialize_by(participant: participant) do |a|
          = render 'answer_fields', :a => answers
          = link_to_add_association 'add answer', a, :answers
  = f.submit
# app/views/surveys/_answer_fields.html.haml
= a.text_area :content
= a.hidden_field :participant_id, participant.id
= link_to_remove_association "remove answer", a

Lier un projet à une personne déjà existante ?

Imaginons que vos projets aient un utilisateur référent (un Owner). Nous devrions pouvoir sélectionner lors du link_to_add_association une personne déjà existante. Et bien c'est possible comme ceci :

    = f.association :owner, :collection => Person.all(:order => 'name'), :prompt => 'Choose an existing owner'
  = link_to_add_association 'add a new person as owner', f, :owner

Sources

railsforum cocoon createdbypete

Publié dans les catégories suivantes

ruby
comments powered by Disqus

Téléphone

+33 637 700 504

Adresse

Bordeaux, 33300
France