Web Services
Encoding and Filtering Data
You are building an API and are rolling with respond_to and respond_with. They are automatically rendering your objects as XML and JSON.
Wait, they are automatically rendering your objects? Everything? Yes! If your models have any sensitive data in them, and they probably do, you’ll need to do some filtering.
to_xml and to_json in the Model
Your easiest option is to override to_xml and to_json in the model. This works in scenarios where you have attributes that should always be hidden.
Let’s look at an Article object, for example:
1 2 3 4 5 | |
In this case, the editor_id attribute is sensitive information. We do not want to expose it to our JSON api.
Overriding to_json / to_xml
We open the article.rb model and add this method:
1 2 3 | |
This method relies on the ActiveRecord::Base implementation of to_json which accepts an :except blacklist of attributes. It can also accept an array of keys:
1 2 3 | |
All of the listed keys will be removed. The exact same syntax can be used for to_xml
Using a Whitelist
Using a whitelist is more secure but takes more maintenance. Create a to_json method that uses the :only parameter:
1 2 3 | |
And, again, you can use the same syntax for to_xml.
Reducing Redundancy
Using either approach you should not list the visible/invisible attributes in both to_xml and to_json.
Option one is to define a constant and reference it twice:
1 2 3 4 5 6 7 8 9 | |
Option two is to use a bit of metaprogramming:
1 2 3 4 5 | |
This works well as long as you want to filter the API globally.
Checking Authorization
More often you want to filter based on authorization rules. For instance, if the current user is an administrator then show them everything. If the current user is just a regular user then show them the filtered list. This is much harder.
You are working with data, which means the logic belongs in the model. But you’re dealing with authorization, which really belongs in the controller. And, at the core, you’re dealing with presentation which goes in the view. It’s tricky!
Using a Decorator
The best way to handle this situation is to use a decorator with the Draper gem.
For this approach to work properly, comment out any work done to override or filter to_json at the model level.
Setup
Add the draper gem to your Gemfile and run bundle.
Generate the Decorator
Then generate the decorator object:
1
| |
Which will create app/decorators/article_decorator.rb
Use the Decorator
Before we look at implementing the actual decorator, let’s set it up for use in ArticlesController. Presume we’re interested in converting a single Article into JSON using the show action.
It should, so far, look like this:
1 2 3 4 5 6 7 8 9 10 | |
Using the decorator is just a one-line change:
1 2 3 | |
The decorator class will handle the lookup with the Article class and wrap the result.
Decorator with Context
But our purpose was to handle the authentication status in the decorator. In Draper, this is called the context. Since we don’t have an authentication system, let’s pass in a symbol representing the current user’s "role", :admin.
1
| |
Within the decorator, that :admin will be stored under the :role key in the context.
Implement the to_json Method
Now we’re ready to actually implement the to_json in the decorator. We open app/decorators/article_decorator.rb and find this frame:
1 2 3 | |
Then we add a to_json method which proxies the call to the wrapped model:
1 2 3 4 5 6 7 | |
You could test that it works in your console:
1 2 3 4 | |
Then, in the to_json, handle the switching based on context:
1 2 3 4 5 6 7 | |
And test it in console:
1 2 3 4 5 6 7 8 | |
Success! When context is blank you see the filtered output. When the context is setup for an admin, you get the full output.
Exercises
Use the Blogger sample application to complete the exercises in this section. See the Setup Instructions for help.
- Implement a filtering
to_jsonmethod in theArticlemodel so only thetitleis returned. - Use a blacklist constant to generate
to_jsonandto_xmlmethods inArticleso they do not display the timestamps. - Setup the Draper gem, create an
ArticleDecorator, and use it in the controller. Verify it works from the console. - Implement a
to_xmlmethod in the decorator which outputs only thetitleandbodyattributes. - Add switching to your
to_xmlso:- when the
contextis:admin, all attributes are output - when the context is
:trusted, everything except the timestamps are output - when the context is empty, only the
titleandbodyare output.
- when the
References
- Rails Serialization: http://api.rubyonrails.org/classes/ActiveRecord/Serialization.html
- Rails
to_jsonAPI: http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html - Draper decorators: https://github.com/jcasimir/draper