I am completely new to rails and playing with the code to make pages work.
The link localhost:3000/zombies/1 works (show action)
but localhost:3000/zombies (index action) doesn't. Below are my routes and controller:
ROUTES ARE:
resources :zombies
CONTROLLER is:
class ZombiesController < ApplicationController
before_filter :get_zombie_params
def index
respond_to do |format|
format.html # index.html.erb
format.json { render json: #zombies }
end
end
def show
#disp_zombie = increase_age #zombie, 15
#zombie_new_age = #disp_zombie
respond_to do |format|
format.html # show.html.erb
format.json { render json: #zombie }
end
end
def increase_age zombie, incr
zombie = zombie.age + incr
end
def get_zombie_params
#zombie=Zombie.find(params[:id])
#zombies = Zombie.all
end
end
Why is this?
Editing answer based on the comment
I get a page with the error: ActiveRecord::RecordNotFound in
ZombiesController#index Couldn't find Zombie without an ID Rails.root:
C:/Sites/TwitterForZombies Application Trace | Framework Trace | Full
Trace app/controllers/zombies_controller.rb:85:in `get_zombie_params'
The url, localhost:3000/zombies which calls index action does not include id parameter.
That's why the app is failing at #zombie=Zombie.find(params[:id]).
If you want to fix this issue, use before_filter on show action only.
before_filter :get_zombie_params, only: :show
And insert this into index action as I have originally suggested.
def index
#zombies = Zombies.all
...
end
This is happening because when you define resources :zombies, you get these routes :
/zombies
/zombies/:id
Therefore when navigating to /zombies you don't have a params[:id], it is nil
Zombie.find method will raise an error if it can't find any record with the given id and halt further processing of your code.
You can use Zombie.find_by_id if you don't want an exception raised when there is no result.
But I don't think that this what you want here, you'd rather define a get_zombie_by_id method and a get_all_zombies method and separate the code from your get_zombie_params
Then you would have to define which method should be called before what action by changing your before_filter like so, in your case :
before_filter :get_zombie_by_id, :only => :show
before_filter :get_all_zombies, :only => :index
This way Zombie.find(params[:id]) will only get called when on the show action.
You can also use :except to do the opposite.
it does work because you need to send back ( to your index view ) the list of your zombies.
The get_zombie_params() excutes right but does not send #zombies to the index() action.
you need to do :
def index
#zombies = Zombie.all
#... the rest of the code
end
Related
What is the best way to handle the error then ID is not found?
I have this code in my controller:
def show
#match = Match.find(params[:id])
end
I was thinking about something like this:
def show
if #match = Match.find(params[:id])
else
render 'error'
end
end
But I still get:
ActiveRecord::RecordNotFound in MatchesController#show
Couldn't findMatch with 'id'=2
Why?
What is the correct solution?
Rescue it in the base controller and leave your action code as simple as possible.
You don't want to deal not found exception in every action, do you?
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, :with => :render_404
def render_404
render :template => "errors/error_404", :status => 404
end
end
By default the find method raises an ActiveRecord::RecordNotFound exception. The correct way of handling a not found record is:
def show
#match = Match.find(params[:id])
rescue ActiveRecord::RecordNotFound => e
render 'error'
end
However, if you prefer an if/else approach, you can use the find_by_id method that will return nil:
def show
#match = Match.find_by_id(params[:id])
if #match.nil? # or unless #match
render 'error'
end
end
You can use find_by_id method it returns nil instead of throwing exception
Model.find_by_id
There is two approaches missing:
One is to use a Null-Object (there I leave research up to you)
Te other one was mentioned, but can be placed more reusable and in a way more elegantly (but it is a bit hidden from you action code because it
works on a somewhat higher level and hides stuff):
class MyScope::MatchController < ApplicationController
before_action :set_match, only: [:show]
def show
# will only render if params[:id] is there and resolves
# to a match that will then be available in #match.
end
private
def set_match
#match = Match.find_by(id: params[:id])
if !#match.present?
# Handle somehow, i.e. with a redirect
redirect_to :back, alert: t('.match_not_found')
end
end
end
I have stumbled upon a situation where my application looks for an id that does not exist in the database. An exception is thrown. Of course, this is a pretty standard situation for any web developer.
Thanks to this answer I know that using rescue deals with the situation pretty neatly, like so:
def show
#customer = Customer.find(params[:id])
rescue ActiveRecord::RecordNotFound #customer with that id cannot be found
redirect_to action: :index #redirect to index page takes place instead of crashing
end
In case the customer cannot be found, the user gets redirected to the index page. This works absolutely fine.
Now, this is all nice, but I need to do the same rescue attempts in actions like show, edit, destroy, etc, i.e. every controller method that needs a specific id.
Having said that, here's my question:
Isn't there any way to generally tell my controller that if it can't find the id in any of its methods, it shall redirect to the index page (or, generally, perform a specific task)?
You must use rescue_from for this task. See example in the Action Controller Overview Guide
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
private
def record_not_found
redirect_to action: :index
end
end
Rails has a built-in rescue_from class method:
class CustomersController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :index
...
end
If you're talking about doing this within a single controller (as opposed to doing this globally in every controller) then here are a couple options:
You can use a before_filter to setup your resource:
class CustomerController < ApplicationController
before_filter :get_customer, :only => [ :show, :update, :delete ]
def show
end
private
def get_customer
#customer = ActiveRecord.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to :action => :index
end
end
Or you might use a method instead. I've been moving in this direction rather than using instance variables inside views, and it would also help you solve your problem:
class CustomerController < ApplicationController
def show
# Uses customer instead of #customer
end
private
def customer
#customer ||= Customer.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to :action => :index
end
helper_method :customer
end
In certain cases, I would recommend that you use Model.find_by_id(id) as opposed to Model.find(id). Instead of throwing an exception, .find_by_id returns nil. if the record could not be found.
Just make sure to check for nils to avoid NoMethodError!
P.S. For what it's worth, Model.find_by_id(id) is functionally equivalent to Model.where(id: id), which would allow you to build out some additional relations if you want.
Suppose you want a Blog with two different layouts. One layout should look like a conventional Blog with a header, a footer, a menu and so on. The other layout should only contain the blog posts and nothing more. How would you do that without losing the connection to the model, forcing the execution and rendering of only one action and prevent to repeat yourself (DRY)?
posts_controller.rb
class PostsController < ApplicationController
layout :choose_layout
# chooses the layout by action name
# problem: it forces us to use more than one action
def choose_layout
if action_name == 'diashow'
return 'diashow'
else
return 'application'
end
end
# the one and only action
def index
#posts = Post.all
#number_posts = Post.count
#timer_sec = 5
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
# the unwanted action
# it should execute and render the index action
def diashow
index # no sense cuz of no index-view rendering
#render :action => "index" # doesn't get the model information
end
[..]
end
Possibly I want to go the wrong way, but I can't find the right one.
Update:
My solution looks like this:
posts_controller.rb
class PostsController < ApplicationController
layout :choose_layout
def choose_layout
current_uri = request.env['PATH_INFO']
if current_uri.include?('diashow')
return 'diashow'
else
return 'application'
end
end
def index
#posts = Post.all
#number_posts = Post.count
#timer_sec = 5
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
[..]
end
config/routes.rb
Wpr::Application.routes.draw do
root :to => 'posts#index'
match 'diashow' => 'posts#index'
[..]
end
Two different routes are pointing at the same location (controller/action).
current_uri = request.env['PATH_INFO'] saves the url into a variable and the following if current_uri.include?('diashow') checks if it is the route we configured in our routes.rb.
You would select which layout to render depending on a certain condition. For example, a parameter in the URL, the device in which the page is being rendered etc.
Just use that condition in your choose_layout function, instead of deciding the layout on the basis of action_name. The diashow action is unnecessary.
I seem to have an authorization hiccup in my Ruby on Rails app. I have been using the following method in my application controller and it has been working beautifully.
def require_owner
obj = instance_variable_get("##{controller_name.singularize.camelize.underscore}") # LineItem becomes #line_item
return true if current_user_is_owner?(obj)
render_error_message("You must be the #{controller_name.singularize.camelize} owner to access this page", root_url)
return false
end
I then filter in the specific controllers by:
before_filter :require_owner, :only => [:destroy, :update, :edit]
I recently created a new controller which has a bit of a different naming convention that seems to be causing a problem. Normally my controllers read messages_controller or posts_controller. In this specific case I named the resource box_wod which generated box_wods_controller.
This is the only controller that seems to be having a problem with this filter so I bet I can tell it is in the naming of it and therefore the application_controller method is not recognizing the owner of the record.
I am not getting an error message but the application is not letting me edit, update or destroy a record because I am not the BoxWod owner. My routes are correct as are my associations and the correct information is getting passed to the box_wod table.
Is there a way to rewrite the application_controller method to recognize the additional underscore in the box_wod resource? Or is this even my problem?
UPDATE:
Here are the three methods in the BoxWodsController:
def edit
#workout_count = Workout.count
#box_wod = BoxWod.find(params[:id])
end
def update
#box_wod = BoxWod.find(params[:id])
respond_to do |format|
if #box_wod.update_attributes(params[:box_wod])
flash[:notice] = 'BoxWod was successfully updated.'
format.html { redirect_to(#box_wod) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #box_wod.errors, :status => :unprocessable_entity }
end
end
end
def destroy
#box_wod = BoxWod.find(params[:id])
#box_wod.destroy
respond_to do |format|
format.html { redirect_to(box_wods_url) }
format.js
end
end
In situations like this, I like to create a controller method that I can override when necessary. For example:
# application_controller.rb
class ApplicationController
def require_owner
obj = instance_variable_get("##{resource_instance_variable_name}")
# Do your authorization stuff
end
private
def resource_instance_variable_name
controller_name.singularize.camelize.underscore
end
end
# box_wods_controller.rb
class BoxWodsController
private
def resource_instance_variable_name
'box_wod' # Or whatever your instance variable is called
end
end
Lastly, please post your BoxWodsController code so we can better diagnose the problem.
It would seem that the #box_wod instance variable is not created until the require_owner method is invoked so current_user_is_owner? is checking a nil value, resulting in it always returning false. Perhaps you need another before_filter to populate the instance variable before require_owner is invoked.
I need to do API version checks and so I've implemented a check in my controller method like:
if request.header["api_version"] != 1
respond_to do |format|
format.json {render :json => expiredMessage}
end
return
end
Works fine, I'm returning in the control statement so Rails doesn't complain about double render. I tried to DRY these up into an application controller method as a before_filter, but then I get the multiple renders complaint from Rails. It seems that the scope has changed such that the 'return' in the control statement no longer returns from the original controller method?
I'm wondering how I can do a render from the application controller method, or if there's a better way how can I implement this functionality? Thanks!
A before filter is called before each controller action, returning from it just allows that action to proceed. I would use exceptions. (following code is untested)
class FrontEndController < ApplicationController
rescue_from Exception do |exception|
if exception.is_a? ExpiredException
respond_to do |format|
format.json {render :json => expiredMessage}
return
end
end
end
before_filter :check_api_version
def check_api_version
raise ExpiredException if request.header["api_version"] != 1
end
end
class ExpiredException < Exception; end