Fat Model, Skinny Controller in Rails with example - ruby-on-rails

I really want to start learning Rails best practices, especially following the "fat model, skinny controller" logic.
Say I have the following comment controller
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(comment_params)
#comment.user_id = current_user.id if current_user
#comment.save!
if #comment.save
redirect_to post_path(#post)
else
render 'new'
end
end
def edit
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
end
def update
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
if #comment.update(params[:comment].permit(:comment))
redirect_to post_path(#post)
else
render 'Edit'
end
end
def destroy
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
#comment.destroy
redirect_to post_path(#post)
end
private
def comment_params
params.require(:comment).permit(:comment)
end
What's a good place to start refactoring the code?
Immediately I think I an make the #post and #comment in both edit and update into a separate method, follow by calling a before_action on the method. But that is still putting all the code in the controller.
Are there any code that I can move to the model? If so, how should I structure them?

This code doesn't have much room for improvement, it's a basic crud, here's an example of a before_action like you suggested
before_action :load_post_and_comment, only: %i(edit update destroy)
def load_post_and_comment
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
end
And here a couple of other notes
def create
# ...
#comment.save!
if #comment.save
# ...
else
# ..
end
end
In this codition the you should remove the extra #comment.save! you only need to save once.
def update
# ...
if #comment.update(params[:comment].permit(:comment))
# ...
else
# ...
end
end
You already have the comment_params method, use it, because if you at any point add a new attribute to the comment, you'll update that method but you'll probably forget this part and you'll get werid errors till you notice that you need to permit here too.

If you want to really go all out with the skinny controller model, there is this gem: https://github.com/NullVoxPopuli/skinny_controllers
Where, you'd configure your CommentsController like so:
class CommentsController < ApplicationController
include SkinnyControllers::Diet
def create
if model.errors.present?
render 'new'
else
redirect_to post_path(model)
end
end
def update
redirect_to post_path(model)
end
# ... etc
private
def comment_params
params.require(:comment).permit(:comment)
end
end

Related

Why am I getting "AbstractController::DoubleRenderError" when calling redirect_to in after_action?

In the comments controller, I am redirecting to the articles show page after both create and destroy.
So I decided to write an after_action which would do the redirect_to.
class CommentsController < ApplicationController
before_action :find_article
before_action :find_comment, only: [:destroy]
after_action :goto_articles_page, only: [:create, :destroy]
def create
#comment = #article.comments.create(comment_params)
end
def destroy
#comment.destroy
end
private
def comment_params
params.require(:comment).permit(:commenter, :body)
end
def find_article
#article = Article.find(params[:article_id])
end
def find_comment
#comment = #article.comments.find(params[:id])
end
def goto_articles_page
redirect_to article_path(#article) and return
end
end
But this gives me AbstractController::DoubleRenderError after both create and destroy.
Why am I getting this error?
By default, Rails will render views that correspond to the controller action. See Rails Guides.
So in your create and destroy actions, Rails is performing a render by default. Then your after_action (which happens after the action) is redirecting, so it's double rendering.
Instead of an after_action, you could call the goto_articles_page method in your controller actions.
For example:
def destroy
#comment.destroy
goto_articles_page
end
def goto_articles_page
redirect_to article_path(#article) #return not needed
end
I think using return when rendering any action but when redirect_to use then not need to use return then finally you can remove and return
Rails guide very nicely explained that you can follow this carefully
redirect_to explanation
Hope to help

rails model association author id

I'm using rails_best_practices for linting my code.
Comments belong to post, which belongs to the user.
My comments_controller.rb file looks like this
class CommentsController < ApplicationController
before_action :find_post
def create
#comment = #post.comments.create comment_params
#comment.user_id = current_user.id
redirect_to #post if #comment.save
end
private
def find_post
#post = Post.find(params[:post_id])
end
def comment_params
params.require(:comment).permit(:post_id, :body)
end
end
And I'm getting this error use model association (for #comment).
After refactoring my create method looks like this
def create
#comment = #post.comments.create(
comment_params.merge(user_id: current_user.id)
)
redirect_to #post if #comment.save
end
My question is: What is the best and correct way to do this?
Normally I'd suggest baking in any required parameters inside your controller-specific _params function. That is, do this:
def comment_params
params.require(:comment).permit(:post_id, :body).merge(
user: current_user
)
end
Then by the time it gets to your controller action you're pretty much good to go.
What I tend to do is have a build method that constructs the right object for both new and create:
def build_comment
#comment = #post.comments.build(comment_params)
end
Now this will correctly populate if you relax the require constraint on params, but it's up to you how to make that flexible. I find this consistently populates and prepares the same object for both multiple edit rounds, and the first round where you need to set some defaults.

undefined method `user_name' for nil:NilClass

I'm trying to make a simple rails app, where useres can leave comments on posts.
When I make a new post, I run into a undefined method 'user_name' for nil:NilClass error.
Specifically, the following:
As you can see in the picture, #post.comments seems to contain a single comment with nil variables.
my comment controller is as follows:
before_action :set_post
def create
#comment = #post.comments.build(comment_params)
#comment.user_id = current_user.id
if #comment.save
flash[:success] = "You commented the hell out of that post!"
redirect_to #post
else
flash[:alert] = "Check the comment form, something went horribly wrong."
redicect_to #post
end
end
#...
private
def comment_params
params.require(:comment).permit(:content)
end
def set_post
#post = Post.find(params[:post_id])
end
And my post controller:
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :owned_post, only: [:edit, :update, :destroy]
#...
def create
#post = current_user.posts.build(post_params)
if #post.save
flash[:success] = "Your post has been created!"
redirect_to #post
else
flash.now[:alert] = "Your new post couldn't be created! Please check the form."
render :new
end
end
#...
private
def post_params
params.require(:post).permit(:image, :description)
end
def set_post
#post = Post.find(params[:id])
end
Thanks for the help. Sorry for and grammatical errors, I've been up for a while working on this, and am not a great speller when I'm awake.
try change
#comment.user_id = current_user.id
to
#comment.user = current_user
You need to either make sure comment does not get saved without an user/user_id, or you have to handle the case in view where user/user_id is nil
You are asking for the name of the user but you are not currently permitting a user_id in your comment_params:
This:
def comment_params
params.require(:comment).permit(:content)
end
Should be:
def comment_params
params.require(:comment).permit(:content, :user_id)
end
It is also possible that your code should be:
comment.user.name
instead of
comment.user.user_name
we would need to see your model to confirm. I would not use user_name as an attribute of user.
One of the comments for that post might not have a user. You can use try method here.
#comment.try(:user).try(:user_name)
But the ideal way to handle this would be to add a presence validation to the user model so that all comments created by any user in future will have a name.

ActiveModel::ForbiddenAttributesError while creating a new entry

I am new to Ruby on Rails and I was trying to create a simple app when I ended up having a ActiveModel::ForbiddenAttributesError
class PostsController < ApplicationController
def index
#posts = Post.all
end
def show
#post = Post.find(params[:id])
end
def new
#post =Post.new
end
def create
#post = Post.new(params[:post])
if #post.save
redirect_to post_path,:notice=>"success"
else
render "new"
end
end
def edit
end
def update
end
def destroy
end
private
def post_params
params.require(:post).permit(:Title, :content)
end
end
I have seen a similar error here but the solution for that did not fix my issue.
My version of rails is 4.2.0.
The error displayed is
You can't use that params[:post] hash (or any params[*] hash) directly in any mass-assignment method, you need to use a permit call so Rails knows you've checked it and to allow it.
So, change your Post.new to #post = Post.new(post_params)
Change #post = Post.new(params[:post]) to #post = Post.new(post_params).
I think that
def create
#post = Post.new post_params
if #post.save
flash[:success] = "Created new post"
redirect_to #post
else
render 'new'
end
end
def create
#post = Post.new(posts_params)
if #post.save
redirect_to post_path,:notice=>"success"
else
render "new"
end
end
private
def posts_params
params.require(:post).permit(:Title, :content)
end

ActiveModel::ForbiddenAttributesError

i've been following rails guide on creating and mounting an engine here.Created blog post and when i tried to comment ,it returned "ActiveModel::ForbiddenAttributesError in Blorgh::CommentsController#create " error.
Comment controller
require_dependency "blorgh/application_controller"
module Blorgh
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment])
flash[:notice] = "Comment has been created!"
redirect_to posts_path
end
end
end
and here is comment model
module Blorgh
class Comment < ActiveRecord::Base
end
end
how to resolve the issue?
I guess you are using rails 4. You need to mark all the required parameters
here it goes :
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(post_params)
flash[:notice] = "Comment has been created!"
redirect_to posts_path
end
def post_params
params.require(:blorgh).permit(:comment)
end
hope this link helps...
I had the same error. So if you disect the params hash it easy to see the nested comment params with text key. Seems the tutorial is for Rails 3, so for the rails 4 way with trusted params the changes needed is to add the comment_params method as below.
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"uOCbFaF4MMAHkaxjZTtinRIOlpMj2QSOYf+Ugn5EMUI=",
"comment"=>{"text"=>"asfsadf"},
"commit"=>"Create Comment",
"post_id"=>"1"}
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(comment_params)
flash[:notice] = "Comment has been created!"
redirect_to posts_path
end
private
# Only allow a trusted parameter "white list" through.
def comment_params
params.require(:comment).permit(:text)
end

Resources