#409 Active Model Serializers
The ActiveModel::Serializers gem can help you build JSON APIs through serializer objects. This provides a dedicated place to fully customize the JSON output.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
Hi Ryan,
Thanks for this tutorial. I certainly prefer Active Model Serializers to RABL or Jbuilder. I also like the default rails'
as_json
method.I have a comment regarding that. You have mentioned in several tutorials, that "we can get some basic json api by overriding the
as_json
method".While this is true, it's also not recommended. Overriding as_json has a very well known downside: it doesn't respect the overriden
as_json
method in included structures. For example:Article has_many :comments
.as_json
inComment
andArticle
. On the latter, you add a:include => :comments
clause.render :json => @article
in your controller, the article will include the comments, but they will not have the format that you specified inComment#as_json
. Instead, they will come with the "Rails default format".The reason this happens is because of the way Rails is built internally. In some edge cases, reading the structure provided by
as_json
would provoke infinite loops.The recommended way to solve this issue is using an external lib, like the ones you mentioned. But if you don't want to, or can't do it, then your next best thing is to overriding
serializable_hash
instead ofas_json
. Both methods work very similarly,serializable_hash
just happens before in the call stack. Usually replacingdef as_json
bydef serializable_hash
in all models is enough to fix the nested dependencies issue.Thanks, Nice Job
Thanks Enrique !
if you use the helpers like content_tag and use
:data => { :articles => @articles }
they will be automatically converted to json and escaped correctly
Thanks for the tips !
Really nice !
Ok, so after watching this screencast I have two questions.
Why doesn't Ryan ever seem to use the respond_to syntax he covered way back in Episode 224
All the Railscasts episodes I have seen that cover Json always focus on customizing a response, but as a person still learning, I have never figured out what to do with a Json response. I also can't seem to find any good example apps that cover this. So now I know at least three ways to generate a Json response and still no idea of how to "consume" that response on the other end. This might be completely obvious to an experienced programmer, but not to me.
as a example u can watch previous cast about ember, frameworks such as ember use json as data source.
other example is building API interface, as a real example you look at shopify.com api interface, its provided by json
Rails can be used as more than a full application framework but also to serve JSON apis to be consumed by different "views" that might not be even in the same app.
These other different "views" could be other Rails apps, Clientside javascript frameworks within your own app, iOS/Android apps, etc.
To consume APIs from your Rails app you would use something like ActiveResource http://railscasts.com/episodes/94-activeresource-basics, https://github.com/rails/activeresource
THX for ressource
I literally just merged
respond_to
support yesterday, so it wasn't there when he reviewed.Also,
respond_to
isn't all that great, many of us don't actually use it.Can you expound a bit as to why
respond_to
isn't all that great?Nice I need to learn this Episode
THX
I don't see the point when i would ever use this over RABL or Jbuilder. It looks to me like a way if you need to hack out something quickly but when it gets more complex would switch to one of the both mentioned above.
I agree. It seems simpler and cleaner to stick with RABL since it's just another view type. Sticking with a view layer, whether viewed by a browser or "viewed" by an API, is just more intuitive to me. Plus, passing around a project built with RABL is going to be a lot easier to understand by a random 3rd party.
Maybe, like you said, this is better for knocking something out quickly or if it's a small project you work on alone. It's going to get really hackey feeling after 5+ controllers each running their own tests...
+1
Oh Hey James! Haha. I tend to agree by the way, although lately I have my doubts on JBuilder's performance. But I might be hallucinating. Need to benchmark.
I have been writing backbonejs apps on top of rails for two years. The first company I worked for used JBuilder, and the company I work for now uses RABL. Both are neat, but I have yet to find an easy way to do caching with them. Last year I stumbled on this article http://www.broadcastingadam.com/2012/07/advanced_caching_part_6-fast_json_apis/ and now all of my personal projects are ActiveModel::Serializers. Super easy way to cache json responses.
RABL does support caching in Rails.
https://github.com/nesquena/rabl/wiki/Caching-in-RABL
I'm new to RABL so I haven't tried out the caching yet.
Looks pretty simple ... does it not work?
So far I have really liked RABL and for me I like the view approach better.
At this moment (and this will change), look at the first five issues for RABL, four of them dealing with Caching.
https://github.com/nesquena/rabl/issues
My first comment seemed a little harsh after I read it. RABL is open source, and I am so grateful for people who take time out of there lives to contribute to open software.
What the comment should have said is.
"If you have performance concerns, or concerns of any type, it is always a good idea to take a peak at the issues page and make an informed decision."
Also, please note, I have never done any API versioning with ActiveModel::Serializers, never even looked up how to do it. And I can imagine that this is trickier than a view based approach.
However, I would also say, API versions don't (shouldn't) change very often, and performance is always a concern. You should do some research, benchmark all three json serializers, and see which one is best for you. Your experiences may be different than mine.
RABL gets pretty cryptic as soon as you start returning complex JSON which is why I moved to jbuilder and while it served me well so far, AMS seems to offer a better structure and I plan to use it for my next project.
Rails is built on conventions. AMS is built on conventions. RABL and JBuilder are built on customizing every last detail of the response.
Why not just use a Draper decorator with an "#as_json" method?
I think it is much simpler than having separate serializers and decorators.
Hi there! I maintain both AMS and Draper, so maybe I can give you some insight. ;)
Draper is about general decoration, AMS is about serializing to JSON. I know several people that use both together: Draper for adding presentation-only concerns, and then AMS to actually do the conversion.
It's arguable that serialization and view-specific logic are two different concerns, and require two different objects.
Also, if you use Draper's
#to_json
, you miss out on the conventions that AMS provides.This article surprised me with a new use model. I see the value in the various methods for providing json for views, but I am building sparse yet complex data structures from the database to perform a diff, i.e. what changed between before and after a process ran.
Serializers seem like an ideal way to produce these structures, especially when it comes to writing exhaustive tests.
Thanks for a completely new idea, Ryan!
In case anyone is interested, this is a really good talk done by Yehuda Katz (one of the authors of this gem) discussing rails APIs in general and active_model_serializers specifically. He makes a lot of good arguments toward this approach, one being convention over configuration.
http://www.youtube.com/watch?v=UlMpIHH1K5s
This is awesome! I've been using an early version of RABL in the past, and it seemed a bit hackish. I did not like the RABL syntax, and there were quite a lot of corner cases where it would not work as expected.
active_model_serializers looks much more stream-lined
thank you, Ryan!
Thanks Ryan.
Could someone elaborate on 'view_context' for me and why it doesn't need to hit the db to lookup the current user? Is this ActionView's context? Does it then have to run through the whole ActionView layer? I feel that this could be made more explicit or simplified.
Thanks.
view_context
is a method onActionController::Metal
(iirc) that builds a new 'view context object.' This is usually an instance ofActionView::Base
, but is override-able. Every time you call it, it generates a new one, because caching is hard, basically.An alternative is to use the Representer pattern outside of the view context. There are some implementations such as https://github.com/apotonick/representable.
I'm probably missing the obvious, but I'm curious why you would place the JSON inside a tag in the HTML. Why wouldn't a user who is interested in the JSON just append .json to the URL?
Thanks.
You may use this approach to store data (here a whole JSON object) that will be used / processed through Javascript client-side.
Can you point me to any resources on how/why/when to do this?
This way you can save a request to server by just reading the data included in DOM.
I am pretty new to json api's and have a beginner question. So in this video Ryan put's the json data in an html data attribute as opposed to using an ajax request to get the json data.
So let's say I have a
respond_to
block for html and json. When the page loads I make an ajax get request to load the data from the server. If I use a get request to retrieve the data, am I really making 2 calls to the database? The first triggered by the html format of the index action and the 2nd triggered by the get request?In Ryan's example, storing the data in the DOM and also retrieving it using AJAX, it would incur multiple database calls. But Ryan was simply demonstrating how you could use the serializers to store data in the DOM. In the real world, you likely wouldn't need both on the same page.
Thanks Ryan for another great episode :)
Word of warning
When using the
active_model_serializers
gem make sure you override the options in the app controller as this gem will override all JSON rendering not just the ActiveModel you're looking to serialise. So use something like:Without the above you will have a root node in all of your JSON rendering possibly breaking various client side javascript apps and WebService clients that consume the JSON.
To be honest, to make APIs rails is not the best options, I prefer to use goliath, https://github.com/postrank-labs/goliath
checkout the performance test (http://jgwmaxwell.com/accelerating-ruby-apis/ ):
Rails 531 request/s
Sinatra 576 request/s
Sinatra::Synchrony 1692 request/s
Goliath 1924 request/s
Cramp 3516 request/s
node.js 3100 request/s
Ryan,
Here's a suggestion for your railscasts that deal with JSON responses. Make the server reaponse automatically pretty-print the JSON so that it's easier to visually follow.
I've found this neat rack middleware trick:
http://stackoverflow.com/a/13029297/1148455
What if you wanted to handle the API on a subdomain?
How unfortunate that ActiveModelSerializers are nothing more than views without templates. If they were coupled with deserializers, that would be a real difference. Recently I'm trying Roar which is basically the same thing, but works in both directions.
Is the inaccessible current_user problem still true ?
the Purpose section of AMS readme says
"Serializers know about both a model and the current_user" so...
(installing the gem now to try it out though !)
Thanks Ryan !
I can't seem to be able to access "current_user" in the Serializer.
Error:
undefined local variable or method `current_user' for #PostSerializer:0x5bcd9af2
The current_user is available in my controllers and views. (jRuby on Rails with Devise)
Does anyone have an idea why this might be?
Seems that you've answered my question ... Thanks !
Now you can watch this RailsCast for the answer ;)
Just needed to add the version in my Gemfile
before:
gem "active_model_serializers"
after:
gem "active_model_serializers", "~> 0.8.0"
Thanks
I struggled a bit, while converting my rails-api app to use ActiveModelSerializer, on how to modify my specs to check for the JSON coming back (without doing something ugly like hard coding them).
Thanks to Ryan for this RailsCast - it got me to take a look at active_model_serializer, as shown at the end of the asciicast, and using that in my tests simplified things.
You can see my conversion to active_model_serializer in this commit, including my request spec changes.
The helper_method part does not work using Rails API's controller:
This is because ActionController::API#helper_method is simply a stub that does nothing.
I just needed to include AbstractController::Helpers in my ApplicationController to make it work.
Hi Ryan,
Great episode once again. I have been using this a bit, but haven't been able to sort my associated objects directly from the Serializer. Does anyone know if this option is available? Or should I add the default sorting to the association (in the model file)?
Thank you!
Ryan, with the advent of Rails4 do you still recommend this as the the best method for implementing a json api? Do you believe certain intended consumption mediums would respond to different techniques? iPhone client full RESTful actions vs simple read consumtion? Thanks in advance!
For version 0.9 and above, the json_for should change from:
target.active_model_serializer.new(target, options).to_json
to
ActiveModel::Serializer.serializer_for(target).new(target, options).to_json
thank you!
Ryan, you know the joke. google -> stackoverflow -> railscasts -> gem
Thank you :-)
Hi Ryan, there is some problem with images no longer available in the ASCIIcast tab.
Sound good
Cool tutorial Ryan! It would be nice to specify that it's applicable to version 0.8 though.
This episode has been updated to Rails 5 as a blog post ActiveModelSerializer Basics in Rails 5
Hi, very helpful! I have this error:
undefined method
read_attribute_for_serialization
What is? Thanks
Nice stuff
I look at the first five issues for RABL, four of them dealing with Caching though. Thanks for sharing such a nice post.
Thanks for this