Sorry if the question is obvious, I am only starting to work with Rails.
I have a following code in several controller methods now:
respond_to do |format|
if #project.save
format.html { redirect_to(edit_project_url(#project), :notice => '#{user.name} added to #{role}.') }
format.js
else
format.html { render :action => "edit" }
format.js #...
end
end
So the question is, what is the best way to do the same thing for errors in all methods?
Is it recommended that I use save! and handle it in rescue_action?
Or should I do my own respond method and pass save in a block?
It's often more convenient to use the exception-raising variant of save and rescue that later in the block than to branch like that. The advantage to exceptions is they'll bust out of transactions.
def create
#project.save!
respond_to do |format|
format.html { redirect_to(edit_project_url(#project), :notice => '#{user.name} added to #{role}.') }
format.js
end
rescue ActiveRecord::RecordInvalid
respond_to do |format|
format.html { render :action => "edit" }
format.js #...
end
end
You'll find that it gets really tricky to wrangle your way out of a pile of nested if statements when trying to save more than one object at a time, but a simple rescue for exceptions will handle it neatly.
def create
Project.transaction do
#project.save!
#something_else.save!
#other_stuff.save!
end
# ...
rescue ActiveRecord::RecordInvalid
# ...
end
If any one of those saves blows up you'll get an exception. To ensure that all of them are displaying validation errors you might have to call .valid? on each to prime them or you will have those after the failure left untested.
It's not a bad thing to use the if #object.save pattern. However, if you are doing exactly the same for all your actions on your controller, you can define a rescue_from action.
Something like
class MyController < ActionController::Base
rescue_from ActiveRecord::RecordInvalid do
render :action => edit
end
end
Related
I feel somewhat stupid about this one, but:
if #prof.update_attributes(params[:profile])
respond_to do |format|
format.html {redirect_to(#prof, :notice => "Profile successfully created.") }
end
end
...is in the update method of my controller. I have some attributes being validated in the model.
If validation fails, I just want them back on the same form to be scolded by various red text. (ie. everything in the errors array).
I'm getting a 'template missing' error when validation fails - the template for 'update'. I feel like I'm overlooking something extremely simple. Help!
Try this:
respond_to do |format|
if #prof.update_attributes(params[:profile])
format.html { redirect_to(#prof, :notice => "Profile successfully created.") }
else
format.html { render action: 'edit' }
end
end
The cause of the error is due to the fact that Rails, unless told otherwise, will attempt to render a template with the same name as the action, in this case update, which obviously doesn't exist.
What you want to do is tell rails to render the edit action again in the event of an error. Typically, you would do this with the respond_to block, allowing the block to respond differently depending on whether validation passed or failed.
At present, you have your if statement wrapping the block, and no statements telling rails to render differently in the event of an error. To fix this, I would do the following:
respond_to do |format|
if #prof.update_attributes(params[:profile])
# all is well, redirect as you already wrote
else
format.html { render action: 'edit' }
end
end
There are some instances where I need to both have a template and return error codes when using respond_with in Rails 3.
I have a before filter that is as follows:
def ensure_premium
respond_with("Must be a premium user!", status: 401, location: nil) unless current_user.is_premium?
end
and a create action that does the following:
def create
#wait_list = #hangout.wait_lists.find_or_create_by(user_id: current_user.id)
respond_with(#wait_list) do |format|
format.json {render 'create', status: 201}
end
end
Even though the before filter trips, it still tries to render the template which results in an error. What am I missing to get it to return the right error and status code and not render the template?
You have multiple respond_with's for the create action. But I think more critically, you might need:
def ensure_premium
respond_with :json => {:error => "Must be a premium user!", :status => :unauthorized } unless....
I don't think this is the problem, but make sure in your controller you have
class SomeController < ApplicationController
respond_to :json
I ended up going a different route completely so this question is no longer valid.
I have RoR 3.0 web application which is acting as an OAuth API provider. Now, in API I'd like to return correct HTTP error codes to the API consumer. How do I do this?
Here is example:
def destroy_oauth
#item = Item.find(params[:id])
if(!#item.nil? && #item.user_id == current_user.id)
#item.destroy
respond_to do |format|
format.js
format.xml
end
else
raise ActionController::RoutingError.new('Forbidden')
end
end
So, in case of error I'm trying to return Forbidden 403 code. Still, when running this I'm getting always 404 Not Found returned. How do I return the correct code?
Or is this somehow webserver configurable thing?
When you're just giving a status code and there is no body, a convenient way is
head 403
This method also accepts the symbolic names for status codes, such as
head :forbidden
You should render page with correct status.
render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false)
According to ActionController::Head docs just use this pattern in actions
return head([status]) if/unless [some condition here]
Example:
return head(:gone) if #record.deleted?
return head(:forbidden) unless #user.owns?(#record)
return is used to make sure that no remaining code in the action will be run.
I think you have two problems here: first is that your #item = Item.find(params[:id]) line is raising 404 and execution never gets to where intended (if statement). Second is that you are raising exceptions and never catch them. Try:
def destroy_oauth
begin
#item = Item.find(params[:id])
if(!#item.nil? && #item.user_id == current_user.id)
#item.destroy
respond_to do |format|
format.js
format.xml
end
else
raise ActionController::RoutingError.new('Forbidden')
end
rescue ActiveRecord::ResourceNotFound
redirect_to :action => 'not_found', :status => 404 # do whatever you want here
rescue ActionController::RoutingError
redirect_to :action => 'forbidden', :status => 403 # do whatever you want here
end
end
Something along those lines, but you also mentioned that you are building the API, so when you are rescuing the error, you may want to render xml error info. Something like:
# in application_controller.rb
rescue_from ActionController::RoutingError, :with => :render_forbidden_error
private
def render_forbidden_error(e)
render :status => e.status, :xml => e
end
Good luck. Udachi.
well, you can use
:status =>500
But, In default Rails take care of the error type rendering itself.
Errors default pages are in the public directory. 500.html,404.html etc..
For more information on :status , how to use it click here
I have been trying to get to grips with jQuery and been following a railscast on adding an Ajax add review form, which works fine but I would now like to add into it the ability for a review to belong to a user as well as a venue.
Reviews controller
def create
#review = Review.create!(params[:review])
#review.venue = #venue
if #review.save
flash[:notice] = 'Thank you for reviewing this venue!'
respond_to do |format|
format.html { redirect_to venue_path(#venue) }
format.js
end
else
render :action => :new
end
end
views\reviews\create.js.erb
$("#new_review").before('<div id="flash_notice"><%= escape_javascript(flash.delete(:notice)) %></div>');
$("#reviews_count").html("<%= pluralize(#review.venue.reviews.count, 'Review') %>");
$("#reviews").append("<%= escape_javascript(render(:partial => #review)) %>");
$("#new_review")[0].reset();
I have tried changing the controller to:
def create
#review = #current_user.reviews.create!(params[:review])
#review.venue = #venue
if #review.save
flash[:notice] = 'Thank you for reviewing this venue!'
respond_to do |format|
format.html { redirect_to venue_path(#venue) }
format.js
end
else
render :action => :new
end
end
but it just wont submit, with no errors.
I think I have the models set correctly with belongs_to and has_many, I think this is a controller issue I'll add other code bits if needed.
Development log
NoMethodError (undefined method `reviews' for nil:NilClass):
app/controllers/reviews_controller.rb:14:in `create'
Thanks for any help!
It appears that your error is residing with #current_user. According to your development log, #current_user is nil when you call #current_user.reviews on it. I would say track down where this #current_user instance variable is being set and find out why it is nil. Now, what kind of authentication are you using? Most authentication plugins, especially those used by Ryan Bates of the Railscasts you mentioned, use a local variable, say just current_user, as the means to access the currently signed in user. I know I do in all my code.
So, rewrite the line as
#review = current_user.reviews.create!(params[:review])
and see if that works. If it doesn't, change it back and then track down where this #current_user is being set. Chances are good it is being set in a before_filter :method_name at the beginning of your controller.
Calling create! (with exclamation mark) will throw an exception and thus abort your create action if saving fails. Check your log/development.log for these exceptions.
Use build instead of create and lose the exclamation mark.
def create
#review = #current_user.reviews.build(params[:review])
#review.venue = #venue
if #review.save
flash[:notice] = 'Thank you for reviewing this venue!'
respond_to do |format|
format.html { redirect_to venue_path(#venue) }
format.js
end
else
render :action => :new
end
end
Given the following controller:
def create
if #mymodel.save
format.js
else
format.js { render :js => #mymodel.errors }
end
end
What's the Rails way of handling a .JS error response... Do I create .js file with a different file name just for servering Errors?
Do I add an IF ELSE in the .js file?
thanks
It's been a while since this question was posted - but I just spent a while figuring this out & couldn't find too much help on this online, so:
The solution is to create .js.erb files - one for success and one for failure.
def create
#foo = Foo.new(params[:foo])
if #foo.save
respond_to do |format|
format.html { redirect_to root_path }
format.js { render :action => "success"} #rails now looks for success.js.erb
end
else
respond_to do |format|
format.html { render :action => 'new'}
format.js { render :action => "failure"} #rails now looks for failure.js.erb
end
end
end
end
If seems that if you don't specify a file name, rails will look for create.js.erb in both cases (because format.js is called from create). This is not great in the case of success/error situations because you want different behaviour for each scenario - so override the filenames through the :action attribute.