4

My model structure looks like this:

Board has_many Topics. Topic has_many Posts.

app/models/board.rb

class Board < ActiveRecord::Base
    has_many :topics
end

app/models/topic.rb

class Topic < ActiveRecord::Base
    belongs_to :user
    belongs_to :board
    has_many :posts

    accepts_nested_attributes_for :posts

    validates :title, presence: true, length: { maximum: 255 }
    validates :user_id, presence: true
    validates :board_id, presence: true
    ...
end

app/models/post.rb

class Post < ActiveRecord::Base
    belongs_to :user
    belongs_to :topic

    validates :user_id, presence: true
    validates :topic_id, presence: true
    validates :content, length: { minimum: 8 }
end

Here is my view for creating a new Topic. the fields_for section is used to create the :content on the new Post

app/views/topics/new.html.erb

<div>
    <%= form_for [@board, @topic] do |f| %>

    <%= render 'shared/error_messages', object: @topic %>

    <%= f.label :title %>
    <%= f.text_field :title %>

    <%= f.fields_for @post do |p| %>
        <%= p.label :content %>
        <%= p.text_area :content %>
    <% end %>

    <%= f.submit "Post new topic", class: "button submit" %>
    <% end %>
</div>

When creating a new Topic, I want a new post with the :content from the form to also be created. Since the Post is dependent on having a Topic in order to be valid, they need to be created or rejected in tandem (if the :content or :title is invalid). I was told that accepts_nested_attributes_for would work correctly but when my code executes it only creates the Topic, not the Post.

app/controllers/topics_controller.rb

def new
    @board = Board.find(params[:board_id])
    @topic = @board.topics.build
    @post = @topic.posts.build
end

def create
    @board = Board.find(params[:board_id])
    @topic = @board.topics.build(topic_params.merge({user_id: current_user.id}))

    if @topic.save
        flash[:success] = "Topic created"
        redirect_to @topic
    else
        render 'new'
    end
end

private

def topic_params
    params.require(:topic).permit(:title, posts_attributes: [:content])
end

For the record, here is my Posts controller and routes, if it helps.

app/controllers/posts_controller.rb

def create
    @topic = Topic.find(params[:topic_id])
    @post = @topic.posts.build(post_params.merge({user_id: current_user.id}))

    if @post.save
        flash[:success] = "Post Created"
        redirect_to topic_path(@topic) 
    else
        render 'new'
    end
  end

  private

    def post_params
      params.require(:post).permit(:content)
    end

rake routes for Boards, Topics and Posts

    topic_posts GET    /topics/:topic_id/posts(.:format)      posts#index
                POST   /topics/:topic_id/posts(.:format)      posts#create
 new_topic_post GET    /topics/:topic_id/posts/new(.:format)  posts#new
      edit_post GET    /posts/:id/edit(.:format)              posts#edit
           post GET    /posts/:id(.:format)                   posts#show
                PATCH  /posts/:id(.:format)                   posts#update
                PUT    /posts/:id(.:format)                   posts#update
                DELETE /posts/:id(.:format)                   posts#destroy
   board_topics GET    /boards/:board_id/topics(.:format)     topics#index
                POST   /boards/:board_id/topics(.:format)     topics#create
new_board_topic GET    /boards/:board_id/topics/new(.:format) topics#new
     edit_topic GET    /topics/:id/edit(.:format)             topics#edit
          topic GET    /topics/:id(.:format)                  topics#show
                PATCH  /topics/:id(.:format)                  topics#update
                PUT    /topics/:id(.:format)                  topics#update
                DELETE /topics/:id(.:format)                  topics#destroy
         boards GET    /boards(.:format)                      boards#index
                POST   /boards(.:format)                      boards#create
      new_board GET    /boards/new(.:format)                  boards#new
     edit_board GET    /boards/:id/edit(.:format)             boards#edit
          board GET    /boards/:id(.:format)                  boards#show
                PATCH  /boards/:id(.:format)                  boards#update
                PUT    /boards/:id(.:format)                  boards#update
                DELETE /boards/:id(.:format)                  boards#destroy

And also the value of params at the start of topics_controller#create

{"utf8"=>"✓", "authenticity_token"=>"...", "topic"=>{"title"=>"New Title", "post"=>{"content"=>"New Content"}},"commit"=>"Post new topic", "action"=>"create", "controller"=>"topics", "board_id"=>"1"}

4
  • 1
    Notice that your params does not include post_attributes as your post_params suggests. Try using f.fields_for @post, as: :post_attributes do |p| instead Commented Aug 8, 2014 at 19:46
  • I modified the fields_for as suggested but my params still look like this: {"utf8"=>"✓", "authenticity_token"=>".....", "topic"=>{"title"=>"NEW TITLE", "post"=>{"content"=>"NEW CONTENT"}}, "commit"=>"Post new topic", "action"=>"create", "controller"=>"topics", "board_id"=>"2"} Commented Aug 11, 2014 at 18:07
  • Okay. So, I changed it to <%= f.fields_for :posts do |p| %> and my params are now being passed with the posts_attributes hash. But now they are running into validation problems with the Post model. When submitting the form I receive 2 form errors: Posts user can't be blank and Posts topic can't be blank. Commented Aug 12, 2014 at 18:20
  • I was able to nix the Posts user can't be blank by including a hidden field on the form, like so: <%= p.hidden_field :user_id, :value => current_user.id %>. I do not think that this is a good way to do this but I don't have any better ideas. I still have the Posts topic can't be blank. I can't wrap my head around how this is supposed to work. The Post needs a topic_id to save but the Topic wont have an id until it is saved. Commented Aug 12, 2014 at 21:19

1 Answer 1

10

Finally found the solution here

This was after I fixed the form to create the params correctly.

Basically, I needed to use :inverse_of on my models. I don't really understand what this accomplishes but it works. Here's my code

topic.rb

class Topic < ActiveRecord::Base
  belongs_to :user
  belongs_to :board
  has_many :posts, :inverse_of => :topic

   accepts_nested_attributes_for :posts

  validates :title, presence: true, length: { maximum: 255 }
  validates :user, presence: true
  validates :board, presence: true
end

post.rb

class Post < ActiveRecord::Base
  belongs_to :user
  belongs_to :topic, :inverse_of => :posts

  validates :user, presence: true
  validates :topic, presence: true
  validates :content, presence: true
end

app/views/topics/new.html.erb

<div>
  <%= form_for [@board, @topic] do |f| %>
  <%= render 'shared/error_messages', object: @topic %>

  <%= f.label :title %>
  <%= f.text_field :title %>

  <%= f.fields_for :posts do |p| %>
      <!-- I needed to pass in the current_user.id for the post -->
      <%= p.hidden_field :user_id, :value => current_user.id %>

      <%= p.label :content %>
      <%= p.text_area :content %>
  <% end %>

  <%= f.submit "Post new topic", class: "button submit" %>
  <% end %>
</div>

app/controllers/topics_controller.rb

def create
  @board = Board.find(params[:board_id])
  @topic = @board.topics.build(topic_params.merge({user_id: current_user.id}))
  debugger

  if @topic.save
    flash[:success] = "Topic created"
    redirect_to @topic
  else
    render 'new'
  end
end

private

def topic_params
    params.require(:topic).permit(:title, posts_attributes: [:content, :user_id])
end
Sign up to request clarification or add additional context in comments.

1 Comment

MAke sure that the user_id passed in the params matches the current_user's id (right now I could manually modify the hidden input in the form and set it to another one, which would lead to a creation of a Post belonging to somebody else)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.