Undefined method `upvote_by' for nil:NilClass - ruby-on-rails

There are the same problem, but answers are not good for my code. Error is still there.
I reinstall gem, try different versions, and I think add methods in before_action properly and problem is still there.
class ArticlesController < ApplicationController
helper_method :sort_column, :sort_direction
before_action :authenticate_user!, except: [:index, :show, :like, :unlike]
before_action only: [:like, :unlike]
...
def like
#article.upvote_by current_user
redirect_to articles_path
end
def unlike
#article.downvote_by current_user
redirect_to articles_path
end
...
And html
<%= link_to "Like", like_article_path(article), class: 'like_url', method: :put %>
When click on it.
undefined method `upvote_by' for nil:NilClass

You need to find your Article first:
def like
#article = Article.find(params[:id])
# ...
end
def unlike
#article = Article.find(params[:id])
# ...
end
You can also extract article finding to before_action
before_action :find_article, only: %i[like unlike] # whatever you please
# ...
private
def find_article
#article = Article.find(params[:id])
end

Related

Rails ERB Validation

I'm trying to add a validation to my Rails app in order to display an error message if the user goes to the wrong id. The project has reviews, if I go to http://localhost:3000/reviews/:id that doesn't exist the app crashes, I'd like to prevent the runtime error by displaying a message.
In the model, I got this validation:
class Review < ApplicationRecord
validates :id, presence: true
end
Then, in the reviews/show.html.erb file, I'm trying this:
<% if #review.valid? %>
<div class='review-header'>
....
</div>
<% else %>
<% #review.errors.objects.first.full_message %>
<% end %>
This is also the Reviews Controller:
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
before_action :authorize!, only: [:edit, :destroy]
def index
if params[:search]
#reviews = Review.where("title like ?", "%#{params[:search]}%")
else
#reviews = Review.all
end
end
def new
#review = Review.new
#comment = Comment.new
#comment.review_id = #review.id
#We need to declare the comments in the new action.
end
def create
#review = current_user.reviews.new(review_params)
if #review.save
redirect_to review_path(#review)
else
render 'new'
end
end
def show
#comment = Comment.new
#We also need to declare the new comment in the show action.
end
def edit
end
def update
if #review.update(review_params)
redirect_to review_path(#review)
else
render 'edit'
end
end
def destroy
#review.destroy
redirect_to reviews_path
end
private
def set_review
#review = Review.find_by(id: params[:id])
end
def review_params
params.require(:review).permit(:title, :content, :category_id, :search)
end
def authorize!
authorize #review #authorize method using the Pundit gem
end
end
However, my project keep crashing rather than showing a message. If there's any way I can make this work? Thanks.
The whole setup of the question is actually broken.
You don't need to add a model validation for the id since ids are automatically generated by the database when you insert records. On most databases primary keys are also non-nullable. Adding the validation will actually break the model as will prevent you from saving records without manually assigning an id (bad idea).
Its also not the models job to verify that a record can be found in the controller. Instead your controller should use find so that it bails early if the record cannot be found:
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
before_action :authorize!, only: [:edit, :destroy]
private
def set_review
#review = Review.find(params[:id])
end
end
This halts execution of the method and other callbacks and prevents the NoMethodErrors that are bound to occur. There is no sense in continuing to process a request if the record that its supposed to CRUD doesn't exist.
By default Rails will handle an uncaught ActiveRecord::RecordNotFound exception by rendering a static HTML page located at public/404.html and returning a 404 status code. If you want to customize this on the controller level use rescue_from:
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
before_action :authorize!, only: [:edit, :destroy]
rescue_from ActiveRecord::RecordNotFound, with: :not_found
private
def set_review
#review = Review.find(params[:id])
end
def not_found
# renders app/reviews/not_found.html.erb
render :not_found,
status: :not_found
end
end
Note that this should be done in different view. If you add a <% if #review.present? %> to your reviews/show.html.erb view you should get your Rails licence revoked as the views one and only job is to display the review.
You can also configure the responses on the application level with config.exceptions_app.
The problem is that if the ID does not correspond to a review in the database, the #review object will be nil, and your line if #review.valid? will throw an error.
You need a different test, something like
<% if #review.present? %>
<div class='review-header'>
....
</div>
<% else %>
Review does not exist.
<% end %>

What is the best way to redirect a user to login screen from 2 or more different controllers

I have this in my application_controller
class ApplicationController < ActionController::Base
before_action :login_required, :only => 'users/login'
protect_from_forgery with: :exception
protected
def login_required
return true if User.find_by_id(session[:user_id])
access_denied
return false
end
def access_denied
flash[:error] = 'Oops. You need to login before you can view that page.'
redirect_to users_login_path
end
end
I want to use the login_required for each controller def method
Is there a better way instead of this?
class UsersController < ApplicationController
before_action :set_user, :login_required, :only => 'users/login'
#before_action only: [:show, :edit, :update, :destroy, :new]
def index
login_required
#users = User.all
end
def new
login_required
#user = User.new
end
end
Is there a better way to include login_required for all controllers methods since before_action doesn't seem to work?
I don't know the motivation of your logic, so I'll just focus on how you can solve this particular problem.
You can do something like this:
In your application controller:
class ApplicationController < ActionController::Base
before_action :login_required
private
def login_required
current_params = params["controller"] + "/" + params["action"]
if current_params == "users/new" or current_params == "users/index"
return true if User.find(session[:user_id])
access_denied
return false
end
end
def access_denied
flash[:error] = 'Oops. You need to login before you can view that page.'
redirect_to users_login_path
end
end
The login_required method will just run only on users controller's index and new action, for the rest, it'll just ignore. Also you can just use User.find() and no need to use User.find_by_id()
Now, in your users_controller.rb, you don't need to mention anything about login_required, everything will happen already in application_controller before coming here.
class UsersController < ApplicationController
before_action :set_user, :only => 'users/login'
#before_action only: [:show, :edit, :update, :destroy, :new]
def index
#users = User.all
end
def new
#user = User.new
end
end
Firstly, I'm going to suggest that you use devise for authentication, it's a lot more secure and should deal with this for you.
As for your problem, you should be able to specify the before_action like this:
before_action :set_user, :login_required, only: [:new]
Which you can put in your UserController. However if you want this globally, just put it in the ApplicationController, without the only: key.
If you want to require login for all pages except /users/login, then you almost have it right except you are specifying only: when you should be using except::
class ApplicationController < ActionController::Base
before_action :login_required, except: 'users/login'
...
end
This configuration will be applied to all sub-classes of ApplicationController as well.

Devise before_action explanation

I am having trouble understanding this line that gets automatically generated in the controller when I install Devise:
before_action :set_post, only: [:show, :edit, :update, :destroy]
I tried reading the documentation but I am unable to make sense of what it does. For example what does the :set_post symbol do? What is it part of?
Any explanations or resources where I can go for further reading would be appreciated.
Suppose you have a controller like this:
class PostController < ApplicationController
def index
#posts = Post.all
end
def show
#post = Post.find(params[:id])
end
def edit
#post = Post.find(params[:id])
end
end
You see that in show and edit actions there is the same code, you're breaking the DRY principle, so to avoid code repetitions you set an action (method):
def set_post
#post = Post.find(params[:id])
end
that will be performed before the actions that require that same code:
before_action :set_post, only: [:show, :edit, :update, :destroy]
In the end you'll have a controller like this:
class PostController < ApplicationController
def index
#posts = Post.all
end
def show
end
def edit
end
private
def set_post
#post = Post.find(params[:id])
end
end
:set_post - a method at the end of the controller.
The device does not have anything to do with

Update route in rails doesn't respond well to after_action?

class FrogsController < ApplicationController
before_action :find_frog, only: [:edit, :update, :show, :destroy]
after_action :redirect_home, only: [:update, :create, :destroy]
def index
#frogs = Frog.all
end
def new
#ponds = Pond.all
#frog = Frog.new
end
def create
#frog = Frog.create(frog_params)
end
def edit
#ponds = Pond.all
end
def update
#frog.update_attributes(frog_params)
end
def show
end
def destroy
#frog.destroy
end
private
def find_frog
#frog = Frog.find(params[:id])
end
def frog_params
params.require(:frog).permit(:name, :color, :pond_id)
end
def redirect_home
redirect_to frogs_path
end
end
Hi all. I was wondering if someone could explain to me why the update route in rails can't take my after_action of redirecting (custom made method on the bottom) it home. The error that I get when i include update in the after_action is "Missing template frogs/update".
This is going to cause me to manually add a redirect_to frogs_path inside the update method.
thanks!
The after_action callback is triggered after the action has run its course. You cannot use it to render or redirect. Do that within the action itself by calling the method:
def update
...
redirect_home
end

Checking an attribute is true before executing a CRUD action

Before any of my article controller crud actions can run (excluding index), i want to make sure that the article's active field is true.
I thought about doing this in a before_filter, but at that point #article has not been set, any ideas please?
Thanks
You could set the article in a helper method and remove some code duplication while you're at it.
class .. < ApplicationController
helper_method :current_article
def index
# your code etc..
end
private
def current_article
#article ||= Article.find(params[:id], :conditions => { :active => true }) ||
raise(ActiveRecord::RecordNotFound)
end
end
Basically you can now call current_article in your show, edit (etc) actions and views instead of #article.
You just need to do 2 before_filter.
1 with load the article and the second one to check if field exist
before_filter :load_article, :only => [:show, :edit, :update]
before_filter :has_field, :only => [:show, :edit, :update]
...
private
def load_article
#article = Article.find(params[:id])
end
def has_field
unless #article.active
redirect_to root_url
end
end

Resources