Rails RABL respond_with error template - ruby-on-rails

Using RABL in Rails 3.2.x, given the following controller action:
respond_to :html, :json
def create
#foo = Foo.create(params[:foo])
respond_with #foo
end
Assuming the validation fails, how do you get respond_with to use a RABL template instead of the standard JSON hash of errors -- IE. I would like other model attributes besides the validation error message sent back along with the request.
Suggestions?

I found this one out the hard way. You should create a custom responder for your application controller, or at least your individual response. See Three reasons to love ActionController::Responder for more details.
My solution:
# app/responders/api_responder.rb
class ApiResponder < ActionController::Responder
def to_format
case
when has_errors?
controller.response.status = :unprocessable_entity
when post?
controller.response.status = :created
end
default_render
rescue ActionView::MissingTemplate => e
api_behavior(e)
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
#...
self.responder = ApiResponder
#...
end
You could also use respond_with #foo, responder: ApiResponder instead.
Thanks to haxney for sending me in the right direction.

I guess, you need to remove the respond_to call at the top of the controller and remove the respond_with call within the action to get rabl render your rabl template.
Just add a respond_to block at the end of each action where you don't need RABL.

Related

Dynamically set current_object and avoid using before_filter

Suppose we have a rails API. In many controllers methods I need to set my current_object thanks to params from the request. I can then set a before_action like:
def set_current_object
if My_object.exists? params[:id]
#current_object = My_object.find params[:id]
else
render json: {error: 'Object not found'}.to_json, status:404
end
end
This is ok. But I would like to set current_object dynamically in my controllers methods. Imagine I have a show method in one controller where I need to use my current_object like:
def show
render json: {object_name: current_object.name}.to_json, status: 200
end
current_object would be a helper method like:
def current_object
if My_object.exists? params[:id]
return My_object.find params[:id]
else
render json: {error: 'Object not found'}.to_json, status:404
end
end
Then, if My_object.exists? params[:id] is false I would like to send a 404 and to stop my controller method. Like written here, it is obviously not working. Any suggestion?
You're on the right track. Typically you would implement this sort of "lazy-loading" as a method which memoizes its return value using the ||= idiom.
You simply need to modify your current_object helper so that it can trigger a 404 error when it's unable to return a valid value. Typically you would do this by raising a recognizable exception such as an ActiveRecord::RecordNotFound, and handling this in your controller with a rescue_from clause.
class ApplicationController
def current_object
if My_object.exists? params[:id]
# memozie the value so subsequent calls don't hit the database
#current_object ||= My_object.find params[:id]
else
raise ActiveRecord::RecordNotFound
end
end
rescue_from ActiveRecord::RecordNotFound with: :show_404
def show_404
render json: {error: 'Object not found'}.to_json, status:404
end
end
Now, because you're following a pretty standard Rails convention of handling ActiveRecord::RecordNotFound at the top-level of your controller hierarchy, you can now clean up your current_object method considerably. Instead of checking for the presence of a record, just try to find the record by id. If it doesn't exist, ActiveRecord will automatically raise the exception for you. In fact, your entire current_object method should be a single line of code:
class ApplicationController
def current_object
#current_object ||= My_object.find(params[:id])
end
rescue_from ActiveRecord::RecordNotFound with: :show_404
def show_404
render json: {error: 'Object not found'}.to_json, status:404
end
end
Assuming My_object is a model, if you simply use find, then a params[:id] that doesn't exist in the database will raise an ActiveRecord::RecordNotFound error, and Rails' ActionController::Base will catch the exception and render a 404 by default:
def current_object
My_object.find params[:id]
end

How to render json for all actions from the after_action filter in ApplicationController?

