Rails: respond_with custom object - ruby-on-rails

respond_with is acatually meant to use with ActiveModel's instances. I tried to use it with OpenStruct's instance, but it raises an error.
Is that ever possible to use respond_with with custom objects?
class CryptController < ApplicationController
respond_to :json
def my_action
respond_with OpenStruct.new(foo: 'foo', bar: 'bar')
end
# ...
end
Raises: **undefined method persisted?' for nil:NilClass**
ruby-2.1.4#rails4/gems/actionpack-4.2.5.1/lib/action_dispatch/routing/polymorphic_routes.rb:298:inhandle_list'
/home/workstat/.rvm/gems/ruby-2.1.4#rails4/gems/actionpack-4.2.5.1/lib/action_dispatch/routing/polymorphic_routes.rb:206:in polymorphic_method'
/home/workstat/.rvm/gems/ruby-2.1.4#rails4/gems/actionpack-4.2.5.1/lib/action_dispatch/routing/polymorphic_routes.rb:114:inpolymorphic_url'

respond_with is a helper method that exposes a resource to mime requests.
From the documentation
respond_with(#user)
for the create action, is equivalent (assuming respond_to :xml in the example) to:
respond_to do |format|
if #user.save
format.html { redirect_to(#user) }
format.xml { render xml: #user, status: :created, location: #user }
else
format.html { render action: "new" }
format.xml { render xml: #user.errors, status: :unprocessable_entity }
end
end
end
The precise equivalent is dependent upon the controller action.
The key takeaway is that respond_with takes a #instance variable as an argument and first attempts to redirect to the corresponding html view. Failing that, it renders an xml response, in the case above.
You are passing in an ostruct, which doesn't correspond to an instance of your model. In this case, respond_with doesn't know where to redirect to in your views and doesn't have an instance from which to render a mime response.
See this RailsCast and this blogpost from José Valim.
A note: The error undefined method persisted? is generated by Devise and probably because it can't find a route.

Related

Rails, respond_to blocks and |format|

Rails scaffold generated the following:
respond_to do |format|
if #student.save
format.html { redirect_to #student, notice => 'Student was successfully created.' }
format.json { render :show, status: :created, location: #student }
else
format.html { render :new }
format.json { render json: #student.errors, status: :unprocessable_entity }
end
end
After reading this I understand how the respond_to is working (sort of), but I don't get what format is doing. Shouldn't it be either format.html or format.json and not both? What are these two lines actually doing?
format.html { render :new }
format.json { render json: #student.errors, status: :unprocessable_entity }
Is there an implied if in there? Is it something like
if (format == html) {}
if (format == json) {}
Side note: Why does update require the respond_to block while show will handle /students/1.json or /students/1 without any logic at all?
format is a local variable that respond_to yields. When you do format.html {} you are actually registering a callback block for a format.
Rails goes through the registered formats and tries to find a compatible format to the MIME type in the request. If there is no handler it will raise an error.
This could be explained as something like using syntactic sugar on top of a case statement (the Ruby equivalent of a switch statement). But the analogy is not completely accurate since Rails does a bit of work in matching the request type.
Also the code inside your block is not executed when the format.html block is registered (as it would be if it was just a conditional statement) but rather when respond_to finishes or not at all if you are using for example E-Tag caching.
Why does update require the respond_to block while show will handle
/students/1.json or /students/1 without any logic at all?
Rails handles many actions by using a convention over configuration approach and guessing the intent of the action.
def PostsController < ApplicationController
def index
# rails auto-magically fills in the controller with something
# like this
#posts = Post.all
respond_to do |format|
format.html { render :index }
format.json { render json: #posts }
end
end
def show
# convention over configuration is awesome!
#post = Post.find(params[:id])
respond_to do |format|
format.html { render :show }
format.json { render json: #post }
end
end
def new
#post = Post.new
render :new
end
def edit
#post = Post.find(params[:id])
render :edit
end
end
Rails assumes that there is a resource with the same name as the controller and auto-magically fills in the controller action. It also assumes there is a view in app/views/posts/(:action).html.[erb|haml|slim|jbuilder]. This is known as implicit rendering.
The comments show roughly what action rails attempts.
It does not fill in actions which operate on data (create, update, destroy) since the actual implementation can vary greatly and it's hard to make useful guesses.
Well, it depends on the format of the request. If a request demands HTML from the server, format.html block will be executed, and in the same way, if a request demands JSON format, format.json will be executed.
Rails will automatically(read: magically) handle the if (format == html) part for you. All you have to do is fill in the blanks. Same way, you can write a block for XML starting with format.xml.
And for the side note, I think you have said it otherwise. update method doesn't require respond_to block, while show requires. And the reason is very simple: update method is there to update the Model, and then, redirect you to somewhere, while show will always return you something. In your case, /students/1 will return you the first student created in the database, and the response will be HTML, while /students/1.json will return you the same result, but response will be JSON this time.
Well you could very well replace 'format' with 'foo' or 'banana' or whatever you want. It is just the variable name in this case because the variable that is sent to your block by respond_to is passing along the format as requested by the incoming http request's Accept header.
Sometimes you'll see 422 "Unacceptable" errors in your logs because you are receiving a request with an Accept header that does not request a mime type your app knows about.
As it is, your callers should be using a browser or be a JSON consumer sending the proper headers to receive responses from the boilerplate.

What does respond_to do when called without a block?

I understand how respond_to works when it's called with something like this:
def index
#users = User.all
respond_to do |format|
format.html
format.json { render json: #users }
end
end
But I've seen some apps which pass respond_to a list of symbols, outside of the controller methods, e.g.:
class UsersController < ApplicationController
respond_to :html, :json
def index
# blah blah bah
end
end
What does this do? I've been playing around with it in one of my controllers and I can't figure out what difference it makes.
For a given controller action, #respond_with generates an appropriate response based on the mime-type requested by the client.
If the method is called with just a resource, as in this example -
class PeopleController < ApplicationController
respond_to :html, :xml, :json
def index
#people = Person.all
respond_with #people
end
end
then the mime-type of the response is typically selected based on the request's Accept header and the set of available formats declared by previous calls to the controller's class method respond_to. Alternatively the mime-type can be selected by explicitly setting request.format in the controller.
If an acceptable format is not identified, the application returns a '406 - not acceptable' status. Otherwise, the default response is to render a template named after the current action and the selected format, e.g. index.html.erb. If no template is available, the behavior depends on the selected format:
for an html response - if the request method is get, an exception is raised but for other requests such as post the response depends on whether the resource has any validation errors (i.e. assuming that an attempt has been made to save the resource, e.g. by a create action) -
If there are no errors, i.e. the resource was saved successfully, the response redirect's to the resource i.e. its show action.
If there are validation errors, the response renders a default action, which is :new for a post request or :edit for patch or put.
Thus an example like this -
respond_to :html, :xml
def create
#user = User.new(params[:user])
flash[:notice] = 'User was successfully created.' if #user.save
respond_with(#user)
end
is equivalent, in the absence of create.html.erb, to -
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
flash[:notice] = 'User was successfully created.'
format.html { redirect_to(#user) }
format.xml { render xml: #user }
else
format.html { render action: "new" }
format.xml { render xml: #user }
end
end
end
for a javascript request - if the template isn't found, an exception is raised.
for other requests - i.e. data formats such as xml, json, csv etc, if the resource passed to respond_with responds to to_, the method attempts to render the resource in the requested format directly, e.g. for an xml request, the response is equivalent to calling render xml: resource.

Rails: Pass Parameters to New Controller during 'redirect_to'

I'm using Ryan Bates' Rails Cast on Wicked Wizard Forms to create a multi-step form. I don't have a current_user method defined (not using an authentication gem) - so, I'm trying to pass the user.id parameter during the redirect_to - unfortunately, I can't seem to get it to work. Any help is appreciated!
My user controller create method
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to controller: 'user_steps', id: 'user.id' }
#format.html { redirect_to #user, notice: 'User was successfully created.' }#
format.json { render json: #user, status: :created, location: #user }
else
format.html { render action: "new" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
The user_steps controller that to which I am redirecting:
class UserStepsController < ApplicationController
include Wicked::Wizard
steps :gender, :items, :brands, :final
def show
render_wizard
end
end
You should pass it through as a param, ideally, which the redirect_to method will do for you if you use a proper route path.
Example:
redirect_to(user_steps_path(#user))
In your case, if you don't have a named route, you might do this:
redirect_to(controller: 'user_steps', id: #user.to_param)
In URLs it's advisable to use the to_param method. id is used for database queries.
What you're passing in is literally 'user.id' as a parameter. It will not be evaluated.

Respond_with templates and status codes

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.

Add JSON support to Rails app

I am experimenting with Rails and was wondering what's needed to allow/add support for JSON requests?
I have a vanilla installation of Rails 2.3.5 and the default scaffolding seem to provide support for HTML & XML requests but not JSON.
class EventsController < ApplicationController
# GET /events
# GET /events.xml
def index
#events = Event.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #events }
end
end
# GET /events/1
# GET /events/1.xml
def show
#event = Event.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #event }
end
end
...
I'm new to this but it would appear as though i would need to add a format line in each method along the lines of:
format.js { render :js => #event.json }
couldn't this be done automatically? perhaps there's a template somewhere i need to update...or a flag i can set? Or perhaps, and most likely, I've missed the boat entirely?!?
You do:
format.json {render :json=>#event}
That will render the default activerecord JSON for the model
The option of ease of use is that you can write a private method which takes the format object and an object to render and then, based on the format, renders different things. Example:
class MyController<ApplicationController
def show
#event=Event.find(params[:id])
respond_to do {|format| myRenderer(format,#event)}
end
...
private
def myRenderer(fmt,obj)
fmt.json {render :json=>obj}
fmt.html
fmt.xml {render :xml=>obj}
end

Resources