Search code examples
ruby-on-railsformsviewpostspolymorphism

Rails polymorphic posts associations and form_for in views


I've been having trouble setting up the form for a polymorphic "department" post in the department view. I followed the rails-cast tutorial for polymorphic associations here

Models:

   class Course < ActiveRecord::Base
        belongs_to :department, inverse_of: :courses
        has_and_belongs_to_many :users, -> { uniq }
        has_many :posts, as: :postable  #allows polymorphic posts
    end
    class Department < ActiveRecord::Base
        has_many :courses, inverse_of: :department
        has_many :posts, as: :postable  #allows polymorphic posts
        has_and_belongs_to_many :users, -> {uniq}
    end
    class Post < ActiveRecord::Base
        belongs_to :user, touch: true #updates the updated_at timestamp whenever post is saved
        belongs_to :postable, polymorphic: true #http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
        belongs_to :department, counter_cache: true #for counting number of posts in department
        belongs_to :course, counter_cache: true
        validates :department_id, :course_id, presence: true
    end

config/routes

  devise_for :users
  devise_scope :users do
    match '/users/:id',       to: "users#show",    via: 'get'
  end
  resources :departments do
    resources :courses
    resources :posts
  end
  resources :courses do
    resources :posts
  end

views/departments/show.html.erb

  <div class="tab-pane" id="posts"><br>
    <center><h3>Posts:</h3></center>
    <%= render "posts/form", postable: @department %>
  </div>

views/posts/_form.html.erb

<%= render "posts/wysihtml5" %>
<center><h3>Create New Post:</h3></center>

    <%= form_for [@postable, Post.new] do |f| %>
      <%= f.label :title %>
      <%= f.text_field :title, class: "form-control" %>

      <%= f.label :description %>
      <%= f.text_area :description, :rows => 3, class: "form-control" %>

      <%= f.text_area :content, :rows => 5, placeholder: 'Enter Content Here', class: "wysihtml5" %>

      <span class="pull-left"><%= f.submit "Create Post", class: "btn btn-medium btn-primary" %></span>
   <% end %>

controllers/post_controller.rb

class PostsController < ApplicationController
    before_filter :find_postable
    load_and_authorize_resource 

    def new
        @postable = find_postable
        @post = @postable.posts.new
    end

    def create
        @postable = find_postable
        @post = @postable.posts.build(post_params)

        if @post.save
            flash[:success] = "#{@post.title} was sucessfully created!"
            redirect_to department_post_path#id: nil        #redirects back to the current index action
        else
            render action: 'new'
        end
    end

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

    def index
        @postable = find_postable
        @posts = @postable.posts
    end
...
    private
    def post_params
        params.require(:post).permit(:title, :description, :content)
    end

    def find_postable   #gets the type of post to create
        params.each do |name, value|
            if name =~ /(.+)_id$/
                return $1.classify.constantize.find(value)
            end
        end
        nil
    end

controllers/departments_controller.rb

def show
    id = params[:id]
    @department = Department.find(id)

    @course = Course.new
    @course.department_id = @department
end

The error is "undefined method `posts_path' for #<#:0x0000010d1dab10>"

I think the error has something to do with the path in the form, but I don't know what. I've tried [@postable, @postable.posts.build] as well but that just gives me undefined method: PostsController.

Anybody know what's going on and how I can fix it?


Solution

  • @department is passed into the form partial as a local variable, but the form calls an instance variable:

    # views/departments/show.html.erb
    <%= render "posts/form", postable: @department %> # <------ postable
    
    # views/posts/_form.html.erb
    <%= form_for [@postable, Post.new] do |f| %>      # <------ @postable
    

    Thus, the namespaced route is not properly determined

    [@postable, Post.new]  # => departments_posts_path
    [   nil   , Post.new]  # => posts_path
    

    Checking your routes, posts are only accessible via nested routes. posts_path is not a valid route, it's method does not exist, and the error is correct: undefined method `posts_path'


    Fix:

    Set a @postable instance variable in the departments controller so that the form helper can use it:

    def show
        id = params[:id]
        @postable, @department = Department.find(id)  # <-- add @postable
    
        @course = Course.new
        @course.department_id = @department
    end
    

    Then you can simply call render in the view:

    <%= render "posts/form" %>