Dan
Dan

Reputation: 15

form_for a "belongs_to" model in 'show' of parent model

I have one model "Breads" that has_many "Posts". I would like to have a form to create a new "Post" on the 'show' page for a given "Bread" that creates the association to the record of 'Bread' which the 'show' page is displaying.

I have tried a few different methods, but all are giving an error. The method that I have shown below gives a "Association cannot be used in forms not associated with an object" error.

/views/breads/show.html.erb:

<p>
  <strong>Bread Type:</strong>
  <%= @bread.bread_type %>
</p>

<table>
  <tr>
    <th>Uploaded By</th>
    <th>Comment</th>
    <th>Picture</th>
  </tr>
<% @bread.posts.each do |post| %>
  <tr>
    <td><%= post.uploader %></td>
    <td><%= post.comment %></td>
    <td><%= image_tag post.attachment_url.to_s %></td>
  </tr>

<% end %>
</table>

<%= @bread.id %>

<%= simple_form_for @bread do |b| %>
  <%= simple_fields_for :posts do |p| %>
    <%= p.input :uploader %>
    <%= p.input :comment %>
    <%= p.association :bread, value: @bread.id %>
    <%= p.file_field :attachment %><br>
    <%= p.button :submit %>
  <% end %>
<% end %>

<%= link_to 'Back', breads_path %>

config/routes.rb

Rails.application.routes.draw do
  get 'welcome/index'

  root 'welcome#index'

  resources :breads

  resources :posts
end

controllers/breads_controller.rb:

class BreadsController < ApplicationController
  def index
    @breads = Bread.all
  end

  def show
    @bread = Bread.find(params[:id])
  end

  def new
    @bread = Bread.new
  end

  def edit
    @bread = Bread.find(params[:id])
  end

  def create
    @bread = Bread.new(bread_params)

    if @bread.save
      redirect_to @bread
    else
      render 'new'
    end
  end

  def update
    @bread = Bread.find(params[:id])

    if @bread.update(bread_params)
      redirect_to @bread
    else
      render 'edit'
    end
  end

  def destroy
    @bread = Bread.find(params[:id])
    @bread.destroy

    redirect_to breads_path
  end

  private
    def bread_params
      params.require(:bread).permit(:bread_type)
    end
end

models/bread.rb:

class Bread < ActiveRecord::Base
  has_many :posts
  validates :bread_type,  presence: true, uniqueness: true
end

models/post.rb:

class Post < ActiveRecord::Base
  belongs_to :bread
  mount_uploader :attachment, AttachmentUploader
end

Upvotes: 1

Views: 307

Answers (3)

Amit Suroliya
Amit Suroliya

Reputation: 1535

Do this -

  <%= simple_form_for @bread do |b| %>
    <%= b.simple_fields_for(:posts,@bread.posts.build) do |p| %>
      <%= p.input :uploader %>
      <%= p.input :comment %>
      <%= p.file_field :attachment %><br>
      <%= p.button :submit %>
    <% end %>
  <% end %>

and make changes in beard_params

 def beard_params
   params.require(:bread).permit!
 end

Here permit! requires all parameters and for other way you can use @pawan's answer.

Upvotes: 1

Pavan
Pavan

Reputation: 33542

Extending @Amit Suroliya answer, you need to add posts_attributes to bread_params

def bread_params
   params.require(:bread).permit(:id, :bread_type, posts_attributes: [:id, :uploader, :comment, :bread_id, :attachment])
end

Update:

You also need to add accepts_nested_attributes_for :posts in Bread model.

Upvotes: 0

Jan Strn&#225;dek
Jan Strn&#225;dek

Reputation: 808

Iam sorry, but this is not good way at all, try to don't abuse rails and rest routes :)

Here is easy example how to do that:

config/routes.rb

resources :bread do 
  resources :posts
end

This means there will be routes like:

bin/rake routes

breads - breads#index
bread/:id - breads#show
etc..
and most important
bread/:bread_id/posts/:id
...

That means posts are nested resources for bread...

app/controllers/breads_controller.rb

controller BreadsController < BaseController
   before_action :find_bread, except: %i(index create new)

   .... action new, update, edit etc..
end

but now its the important part in PostsController..

app/controllers/posts_controller.rb

controller PostsController < BaseController
   before_action :find_bread
   before_action :find_post, except: %i(index new create)
   before_action :build_post, only: %i(new create)

   .... action new, update, edit etc..

   # Example with :return link 
   def create
    if @post.save
       if params[:back] == 'bread_show'
         redirect_to bread_path(@bread)
       else
         redirect_to bread_post_path(@bread, @post)
       end
     else
       render 'new'
     end
   end

   private

   def build_post
      if params[:post]
         @post = @bread.posts.build(post_params)
      else
         @post = @bread.posts.build
      end
   end

   def find_post
     @post = @bread.posts.find(params[:id])
   end

   def find_bread
     @bread = Bread.find(params[:bread_id])
   end

   ... post params ...
end

Now you have rest full routes and you're able to do what you want without such a pain and clean

... output hidden

<%= @bread.id %>

<%= simple_form_for @bread.posts.build do |b| %>
  <%= p.input :uploader %>
  <%= p.input :comment %>
  <%= p.file_field :attachment %><br>
  <%# Send back link to return on proper page %>
  <%= p.hidden_field :back, 'bread_show' %>
  <%= p.button :submit %>
<% end %>

<%= link_to 'Back', breads_path %>

There can be some mistakes, I write this code from memory, can't try that :(

Upvotes: 0

Related Questions