How do you create a doubly nested model? - ruby-on-rails

I have a double nested resource comments belongs to answer, answer belongs to question. I'm trying to create a comment with
def create
#answer = Answer.find(params[:answer_id])
#comment = #answer.comments.create(params[:comment])
redirect_to question_path(#question)
end
From the form like this:
form_for([#answer, #answer.comments.build])
However, I'm getting an undefined method error for render #answer.comments. Is my create action wrong?
EDIT: I have the associations such as, answer has_many comments, and comment belongs_to answer and the nested routing file for resources..

You probably want to refactor so you're not doing the logic of building the comments in the view.
Build the comment in the controller#new action for the form, then
form_for [#answer, #comment]

First thing first.
In order to use #answer.comments you need to have
has_many :comments
in your answer.rb (model)

Related

Creating child instances at the same time as their parent

I'm trying to create a polling system:
# models
class Poll < ActiveRecord::Base
has_many :answers
end
class Answer < ActiveRecord::Base
belongs_to :poll
end
# routes
resources :polls do
resources :answers
end
Poll having one string attribute, question, and Answer having one string attribute, answer, and one integer, votes.
I'd like to have it so when a user creates a Poll, a form for a nested Answer instance is displayed. Then have a button with an AJAX call for another form (allowing up to say 10) so multiple answers can be added.
I know how to do the AJAX part, but I don't know how to add child instances to a parent that doesn't exist yet.
The only way I can think of is to use a text_field_tag to pass it through params, then manually create it in the PollsController create method. Sort of like this:
def create
#poll = #poll.create(poll_params)
if #poll.save
if params[:poll_answer1].present?
#poll.answers.create!(answer: params[:poll_answer1])
end
if params[:poll_answer2].present?
#poll.answers.create!(answer: params[:poll_answer2])
end
flash[:notice] = "Poll created."
redirect_to #poll
else
flash[:error] = "Poll could not be created."
render :new
end
end
But it seems like there should be a better way. I'm not opposed to scrapping the entire setup, and maybe placing the questions in an array attribute on Poll instead. But then there's the issue of counting votes.

How to fit a form into a rails show after another form? Multiple same forms

I am making this form for a reply function in a blog-like app with the same recipe as the comment in which it should nest. (Comment recipe)
I get the following error when I try to view my app in the browser:
No route matches {:action=>"index", :post_id=>"10", :controller=>"replies", :comment_id=>nil} missing required keys: [:comment_id]
This is my reply view file for my replies/_form.html.haml
%h5 Reply
= form_for [ #post, #comment, #reply ] do |f|
%p
= f.label :name
%br
= f.text_field :name
%p
= f.label :talk
%br
= f.text_area :talk
%p
= f.submit 'Submit'
This is my replies controller getting a hold on the comments_id much like the recipe said I should do between the comments and the post_id:
class RepliesController < ApplicationController
def create
#reply = Reply.new(reply_params)
#reply.comment_id = params[:comment_id]
#reply.save
redirect_to post_path(#reply.comment.post)
end
end
And this is my id passing in the comments show controller as it is similar in the post show controller. Or should I add something more to the post show controller now?
def show
#reply = Reply.new
#reply.comment_id = #comment.id
end
I tried adding replies though the rails console. They show up neatly, so I think my routes file works. Something with the id's and the handling of the collections isn't going great though. The form part is not working.
I don't like adding gems if I don't have to, I want to understand my app.
EDIT: I should probably add that my app has a view that looks like a indexing-form system in a indexing-form system.
Below the post there are comments - with a form, and below these there are replies - with a form.
EDIT 2: First argument in form cannot contain nil or be empty is what I get now al the time.
EDIT 3: I still can't make my forms under the comments. This is my routes file, maybe it clarifies.
resources :posts do
resources :comments do
resources :replies do
end
end
end
I am debugging now by making print outs and found out that in my _form haml file for a reply rails can find the #post, but not the comment nor replies (but they have to be created of course with the form), while I can get almost exactly the same structure to work in my _form for a comment.
Is it even possible in Rails to have multiple forms printed out on the same page?
Still all help is appreciated!
EDIT 4: I have gotten a little further. Now Rails says:
No route matches {:action=>"index", :post_id=>"2", :controller=>"replies", :comment_id=>nil} missing required keys: [:comment_id]
As a direct effect of changing my show action in the post controller:
def show
#post = Post.find(params[:id])
## create a blank comment
#comment = Phase.new
#comment.post_id = #post.id
##
## The same for a blank reply
#reply = Reply.new
#reply.comment_id = #comment.id
##
end
The last line of this action seems to not make any difference. Now I just need to be able to grab this comment_id in the reply form and then I am done. Nearly a week of struggle.
I believe you may not be getting a #comment.id because you are doing Comment.new(reply_params) and objects only get id fields whens you use .create OR .save after instantiating with new. Let me know if this helps!
Because of this you are getting the nil error for #comment in the form.
EDIT
It seems like you are looking to get the comment_id from the form, another solution would be to get it from the URL by having this happen under comments path. You'd have to nest your resources so it's something like comments/:id/reply/:reply_id or comments/:id/reply/new.
Here's some great examples for nested attributes: http://guides.rubyonrails.org/routing.html#nested-resources
SECOND EDIT
Okay, I was a bit confused because typically you would have the form in new not in show. Either way I would be looking at the instance variables being passed in. How do your associations look?
if it's Post has_many :comments and Comment has_many :replies, then you can do something like this in your show actions:
def new
#post = Post.find(params[:post_id]
#comment = #post.comments.find(params[:comment_id]) #scopes your search to only the comments belonging to this post
#reply = #comment.replies.new
end
The more important point to get out of this is that each instance variable needs to be passed in. that being said, I'm not sure which ones are currently working for you
As far as create you should be doing something similar, creating through the associations. This rails magic handles all the foreign keys and assigning the id's etc:
def create
#post = Post.find(params[:post_id]
#comment = Comment.find(params[:comment_id])
#reply = #comment.replies.new(reply_params)
if #reply.save
# some logic
redirect_to wherever_path
else
# other logic
render :new
end
end
The more important point to get out of this is that each instance variable needs to be passed in. that being said, I'm not sure which ones are currently working for you
Let me know how this works!

Form for 3 association models

I am still a newbie so please forgive this silly question. I got 3 models:
- User (generated by devise), Comment and Post
User has many posts and comments
Comment belongs to both post and user
Post has many comments and belongs to user
My routes.rb
resources :users do
resources :posts do
resources :comments
end
end
My form code:
<%= form_for([#user,#post,#comment]) do |f| %>
...
<% end %>
I want to generate to user_post_comments_path but the above form_for generate to post_comments_path. Why? Did I misunderstand something. Thanks alot
The Rails routing and Form handling is very confusing and I still get it wrong all the time...
I think that your problem is that some of your variables is not set (nil) and so rails can not determine what you are actually up to.
I would also like to recommend to you that you don't nest your routes like that unless you have to for the sake of the urls.
It's usually enough to nest one level deep and use the current_user to assign to models when creating them. This also reduces the security risk involved when posting ids of other users:
def create
#post = current_user.posts.build(post_params)
[...]
end
def create
#comment = current_user.comments.build(comment_params)
#comment.post = Post.find params[:post_id]
[...]
end

How can I assign multiple related objects to an object in ActiveRecord

I have the following two models
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
I have an object #post and an array of comments #comments. How can I assign all the comment objects to post in a single line?
#post.comments = #comments
should do what you're asking. Or am I missing something?
#post.update_attributes(:comments => #comments)
OR
#post.comments = #comments ; #post.save
Not sure what exactly do you mean, but maybe this will help:
#post.comments << #comments
The simple answer to your question is
#post.comments = #comments
However you may want to examine carefully how exactly you are creating your comments. It is more likely that comments will need to be created one at a time and in that case you can simply do the following
#post.comments.create!(:body => "foo")
This will add a new comment to your Post

Rails restful routes problem

I've got two models: Book and ReadingList. A ReadingList has_and_belongs_to_many Books. On the BooksController#show page, I'd like to have a select list that shows all the reading lists, with a button to add the current book to the selected reading list.
Presumably this should go to the ReadingListController#update action, but I can't specify this as the form's URL, because I won't know which ReadingList to send to at the time the form is created. I could hack it with JavaScript, but I'd rather not rely on that.
Would it be better to have a custom action in the BooksController that accepts a reading list id to add the book to, or can I work the routes so this request ends up getting to the ReadingListController#update action?
I suggest that you have a resource which is a ReadingListEntry that represents a book in a reading list. Then you can simply POST to that resource to add it. There doesn't actually need to be a model behind it, you can manipulate the reading list directly.
Obviously this is something that could easily be achieved by using Ajax to submit the form, but in the case where JavaScript is disabled / unavailable, your best option is to have a custom action in the BooksController that adds it to the required reading list.
You could combine both by having the form pointing to the action in the BooksController, but having an onsubmit handler that posts to the ReadingList controller via Ajax.
I would create a custom action and route such that you can provide a book_id and list_id and form the relation.
Assuming you're using restful routes
resources :books do
post '/lists/:list_id/subscribe' => 'lists#subscribe', :as => :subscribe
end
def subscribe
#list = List.find params[:list_id]
#book = Book.find params[:book_id]
#list << #book
end
Now you can use button_to with or without ajax.
Perhaps a has_many :through relationship would be better? I like Anthony's idea of a ReadingListEntry resource - perhaps put a model behind this giving you:
# models/book.rb
has_many :reading_list_entries
has_many :reading_lists, :through => :reading_list_entries
I think here you are changing the Book, not the ReadingList. Therefore you should PUT to the BooksController#update resource with a new list_id attribute.
# in views/books/show.html.erb
<%= form_for #book, :url => book_path(#book) do |f| =>
<%= f.select :list, ReadingList.all.map { |l| [l.name, l.id] } =>
<%= submit_tag "Change" =>
<% end %>
# in controllers/books_controller.rb
# params[:book][:list_id] => 123
def update
#book = Book.find(params[:id])
#book.update_attributes(params[:book])
end
# config/routes.rb
resources :books
resources :lists do
resources :books
end
If you wanted a Book to belong to more than one ReadingList you'd need a has_and_belongs_to_many relationship instead

Resources