5 votes

Utiliser un seul form_with pour créer et modifier des ressources imbriquées dans Rails

Contexte

Dans mon application, un série est composé de nombreux livres . Une série Afficher permet à un utilisateur de voir tous les livres d'une série et d'ajouter un nouveau livre à la série à l'aide d'un formulaire.

Tous les livres figurant sur la liste de l Afficher contient un lien vers une page Editer pour ce livre. La page d'édition contient la même forme utilisé pour ajouter initialement un livre. Lors de l'édition d'un livre, le formulaire doit se remplir automatiquement avec les informations existantes du livre.

Question

Comment configurer mon form_with afin qu'il puisse à la fois créer un nouveau livre et modifier un livre existant (en remplissant automatiquement le formulaire de modification) ? J'ai essayé les configurations suivantes, mais elles cassent soit la page Editer, soit la page Afficher :

  1. <%= form_with(model: [ @series, @series.books.build ], local: true) do |form| %>
    • Rupture de livre Editer la page
    • Erreur : Pas d'erreur, mais le formulaire ne remplit pas automatiquement les données.
  2. <%= form_with(model: @book, url: series_book_path, local: true) do |form| %>
    • Ruptures de série Afficher la page
    • Erreur : No route matches {:action=>"show", :controller=>"books", :id=>"6"}, missing required keys: [series_id]
  3. <%= form_with(model: [:series, @book], local: true) do |form| %>
    • Ruptures de série Afficher la page
    • Erreur : Undefined method 'model_name' for nil:NilClass
  4. <%= form_with(model: [@series, @series.books.find(@book.id)], local: true) do |form| %>
    • Ruptures de série Afficher la page
    • Erreur : undefined method 'id' for nil:NilClass
  5. <%= form_with(model: @book, url: [@series, @book], local: true) do |form| %>
    • Coupures lors de la soumission d'un nouveau livre sur la page Show de la série
    • Erreur : No route matches [POST] "/series/6"

Ressources que j'ai consultées :

Code existant

Vous trouverez ci-dessous des sections dépouillées du code concerné, ainsi que des liens vers l'endroit où elles se trouvent dans mon dépôt GitHub actuel.

config/ routes.rb

resources :series do
  resources :books
end

app/modèles/ book.rb

class Book < ApplicationRecord
  belongs_to :series
end

app/modèles/ series.rb

class Series < ApplicationRecord
  has_many :books, dependent: :destroy
end

db/ schema.rb

create_table "books", force: :cascade do |t|
  t.integer "series_number"
  t.integer "year_published"
  t.integer "series_id"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["series_id"], name: "index_books_on_series_id"
end

create_table "series", force: :cascade do |t|
  t.string "title"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

app/views/series/ show.html.erb

<%= render @series.books %>
<%= render 'books/form' %>

app/views/books/ livre.html.erb

<%= link_to 'Edit', edit_series_book_path(book.series, book) %>

app/views/books/ edit.html.erb

<%= render 'form' %>

app/views/books/ formulaire.html.erb

<%= form_with(model: @book, url: [@series, @book], local: true) do |form| %>
  <%= form.label :series_number %>
  <%= form.number_field :series_number %>

  <%= form.label :year_published %>
  <%= form.number_field :year_published %>
<% end %>

app/controllers/ books_controller.rb

class BooksController < ApplicationController
  def index
    @books = Book.all
  end

  def show
    @book = Book.find(params[:id])
  end

  def new
    @book = Book.new
  end

  def edit
    @series = Series.find(params[:series_id])
    @book = @series.books.find(params[:id])
  end

  def create
    @series = Series.find(params[:series_id])
    @book = @series.books.create(book_params)
    redirect_to series_path(@series)
  end

  def destroy
    @series = Series.find(params[:series_id])
    @book = @series.books.find(params[:id])
    @book.destroy
    redirect_to series_path(@series)
  end

  private
    def book_params
      params.require(:book).permit(:year_published, :series_number)
    end
end

Itinéraires

          Prefix Verb   URI Pattern                                 Controller#Action
        articles GET    /articles(.:format)                         articles#index
                 POST   /articles(.:format)                         articles#create
     new_article GET    /articles/new(.:format)                     articles#new
    edit_article GET    /articles/:id/edit(.:format)                articles#edit
         article GET    /articles/:id(.:format)                     articles#show
                 PATCH  /articles/:id(.:format)                     articles#update
                 PUT    /articles/:id(.:format)                     articles#update
                 DELETE /articles/:id(.:format)                     articles#destroy
    series_books GET    /series/:series_id/books(.:format)          books#index
                 POST   /series/:series_id/books(.:format)          books#create
 new_series_book GET    /series/:series_id/books/new(.:format)      books#new
edit_series_book GET    /series/:series_id/books/:id/edit(.:format) books#edit
     series_book GET    /series/:series_id/books/:id(.:format)      books#show
                 PATCH  /series/:series_id/books/:id(.:format)      books#update
                 PUT    /series/:series_id/books/:id(.:format)      books#update
                 DELETE /series/:series_id/books/:id(.:format)      books#destroy
    series_index GET    /series(.:format)                           series#index
                 POST   /series(.:format)                           series#create
      new_series GET    /series/new(.:format)                       series#new
     edit_series GET    /series/:id/edit(.:format)                  series#edit
          series GET    /series/:id(.:format)                       series#show
                 PATCH  /series/:id(.:format)                       series#update
                 PUT    /series/:id(.:format)                       series#update
                 DELETE /series/:id(.:format)                       series#destroy

9voto

papirtiger Points 1870

Vous pouvez passer un tableau au formulaire pour gérer les itinéraires imbriqués et les itinéraires "superficiels" :

<%= form_with(model: [@series, @book], local: true) do |form| %>

<% end %>

Rails compacte le tableau (supprime les valeurs nulles), donc si @series est nul, le formulaire reviendra à book_url(@book) o books_url . Cependant, vous devez gérer le paramétrage @series y @book correctement à partir du contrôleur.

class SeriesController < ApplicationController
  def show
    @series = Series.find(params[:id])
    @book = @series.books.new # used by the form
  end
end

Vous pourriez plutôt gérer cela dans vos vues en utilisant local variables :

# app/views/books/_form.html.erb
<%= form_with(model: model, local: true) do |form| %>

<% end %>

# app/views/books/edit.html.erb
<%= render 'form', locals: { model: [@series, @book] } %>

# app/views/series/show.html.erb
<%= render 'books/form', locals: { model: [@series, @series.book.new] } %>

Vous pouvez également utiliser la fonction shallow: true dans vos itinéraires afin d'éviter l'imbrication des itinéraires membres (afficher, modifier, mettre à jour, détruire) :

resources :series do
  resources :books, shallow: true
end

Cela vous permettra de le faire :

# app/views/books/edit.html.erb
<%= render 'form', model: @book %>

# app/views/books/_book.html.erb
<%= link_to 'Edit', edit_book_path(book) %>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X