Can't mass assign a nested attribute? - ruby-on-rails

I'm trying to assign comments which belongs_to answers (and answers has_many comments) and get a can't assign mass protected attributes: answer.
Here is my commentsController
class CommentsController < ApplicationController
def create
#answer = Answer.find(params[:answer_id])
#comment = #answer.comments.new(params[:comment])
#comment.save
redirect_to question_path(#answer)
end
end
Here is my answers controller for the page I am using
def show
#answer = Answer.find(params[:id])
#comment = #answer.comments.new(params[:comment])
end
My answer model accepts_nested_attributes for comments.
And this is my form,
<%= form_for([#answer, #comment]) do |f| %>
<p>
</p>
<p>
<%= f.label :comment %>
<%= f.text_area :answer, :cols => "50", :rows => "30"%>
</p>
<p>
<%= f.submit "Submit Comment" %>
</p>
Any ideas?

you should permit params before using it in your comments controller, in rails4 u can do this
class CommentsController < ApplicationController
def create
#answer = Answer.find(params[:answer_id])
#comment = #answer.comments.new(params.require(:comment).permit(:answer))
#comment.save
redirect_to question_path(#answer)
end
end
or u can also define a method in comments controller for permitting params as
def comment_params
params.require(:comment).permit(:answer)
end
then in your controller create a new record using this function as-
#comment = #answer.comments.new(comment_params)
#comment.save
as u had said u have nested attrs for comments in answer something like
class Answer < ActiveRecord::Base
has_many :comments, dependent: :destroy
accepts_nested_attributes_for :comments
end
and u are also using attr_accessible so to permit params in nested form u have to do
attr_accessible :comments_attributes along with your answer attrs in your answer.rb

Related

Create an object with multiple associations in ruby on rails

I have 3 objects : article, comment and user
I want, when a comment is published with a form, that the comment is associated with article AND user.
When I had only article and comment, it was perfect : we could send the form, create the comment, and it was showed.
Comment's form :
<%= form_with(model: [ #article, #article.comments.build ], local: true) do |form| %>
<p>
<%= form.label :body %><br>
<%= form.text_area(:body, {:class => 'form-control'} ) %>
</p>
<p>
<%= form.submit({:class => 'btn btn-success'}) %>
</p>
<% end %>
Create method of CommentsController :
before_action :authenticate_user!
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.build(comment_params)
#comment.commenter = current_user
#comment.save
redirect_to article_path(#article)
end
And models :
class Comment < ApplicationRecord
belongs_to :article
belongs_to :user
end
class User < ApplicationRecord
has_many :comments, dependent: :destroy
# Some devise things
end
class Comment < ApplicationRecord
belongs_to :article
belongs_to :user
end
I want that the comment is pushed on the database, but the actual result is nothing is done : the comment is simply ignored, and I don't know why. I already search here, I found a topic with a similar issue, but the solution was my actual code.

How to implement Update and destroy methods with a has many through association? Rails 5

Hi I'm having trouble by making the update and destroy method in my posts_controller, I'm able to create new Posts but I'm not able to update and I want to know how to destroy the model while destroying all the associations with the other model it.
My models:
Post Model
class Post < ApplicationRecord
has_many :comments, dependent: :destroy
has_many :has_categories
has_many :categories, through: :has_categories
validates :title, presence: true,
length: { minimum: 5 }
after_create :save_categories
def categories=(value)
#categories = value
end
private
def save_categories
#categories.each do |category_id|
HasCategory.create(category_id: category_id, post_id: self.id)
end
end
end
Has_Category model
class HasCategory < ApplicationRecord
belongs_to :post
belongs_to :category
end
Category Model
class Category < ApplicationRecord
validates :name, presence: true
has_many :has_categories
has_many :posts, through: :has_categories
end
So in my partial form for the new and the edit actions is like this
<%= form_with model: #post, local: true do |form| %>
<!--Inputs before the categories-->
<div>
<label>Categories</label>
<% #categories.each do |category| %>
<div>
<%= check_box_tag "categories[]", category.id %> <%= category.name %>
</div>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
My posts_controller create and update method
def create
#post = Post.new(post_params)
#post.categories = params[:categories]
if #post.save
redirect_to #post
else
render :new
end
end
def update
#post = Post.find(params[:id])
#post.categories = params[:categories]
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
My create action is working but the update action is just updating the inputs before the check_box_tag.
I know that the save_categories method on my Post model is the one who is taking the array I'm receiving from the form and creating the HasCategory association, How should I make the update action or even the destroy action given the situation that Is a many to many association?
The line has_many :categories, through: :has_categories gives you category_ids for post. So you can change your form:
<%= form_with model: #post, local: true do |form| %>
<!--Inputs before the categories-->
<div>
<label>Categories</label>
<%= f.collection_check_boxes(:category_ids, #categories, :id, :name)
</div>
<div>
<%= form.submit %>
</div>
<% end %>
and controller:
def create
# you need it here for correct rerendering `new` on validation error
#categories = Category.all # or maybe you have here more complicated query
#post = Post.new(post_params)
if #post.save
redirect_to #post
else
render :new
end
end
def update
# you need it here for correct rerendering `edit` on validation error
#categories = Category.all # or maybe you have here more complicated query
#post = Post.find(params[:id])
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
private
def post_params
params.require(:post).permit(:name, :some_other_post_params, category_ids: [])
end
You need to remove callback and categories=(value) method from the Post model. And define #categories in the new and edit actions. If it equals Category.all you can just put it to the form: f.collection_check_boxes(:category_ids, Category.all, :id, :name), without defining #variable

rails 4 no data is being transferred to db

I'm trying to do user reviews, when user can write review to another user, i create tables review with :content, user_reviews with :for_user_id, and by_user_id,
my routes
devise_for :users
resources :users, :only => [:show] do
resources :reviews
end
class Review < ActiveRecord::Base
belongs_to :user
has_many :users, :through => :users_reviews
end
class User < ActiveRecord::Base
has_many :users_review
has_many :reviews, :through => :users_review
end
class UsersReview < ActiveRecord::Base
belongs_to :user
belongs_to :review
end
class ReviewsController < ApplicationController
def new
#user = User.find(params[:user_id])
#review = #user.reviews.new(params[:for_user_id])
end
def create
#user = User.find(params[:id])
#review = current_user.reviews.build(review_params)
redirect_to root_path
end
def show
end
def index
#user = User.find(params[:for_user_id])
#reviews = Review.all
end
private
def review_params
params.require(:review).permit(:user_id, :user_id, :content)
end
end
and my view
<%= form_for([#user, #user.reviews.build]) do |f| %>
<%= f.text_area :content, placeholder: "Your review" %>
<%= f.submit "Go", class: "btn btn-large btn-primary" %>
<% end %>
all work, but no data send to the db :\ what i doing wrong?
You're not saving anything in the create method, therefore nothing is going to persist.
You'll want something like:
def create
#user = User.find(params[:id])
#review = #user.reviews.build(review_params)
if #user.save && #review.save
redirect_to root_path
else
(handle bad data)
end
end
I would also tend to agree with #marzapower - If you want to use current_user, you don't need the line above #review. My method above has this change included.
You are missing a call to the save method in your create action:
def create
#user = User.find(params[:id])
#review = current_user.reviews.build(review_params)
#review.save
redirect_to root_path
end
This post explains the difference between build and create.
I think you're missing two things. The User and Review classes both need to reference the UserReview class that they employ in the through relationship, e.g.,
class User < ActiveRecord::Base
...
has_many :user_reviews_received, class_name: 'UserReview', foreign_key: :for_user_id
has_many :reviews_received, through: :user_reviews_received, class_name: 'Review'
has_many :user_reviews_written, class_name: 'UserReview', foreign_key: :by_user_id
has_many :reviews_written, through: :user_reviews_written, class_name: 'Review'
...
end
As noted above, the create action seems a bit confused. Generally speaking the params should be the same for building up an instance in the new and create actions but you've got some differences. You could resolve this in one of two ways. One option would be to resolve it in the controller.
class ReviewsController < ActionController::Base
def new
#user = User.find(params[:user_id])
#review = #user.reviews_received.new(by_user_id: current_user.id)
end
def create
#review = #user.reviews_received.create(params[:review].merge(by_user_id: current_user.id))
redirect_to root_path
end
end
The second option would be to resolve it in the view (new first line in the form).
<%= form_for([#user, #user.reviews_received.build(by_user_id: current_user.id)]) do |f| %>
<%= f.hidden_field :by_user_id %>
<%= f.text_area :content, placeholder: "Your review" %>
<%= f.submit "Go", class: "btn btn-large btn-primary" %>
<% end %>
I would tend towards something like the first option
There's an error in the controller, I think:
def create
#user = User.find(params[:id])
#review = #user.reviews.build(review_params)
#review.save
redirect_to root_path
end
You were creating new Reviews for current_user instead of #user.

Micropost's comments on users page (Ruby on Rails)

On user's page i have many microposts and i want to add comment form and comments to each micropost.
I have three models: User, Micropost, Comment.
user.rb
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :comments
end
micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
has_many :comments, dependent: :destroy
end
comment.rb
class Comment < ActiveRecord::Base
attr_accessible :comment_content
belongs_to :user
belongs_to :micropost
validates :comment_content, presence: true
validates :user_id, presence: true
validates :micropost_id, presence: true
end
comments_controller.rb
class CommentsController < ApplicationController
def create
#comment = current_user.comments.build(params[:comment])
if #comment.save
flash[:success] = "Comment created!"
redirect_to current_user
else
render 'shared/_comment_form'
end
end
end
_micropost.html.erb
<tr>
<td class="micropost">
<span class="content"><%= wrap(micropost.content) %></span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
</span>
<%= render 'shared/comment_form' %>
</td>
</tr>
Comment form
<%= form_for(#comment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :comment_content %>
</div>
<button class="btn" type="submit">
Create
</button>
<% end %>
Every micropost must have its own comments.
In my DB i have comment table with
id / comment_content / user_id / micropost_id
columns.
Comment is not creating because RoR can't understand to which micropost belongs this new comment.
What should i do to have all needed information in my DB?
UPDATE
users_controller
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(page: params[:page])
#comment = Comment.new
end
microposts_controller
def create
#micropost = current_user.microposts.build(params[:micropost])
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to current_user
else
render 'shared/_micropost_form'
end
end
SOLUTION!!!
Big thanks to carlosramireziii and Jon! They are both right
comments_controller
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = Comment.new(params[:comment])
#comment.micropost = #micropost
#comment.user = current_user
if #comment.save
flash[:success] = "Comment created!"
redirect_to current_user
else
render 'shared/_comment_form'
end
end
_micropost.html.erb
<%= render 'shared/comment_form', micropost: micropost %>
Comment form
<%= form_for([micropost, #comment]) do |f| %>
routes.rb
resources :microposts do
resources :comments
end
Try passing in the current micropost to the comment partial
<%= render 'shared/comment_form', micropost: micropost %>
Then add the micropost to the comment form_for call
<%= form_for([micropost, #comment]) do |f| %>
Make sure your routes are nested
# in routes.rb
resources :microposts do
resources :comments
end
Then build the comment off of the micropost in the CommentsController
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = #micropost.comments.build(params[:comment])
...
end
I would use nested resources for micropost and comments like this in your routes.rb file:
resources :microposts do
resources :comments
end
Then in your comments controller, which you access through the micropost_comments_path(#micropost) url helper you can do the following to build the association in the create action:
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = Comment.new(params[:comment])
#comment.micropost = #micropost
#comment.user = current_user
if #comment.save
...
end
You could reduce the number of lines of code using the merge method, but I find that this sometimes reduces the readability of your code, and since you'll need the #micropost object if redisplaying the form after validation errors you may as well just fetch the record.

Rails: Routing Error, comment model to micropost model

I made a commenting system and I am trying to get it to post under a micropost but I constantly get this routing error. Any suggestions? All help is much appreciated!
Routing Error
No route matches [POST] "/microposts/comments"
Form
<div class="CommentField">
<%= form_for ([#micropost, #micropost.comments.new]) do |f| %>
<%= f.text_area :content, :class => "CommentText", :placeholder => "Write a Comment..." %>
<div class="CommentButtonContainer">
<%= f.submit "Comment", :class => "CommentButton b1" %>
</div>
<% end %>
</div>
comment controller
class CommentsController < ApplicationController
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = #micropost.comments.build(params[:comment])
#comment.user_id = current_user.id
#comment.save
respond_to do |format|
format.html
format.js
end
end
end
routes
resources :microposts do
resources :comments
end
Micropost Model
class Micropost < ActiveRecord::Base
attr_accessible :title, :content, :view_count
acts_as_voteable
belongs_to :user
has_many :comments
has_many :views
accepts_nested_attributes_for :comments
end
User Controller
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#school = School.find(params[:id])
#micropost = Micropost.new
#comment = Comment.new
#comment = #micropost.comments.build(params[:comment])
#microposts = #user.microposts.paginate(:per_page => 10, :page => params[:page])
end
end
The reason you are getting the error is that you are trying to build a form for a comments of a micropost that does not exist yet in the database.
the form, there is a -
form_for ([#micropost, #micropost.comments.new]) do |f|
And in UsersController you have -
#micropost = Micropost.new
comment is a sub-resource of micropost, so a url that creates a comment should look like /micropost/:id/comments where :id is the id of micropost. That is possible only after the micropost is saved.
So I believe your action should assign #micropost to an existing post, or create one right there to have the form working. Something like -
#micropost = Micropost.last || Micropost.create
would at least get rid of the error.
I'll try this again (deleted my other answer since, as Marc Talbot pointed out, was not the correct response to your issue).
Perhaps the issue is as simple as making :microposts be :micropost instead (to reflect your model's name).
resources :micropost do
resources :comments
end

Resources