Is it possible to create an after_filter method in the Rails ApplicationController that runs on every action and renders to JSON? I'm scaffolding out an API, and I'd like to render output to JSON for every action in the controller.
clients_controller.rb
def index
#response = Client.all
end
application_controller.rb
...
after_action :render_json
def render_json
render json: #response
end
The after_action is never executed, and the code aborts with:
Template is missing. Missing template clients/index, ...
If the render json: #response is moved into the controller action, it works correctly.
Is there a filter that will allow me to DRY up the controllers and move the render calls to the base controller?
You can't render after_action/ after_filter. The callback after_action is for doing stuff after rendering. So rendering in after_action is too late.
But your exception is just because you miss the JSON template. I recommend using RABL (which offers a lot of flexibility to your JSON responses and there is also a Railscast about it). Then your controller could look like:
class ClientsController < ApplicationController
def index
#clients = Client.all
end
def show
#client = Client.find params[:id]
end
end
And don't forget to create your rabl templates.
e.g. clients/index.rabl:
collection #clients, :object_root => false
attributes :id
node(:fancy_client_name) { |attribute| attribute.client_method_generating_a_fancy_name }
But in the case you still want to be more declarative you can take advantage of the ActionController::MimeResponds.respond_to like:
class ClientsController < ApplicationController
respond_to :json, :html
def index
#clients = Client.all
respond_with(#clients)
end
def show
#client = Client.find params[:id]
respond_with(#client)
end
end
Btw. beware if you put code in an after_action, this will delay the whole request.

How to write this rails library requiring respond_to and render?

I want to write this library that responds to some json and html request. In the controller's action, I will call MyLib.search(params). Then in "module Something; class MyLib", I have "def search(params); respond_to ... render :json ...; end". If I try to use this library, I get "NoMethodError (undefined method `respond_to' ...".
How should I write this, so that I get respond_to and render in scope?
Perhaps a mixin would serve you better, something like this:
module Something
def search # params will be in scope so no need to pass it
#...
respond_to do |format|
format.json ...
end
end
end
and then in the controller:
class SomeController < ApplicationController
include Something
def whatever
# ...
search
end
end

How do I use respond_with with custom classes in Rails 3?

I am making a JSON API with Rails and it seemed to work fine except for when I use respond_with custom classes (not an ActiveRecord inherited one).
Here is my class:
class JsonResponse
def initialize(data, status)
#data = data
#status = status
end
def as_json(options={})
{
:data => #data,
:status => #status
}
end
end
which is a simple response wrapper. When I try doing this:
def create
unless(Match.find_by_user_id(params[:user_id]))
Match.create(:user_id => params[:user_id])
end
time_response = JsonResponse.new("5", "success")
respond_with(time_response)
end
I get this error:
NoMethodError (undefined method `model_name' for JsonResponse:Class):
app/controllers/matches_controller.rb:9:in `create'
Any ideas? respond_with is driving me crazy.
Your class should response to to_json method
Obviously set :location option in respond_with method. Rails try to create restful route from the object you pass to the method, but because your object is not resource, the error is raised.
I am not sure if this helps but I do not see respond_to...
respond_with works together with respond_to...
respond_to :html, :xml, :json
This can be defined on Controller level
example:
class UsersController < ApplicationController::Base
respond_to :html, :xml, :json
def index
respond_with(#users = User.all)
end
def create
#user = User.create(params[:user])
respond_with(#user, :location => users_url)
end
end
and then you can define your json template... don't know if you leave the json template empty if it takes your "JSONResponse" class...
just a thought...

Best way to handle 404 in Rails3 controllers with a DataMapper get

It's very simple, I want to handle a normal [show] request with a call to DataMapper like I did in Merb.
With ActiveRecord I could have done this:
class PostsController
def show
#post = Post.get(params[:id])
#comments = #post.comments unless #post.nil?
end
end
and it handles the 404 by catching the resource's exceptions.
DataMapper instead doesn't do this automatically so right now I'm solving it with this solution:
[moved in the answers]
It is possible to tell the controller to halt inside the not_found function?
I like to use exception throwing, and then use ActionController's rescue_from.
Example:
class ApplicationController < ActionController::Base
rescue_from DataMapper::ObjectNotFoundError, :with => :not_found
def not_found
render file => "public/404.html", status => 404, layout => false
end
end
class PostsController
def show
#post = Post.get!(params[:id]) # This will throw an DataMapper::ObjectNotFoundError if it can't be found
#comments = #post.comments
end
end
Done 'the old Merb way':
class ApplicationController
def not_found
render file: "public/404.html", status: 404, layout: false
end
end
class PostsController
def show
#post = Post.get(params[:id])
not_found; return false if #post.nil?
#comments = #post.comments
end
end
again: It is possible to tell the controller to halt inside the not_found function instead of explicitly calling 'return false' in the show action?
edit: thanx to Francois that found a better solution:
class PostsController
def show
#post = Post.get(params[:id])
return not_found if #post.nil?
#comments = #post.comments
end
end
As DM documentation says, you can use #get!

Resources