Search code examples
ruby-on-railscontrollershownested-form-for

form_for tag and nested form - to use custom method from controller


I'm new to Rails and making application where college members (teachers and students) can create posts and comment on them. Later on I wish to add nesting (ancestry) and points system in it.

I have Post, Comment and Member model. The Post model was made via Scaffolding, Member model was made with help of Devise, and Comment is just a model.

In my show page of Post, I'd like to have comments beneath the posts, I've made some progress (thanks to SO I came to know quite a bit) but now I am stuck with a problem that whenever I attempt to post a blank comment, rails was redirecting to the edit page. How to change this so that rails stays only on the show page and display errors?

For this I searched a bit, created a new method 'update_comments' in post_controller.rb and tried modifying the forms_for tag attributes, as in the code below, but now I get routing error on submitting.

app/models/member.rb

class Member < ActiveRecord::Base
  #Associations
  belongs_to :department

  has_one :student, :dependent => :destroy
  accepts_nested_attributes_for :student

  has_one :nstudent, :dependent => :destroy
  accepts_nested_attributes_for :nstudent

  has_many :posts, :dependent => :destroy
  has_many :comments, :dependent => :destroy  
end

app/models/post.rb

class Post < ActiveRecord::Base

  #Associations
  belongs_to :member
  has_many :comments, :dependent => :destroy

  accepts_nested_attributes_for :comments
end

app/models/comment.rb

class Comment < ActiveRecord::Base

  # Associations
  belongs_to :member
  belongs_to :post

  validates_presence_of :content
end

config/routes.rb

Urdxxx::Application.routes.draw do
  devise_for :members

  resources :posts do
    member do
     get 'update_comment'
    end
  end

  root :to => 'posts#index'

app/controllers/posts_controller.rb

class PostsController < ApplicationController
  # Devise filter that checks for an authenticated member
  before_filter :authenticate_member!

# GET /posts
# GET /posts.json
def index
  @posts = Post.find(:all, :order => 'points DESC')

  respond_to do |format|
    format.html # index.html.erb
    format.json { render json: @posts }
  end
end
...
# GET /posts/1/edit
def edit
  @post = Post.find(params[:id])    
end

# POST /posts
# POST /posts.json
def create
  @post = Post.new(params[:post])
  @post.member_id = current_member.id if @post.member_id.nil?

  respond_to do |format|
    if @post.save
      format.html { redirect_to @post, notice: 'Post was successfully created.' }
      format.json { render json: @post, status: :created, location: @post }
    else
      format.html { render action: "new" }
      format.json { render json: @post.errors, status: :unprocessable_entity }
    end
  end
end

# PUT /posts/1
# PUT /posts/1.json
def update
  @post = Post.find(params[:id])

  respond_to do |format|
    if @post.update_attributes(params[:post])
      format.html { redirect_to @post, notice: 'Post was successfully updated.' }
      format.json { head :no_content }
    else
      format.html { render action: "edit" }
      format.json { render json: @post.errors, status: :unprocessable_entity }
    end
  end
end

# DELETE /posts/1
# DELETE /posts/1.json
def destroy
  @post = Post.find(params[:id])
  @post.destroy

  respond_to do |format|
    format.html { redirect_to posts_url }
    format.json { head :no_content }
  end
end

# Not made by scaffold
def update_comment
  @post = Post.find(params[:id])        

  respond_to do |format|
    if @post.update_attributes(params[:post])
      format.html { redirect_to @post, notice: 'Comment was successfully created.' }
      format.json { head :no_content }
    else
      format.html { render action: "show" }
      format.json { render json: @post.errors, status: :unprocessable_entity }
    end
  end
end
end

app/views/posts/show.html.erb

<p> Have your say </p>
<%= form_for @post, :url => {:action => 'update_comment'} do |p| %>
  <%= p.fields_for :comments do |c| %>
     <!-- Following 3 lines saved my life -->
      <% if c.object.new_record? %>
        <%= c.text_area :content, :rows => 4 %>
        <%= c.hidden_field :member_id, value: current_member.id %>
      <% end %>
   <% end %>
   <%= p.submit "Reply" %>
<% end %>

image of my show page: https://i.sstatic.net/TBgKy.png

on making a comment: https://i.sstatic.net/JlWeR.png

Update:

Looked back and made changes here, following what Ken said. I don't know how but it works for now.

app/controllers/posts_controller.rb

def update
  @post = Post.find(params[:id])

  respond_to do |format|
  if @post.update_attributes(params[:post])
    format.html { redirect_to @post, notice: 'Post was successfully updated.' }
    format.json { head :no_content }
  elsif :comments
    format.html { render action: "show" }
    format.json { render json: @post.errors, status: :unprocessable_entity }
  else
    format.html { render action: "edit" }
    format.json { render json: @post.errors, status: :unprocessable_entity }
  end
  end
end

Solution

  • You don't need a custom method. It is not very RESTful. See, e.g., http://www.sitepoint.com/restful-rails-part-i/ for info on REST. This is not a case where there is justification to use a custom method.

    Whenever you find yourself adding custom methods you should think long and hard about whether it's necessary. Usually if you need custom methods what you actually need is another controller (or a different set of controllers).

    The update method here is all you need. If you really want to go to the show method after a failed update (though I don't know why) then change the render edit call in the block in the update method after the update fails.

    It seems like your real problem is the edit view isn't showing errors. Although the scaffold generated view should do that so maybe you changed it.

    In case you missed it you may also benefit from this screencast:

    http://railscasts.com/episodes/196-nested-model-form-part-1