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 %>
Related
I have a model called Thing and a controller called Things.
I followed this tutorial to try and set a maximum amount of Things a user can create.
Here's the warning: the terminal is giving a warning (not a huge issue) of DEPRECATION WARNING: Passing an argument to force an association to reload is now deprecated and will be removed in Rails 5.1. Please call "reload" on the result collection proxy instead. What should I do to make it go away?
Here's the problem: The line self.errors.add(:base, "Exceeded Things Limit") isn't displaying an alert or notice in the view. How would I achieve this? It's not creating a new Thing (because I met the maximum limit of 2) which is good, but it's just reloading a new form which would be horrible for user experience.
I'm working Rails 5 and Devise.
Here's my Thing model:
class Thing < ApplicationRecord
belongs_to :user
validate :thing_count_within_limit, :on => :create
attr_accessor :validation_code, :flash_notice
def self.search(search)
if search
where("zipcode LIKE ?", "%#{search}%")
else
all
end
end
def thing_count_within_limit
if self.user.things(:reload).count >= 2
self.errors.add(:base, "Exceeded Things Limit")
end
end
end
And here's my Things controller:
class thingsController < ApplicationController
before_action :find_thing, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user_first, only: [:edit, :update, :destroy]
before_action :authorized_pilot, only: [:edit, :update, :destroy, :profile]
def index
#things = Thing.all.order("created_at ASC")
#things = Thing.search(params[:search])
end
def new
#thing = current_user.things.build
end
def create
#thing = current_user.things.build(thing_params)
if #thing.save
redirect_to #thing
else
render "new"
end
end
def profile
#things = Thing.where(user_id: current_user)
end
def show
end
def edit
end
def update
if #thing.update(thing_params)
redirect_to #thing
else
render "edit"
end
end
def destroy
if #thing.destroy
redirect_to root_path
else
redirect_to #thing
end
end
private
def thing_params
params.require(:thing).permit(:title, :description, :image).merge(zipcode: current_user.zipcode)
end
def find_thing
#thing = thing.find(params[:id])
end
def authenticate_user_first
if current_user != thing.find(params[:id]).user
redirect_to #thing
else
end
end
end
Can anyone help? Help is greatly appreciated.
There are two things that aren't connected to each other.
First, there is the deprecation warning. Because it is just a warning, not an error, you can choose to ignore it at the moment. If you want to remove the warning, just follow its instruction and change this line
if self.user.things(:reload).count >= 2
to
self.user.things.reload.count >= 2
Seconds, your code works like expected. Rails validations do not raise any errors, but they add error messages to the object. Just make sure that you display the errors to the user. To display the error you added to :base, add something like the following to your new.html.erb view:
<% if #thing.errors[:base].any? %>
<div class="error_message">
<%= #thing.errors.full_messages_for(:base).to_sentence %>
</div>
<% end %>
I have a user profile controller called "userinfo" and it's corresponding view. The userinfo index is the root path. In the homepage(which is the userinfo index), I have a link that takes you to the user profile page. It is giving me this error when I go to the home page:
My routes are:
My userinfos_controller:
class UserinfosController < ApplicationController
before_action :find_userinfo, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
def index
#userinfors = Userinfo.find(params[:id])
end
def show
#myvideo = Video.last
end
def new
#userinformation = current_user.userinfos.build
end
def create
#userinformation = current_user.userinfos.build(userinfo_params)
if #userinformation.save
redirect_to root_path
else
render 'new'
end
end
def edit
end
def update
end
def destroy
#userinformation.destroy
redirect_to userinfo_path
end
private
def userinfo_params
params.require(:userinfo).permit(:name, :email, :college, :gpa, :major)
end
def find_userinfo
#userinformation = Userinfo.find(params[:id])
end
end
and my view is:
<%= link_to 'profile', userinfors_path(#userinfors) %>
My routes.rb file:
Rails.application.routes.draw do
devise_for :users
resources :userinfos do
resources :videos
end
resources :pages
get '/application/decide' => 'application#decide'
root 'userinfos#index'
get '/userinfos/:id', to: 'userinfos#show', as: 'userinfors'
end
Thanks for any help!
ok, there are multiple errors and you are not following conventions of rails, index is not for what you have used.
Index is used to list all the users and show for a particular one with id passed in params.
Your index path is, as you can see, /userinfos which is correct and it doesn't have any id with it but you are trying to find user with params[:id] which is nil and hence the error.
Lets try out this:
def index
#userinfors = Userinfo.all #pagination is recommended
end
In your index view,
<% #userinfors.each do |userinfor| %>
<%= link_to "#{userinfor.name}'s profile", userinfo_path(userinfor) %>
<% end %>
It should work now.
Please read routing and action controller to get the idea and understand the magic behind rails routing and mvc architecture..
I've made a very simple blog where users can Create, Edit and Delete posts however I want to add functionality where users can only Edit for a limited time (say 3 days). My understanding of Ruby is not strong enough to know how to do this so any help is appreciated.
This is my Notes (my name for Posts) controller
class NotesController < ApplicationController
before_action :find_note, only: [:show, :edit, :update, :destroy]
def index
#notes = Note.where(user_id: current_user)
end
def show
end
def new
#note = current_user.notes.build
end
def create
#note = current_user.notes.build(note_params)
if #note.save
redirect_to #note
else
render 'new'
end
end
def edit
end
def update
if #note.update(note_params)
redirect_to #note
else
render 'edit'
end
end
def destroy
#note.destroy
redirect_to notes_path
end
private
def find_note
#note = Note.find(params[:id])
end
def note_params
params.require(:note).permit(:title, :content)
end
end
I assume somewhere in the edit method I need to write a rule for restricting the ability to edit posts to only 3 days, using the created_at function somehow? I'm just at a loss as to exactly how to do this.
Any help is appreciated.
Perfect solution for that is :before_filter
class NotesController < ApplicationController
before_filter :check_time!, only: [:edit, :update]
def edit
end
def create
end
private
def check_time!
if Time.now() > #note.created_at + 3.days
flash[:danger] = 'Out of 3 days'
redirect_to note_path(#note)
end
end
end
I have a jobs pages where users can create new jobs edit and destroy their jobs and I want to let user edit or destroy only their post only if are connected else they will returned to the job show page for this i have this code in my job controller
def require_login
#job = current_user.jobs.find_by_slug(params[:id])
redirect_to job_path if #job.nil?
end
before_action :login_required
def login_required
redirect_to new_user_session_path unless user_signed_in?
end
before_action :login_required, :require_login, only: [:edit, :update, :destroy]
the only part where this don't work is when i not connected and try to edit my job it redirect me to the log in form but after login it redirect me to the home page instead of the edit page
First of all, the code block you pasted looks weird, for three reasons:
Indentation is wrong
You have before_action :login_required twice and it's not even the same
From the methods names it's unclear what you want exactly
From what you wrote, you want a user to edit or destroy a job only if they are logged in - otherwise you want to send them to the jobs index page. If that's right, the code in your controller should look like this:
class JobsController < ApplicationController
before_filter :require_login, :only => [:edit, :update, :destroy]
def edit
# your code here
end
def update
# your code here
end
def destroy
# your code here
end
private
def require_login
redirect_to job_path unless user_signed_in?
end
end
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