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.
as_xml
and as_json
in the Model
Your easiest option is to override as_xml
and as_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 as_json
/ as_xml
We open the article.rb
model and add this method:
1 2 3 |
|
This method relies on the ActiveRecord::Base
implementation of as_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 as_xml
Using a Whitelist
Using a whitelist is more secure but takes more maintenance. Create a as_json
method that uses the :only
parameter:
1 2 3 |
|
And, again, you can use the same syntax for as_xml
.
Reducing Redundancy
Using either approach you should not list the visible/invisible attributes in both as_xml
and as_json
.
1 2 3 4 5 6 7 8 9 |
|
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 as_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 as_json
Method
Now we’re ready to actually implement the as_json
in the decorator. We open app/decorators/article_decorator.rb
and find this frame:
1 2 3 |
|
Then we add a as_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 as_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
as_json
method in theArticle
model so only thetitle
is returned. - Use a blacklist constant to generate
as_json
andas_xml
methods inArticle
so 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
as_xml
method in the decorator which outputs only thetitle
andbody
attributes. - Add switching to your
as_xml
so:- when the
context
is:admin
, all attributes are output - when the context is
:trusted
, everything except the timestamps are output - when the context is empty, only the
title
andbody
are output.
- when the
References
- Rails Serialization: http://api.rubyonrails.org/classes/ActiveRecord/Serialization.html
- Rails
as_json
API: http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html - Draper decorators: https://github.com/jcasimir/draper