I have a rails site which is mainly REST based and I want to add JSON API support.
For a clean code base, should I add this support within the existing controllers or create new controllers which only handle this API methods and then move all the common code to models/helpers?
I used both techniques: writing API logic in the same controllers and making separate controllers for the API request.
If it's only an API, a small app used only by you, go with the default controller-model relation that rails offers. The code will be quite clean. Your routes file will be also clean.
If you have a website and you want to build an API along, do it separately. I've build one alongside the existing controllers and the code was too messy. I refactored the code several times, but I still didn't like it (it's also a matter of personal taste).
Another solution is to make controllers with a prefix. Ex: ApiUsersController
. This would make your routes.rb
file look ugly because you would have to manually specify the route to match the corresponding controller method.
The working solution for me was to move all the API logic in separate controllers under an API namespace. Namespaces also let you do API versioning. So, for example, your routes will be:
GET /api/v1/users.json
POST /api/v1/users.json
And then, in the future you can create another API version, let's say v2
, without breaking existing applications that use the older version of the API.
You can find more about namespaces here: http://guides.rubyonrails.org/routing.html#controller-namespaces-and-routing
An awesome tutorial about REST-full API with versioning: http://railscasts.com/episodes/350-rest-api-versioning?view=asciicast
Rails controller generators implement JSON responses by default.
For example, if you have this method:
class UsersController < ApplicationController
def index
@users = User.all
end
end
You could add JSON response like so
class UsersController < Application Controller
def index
respond_to do |format|
format.html
format.js { render :json => @users }
end
end
end
Now, you have two responses for /users
http://someapp.com/users
http://someapp.com/users.json
You can add another one very easily; e.g.,
format.xml { render :xml => @users }
Now your app will respond to http://someapp.com/users.xml
Chances are you won't want to output all the fields of a table in your json. For that, look to rails/jbuilder
. It lets you create JSON structures with a builder-style DSL.
An example from the jbuilder README
Jbuilder.encode do |json|
json.content format_content(@message.content)
json.(@message, :created_at, :updated_at)
json.author do
json.name @message.creator.name.familiar
json.email_address @message.creator.email_address_with_name
json.url url_for(@message.creator, format: :json)
end
if current_user.admin?
json.visitors calculate_visitors(@message)
end
json.comments @message.comments, :content, :created_at
json.attachments @message.attachments do |attachment|
json.filename attachment.filename
json.url url_for(attachment)
end
end
Produces the following output:
{
"content": "<p>This is <i>serious</i> monkey business",
"created_at": "2011-10-29T20:45:28-05:00",
"updated_at": "2011-10-29T20:45:28-05:00",
"author": {
"name": "David H.",
"email_address": "'David Heinemeier Hansson' <[email protected]>",
"url": "http://example.com/users/1-david.json"
},
"visitors": 15,
"comments": [
{ "content": "Hello everyone!", "created_at": "2011-10-29T20:45:28-05:00" },
{ "content": "To you my good sir!", "created_at": "2011-10-29T20:47:28-05:00" }
],
"attachments": [
{ "filename": "forecast.xls", "url": "http://example.com/downloads/forecast.xls" },
{ "filename": "presentation.pdf", "url": "http://example.com/downloads/presentation.pdf" }
]
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With