I have a model that I only want to ever return JSON, regardless of any conneg or file-like extensions on the URI (e.g. /app/model.json). Google-fu is coming up short and this can't be that difficult.
In your controllers you simple have to create a respond_to block that only responds to JSON:
respond_to do |format|
format.json { render :json => #model }
end
This is actually a decision made by the controller, not because there is/is not a model or view present. In your controller you can:
render json: #your_model
However you will quickly find that the default implementation of to_json (which is what is used internally, above) can be annoying hard to do exactly what you want. When you reach that point you can use RABL to create views that massage the JSON from your model(s) exactly as you want.
Related
def new
#post = Post.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #post }
end
end
def create
#post = Post.new(params[:post])
#something else
end
Here since when we are actually creating a new post we call the method create where Page.new(params[:page]) is used, method new should only be used to call the view new.html.erb. So why we still need an instance variable #post in new method here?
You don't need any instance variables in new or any other action, but by default, Rails's scaffolding uses them. There are some minor benefits that come with using instance variables, and there's some convention around them, but frankly it's sloppy code and should not be the default. In a proper MVC framework, the controller's instance variables wouldn't even be visible to the view object.
I prefer to be explicit, use local variables, and pass them to the view as locals:
def new
post = Post.new
respond_to do |format|
format.html { render locals: { post: post } }
format.json { render json: post }
end
end
This is more explicit, and makes your intent clear. The view becomes more flexible with locals, since you don't have to worry about setting instance variables before rendering a partial from inside another view. It properly encapsulates the data and doesn't expose your post outside the action.
If you're trying to quickly prototype/spike something, you might save a few characters by using instance variables, but it's not clean code.
If you did not create a new instance variable then you wouldn't have a model to hold the data in which the model is supposed to be managing - not to mention you would be adding a lot more boiler plate for your forms instead of using the form helpers that take a model. Without using the model you're not using the MVC framework fully as intended.
Now that all being said, it's by no means required to do anything it's just following the MVC structure that Rails is built on top of. No pattern is required, there are always other solutions, it's just that the accepted method of performing this action involves a model - albeit and empty one.
Finally, I don't know when or why you'd want to respond with a new route with JSON since the new route is generally used for displaying a form to create an object while the create function is the one used to actually create a new instance of the model.
I have Vacancies controller and I need to pass #vacancies to json and also render another layout. The following code does not work (json is not passed however I have "wide" layout). If I remove format.html { render layout: "wide"} } json passes correctly. How to combine these two things?
class VacanciesController < ApplicationController
respond_to :html, :json
...
def index
#vacancies = Vacancy.all
respond_with(#vacancies) do |format|
format.html { render layout: "wide"} }
format.json { render json: #vacancies }
end
end
...
You can't call render twice, that's problem #1. You also can't send two responses to a single request.
There is also no purpose in rendering HTML (which means a fresh page load) and sending JSON (which is for AJAX requests, ie requests that don't reload the page) at the same time. It isn't possible, but it also would be pointless even if it was possible.
If you want to tell a request to use a specific layout, you can pass the layout option to a render call. However, a render call does not take a data object as the first argument, it takes a view name, or only an options hash. So to call this correctly you should use:
render :index, :layout => 'example'
I expect that will make your HTML views show up correctly.
Please understand however, the layout option is only useful for HTML responses, not JSON responses. Layout is telling your render call what outer HTML to wrap around the view your action is calling, and if you don't specify it uses 'application.html'
To help you understand one more thing: your respond block is telling the computer how to respond to different kinds of requests. It's like a switchboard. If you wrote it with if/else statements it might look like this:
if request_type == 'html'
render :index, :layout => 'wide'
elsif request_type == 'json'
render :json => #vacancies
else
raise raise ActionController::UnknownFormat
end
So, with your respond_with block, if you fix your html render call, and assuming you're developing on localhost, if you enter the following URL in your browser and hit enter...
http://localhost:3000/vacancies
That would be making an HTML format GET request, which will load the page with layout: 'wide' but no other data. If you type:
http://localhost:3000/vacancies.json
That will simulate a JSON request, and you'll get just the JSON representation of the #vacancies data.
I hope that helps you solve your problem. If not, please describe what you're trying to accomplish in more detail so I can help you understand how to do it.
PS: one last tip: if you want to specify layouts at the controller level you can just call layout at the top of your controller, like so:
class ExampleController < ApplicationController
layout 'awesome', :only => [:new,:edit]
...
end
This works like any other filter, you can pass :only, or :except, or no options at all.
I'm really confused on the concept of this.
I'm supposed to make API that handles JSON responses. I read this
and other places, they all showed example on how to do it for the show or index aspect of the controller. I understand that, where you outputs all the attributes of the model. But my main question is, if somebody were to create or edit, what do I do with the whole JSON on there?
If all you need to do is pass back a 200 for a successfull create, then don't creating a response.
render :nothing => true
If on the other hand, you added some important information to the object that you are updating that the client will require (If you are not sure, then assume that the client will require it), then you should pass the updated object's attributes back
respond_to do |format|
format.json {render :json => {... }}
end
I'm reading this Railscast: http://railscasts.com/episodes/340-datatables?view=asciicast
Mid-way down the page, Ryan says
The as_json method is triggered behind the scenes by the render_json
call in the controller.
but no more explanation is given. I'm trying to have this class respond with CSV as well, but def as_csv in the class and format.csv { render :csv => in the calling controller does nothing.
So, somehow the class knows when it was initialized by render :json, but I can't figure out how to make it know it was initialized by render :csv. Can someone explain this?
If you pass an object to render :json, the as_json method is called on that object to retrieve a JSON representation of that object. You can overwrite this method so that it returns whatever you want.
This only works specifically for JSON, it's not a general rule that can be applied to all formats. If you'd like to render a CSV representation of some object, you can do it easily enough by using something like:
format.csv do
render :text => object.as_csv
end
and then implementing the as_csv method in the class.
I have a situation where I'm returning json objects to my application which are built from YML files. Because to parse the yml file and return it as json I always have to do something like this:
format.json { render json: YAML.load(render_to_string :file => File.join(Rails.root,'app','views','home','icons.yml.erb'), :layout => false ) }
I would like to make this operation shorter, by creating a custom format that (however) result in a json, so I don't want to create a new mime type.
My idea is to write:
format.myformat
Which will automatically search for myaction.myformat.erb inside views/mycontroller directory, and will automatically parse the yaml file returning it as a json object.
Is this possible? If yes, how can I eventually do this?
Edit 1:
I found an important suggestion in config/mime_types.rb:
Mime::Type.register_alias "text/html", :iphone
So I can alias a mime type, now the biggest problem is: how to define the default render action for a given format, like format.html does?
If I write
format.html
current_action.html.erb will be automatically rendered, how can I choose the correct method to render a custom format?
Edit 2:
I managed to create this code (inside a controller, through some helper methods I built):
def icons
respond_to do |format|
format.extjson { render_to_extjson }
end
end
Is possible to make rails understand that if I write:
def icons
respond_to do |format|
format.extjson
end
end
it has to do:
format.extjson { render_to_extjson }
by default?
You could do something like:
respond_to do |format|
format.html { #foo = Foo.all(:limit => 10) }
format.any(:atom, :rss) { #foo = Foo.all }
end
A longer post going into the guts of the render actions can be found here: http://ryanbigg.com/2009/04/how-rails-works-2-mime-types-respond_to/
While Josh's answer is a valid one, I would rather see your parsing code wrapped up into an object. If you put that object into app/models, it'll be testable and you can always verify any change to the logic with a test suite.
Another upside to this is that you can re-use the format.json call and make you controller that much simpler.
This is not doable at the moment, I read a lot of rails sources and there isn't a way to access that method, so isn't possible to customize it.
I'll write a rails plugin to support this and eventually I'll post it here, but for sure the answer actually is: this can't be done.