mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-23 06:16:50 +00:00
Remove everything, rewrite of AMS starts here
This commit is contained in:
parent
919bb38401
commit
14f51f2ea9
18
.travis.yml
18
.travis.yml
@ -1,18 +0,0 @@
|
|||||||
language: ruby
|
|
||||||
rvm:
|
|
||||||
- 1.9.3
|
|
||||||
- 2.0.0
|
|
||||||
- jruby-19mode
|
|
||||||
- rbx-19mode
|
|
||||||
gemfile:
|
|
||||||
- Gemfile
|
|
||||||
- Gemfile.edge
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- gemfile: Gemfile.edge
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
||||||
campfire:
|
|
||||||
on_success: change
|
|
||||||
rooms:
|
|
||||||
- secure: "TP0fJ4aqXCRD7CaAgaYW7Pa22j4/uLChdBb59ob/sJvHtfi4Zx3I54xWApmp\nZl1KItFGCV8oQZhQl5hAmHJfJ+1gCNeBvIKwY6TsIyTmyDg1KcJUcJDrwYxO\ntAeYI2PvU5PtKMmpnfnwFQMxL+2nfWJWNzboBCDr4YvoFI+rN+A="
|
|
||||||
113
CHANGELOG.md
113
CHANGELOG.md
@ -1,113 +0,0 @@
|
|||||||
# UNRELEASED
|
|
||||||
|
|
||||||
* ActiveModel::Serializable was created it has the shared code between
|
|
||||||
AM::Serializer and AM::ArraySerializer. Basically enable objects to be
|
|
||||||
serializable by implementing an options method to handle the options
|
|
||||||
of the serialization and a serialize method that returns an object to
|
|
||||||
be converted to json by the module. This also removes duplicate code.
|
|
||||||
https://github.com/rails-api/active_model_serializers/commit/6c6bc8872d3b0f040a200854fa5530a775824dbf
|
|
||||||
|
|
||||||
* ActiveModel::Serializer::Caching module was created it enables
|
|
||||||
Serializers to be able to cache to\_json and serialize calls. This
|
|
||||||
also helps removing duplicate code.
|
|
||||||
https://github.com/rails-api/active_model_serializers/commit/3e27110df78696ac48cafd1568f72216f348a188
|
|
||||||
|
|
||||||
* We got rid of the Association.refine method which generated
|
|
||||||
subclasses.
|
|
||||||
https://github.com/rails-api/active_model_serializers/commit/24923722d4f215c7cfcdf553fd16582e28e3801b
|
|
||||||
|
|
||||||
* Associations doesn't know anymore about the source serializer.
|
|
||||||
That didn't make any sense.
|
|
||||||
https://github.com/rails-api/active_model_serializers/commit/2252e8fe6dbf45660c6a35f35e2423792f2c3abf
|
|
||||||
https://github.com/rails-api/active_model_serializers/commit/87eadd09b9a988bc1d9b30d9a501ef7e3fc6bb87
|
|
||||||
https://github.com/rails-api/active_model_serializers/commit/79a6e13e8f7fae2eb4f48e83a9633e74beb6739e
|
|
||||||
|
|
||||||
* Passing options[:hash] is not public API of include!. That was
|
|
||||||
removed.
|
|
||||||
https://github.com/rails-api/active_model_serializers/commit/5cbf9317051002a32c90c3f995b8b2f126f70d0c
|
|
||||||
|
|
||||||
* ActiveModel::Serializer::Associations::Config is now
|
|
||||||
ActiveModel::Serializer::Association but it's an internal
|
|
||||||
thing so shouldn't bother.
|
|
||||||
ActiveModel::Serializer::Associations::Has\* are now
|
|
||||||
ActiveModel::Serializer::Association::Has\* and inherit from
|
|
||||||
ActiveModel::Serializer::Association
|
|
||||||
https://github.com/rails-api/active_model_serializers/commit/f5de334ddf1f3b9764d914a717311532021785d2
|
|
||||||
https://github.com/rails-api/active_model_serializers/commit/3dd422d99e8c57f113880da34f6abe583c4dadf9
|
|
||||||
|
|
||||||
* serialize\_ids call methods on the corresponding serializer if they
|
|
||||||
are defined, instead of talking directly with the serialized object.
|
|
||||||
Serializers are decorators so we shouldn't talk directly with
|
|
||||||
serialized objects.
|
|
||||||
|
|
||||||
* Array items are not wrapped anymore in root element.
|
|
||||||
|
|
||||||
* Remove support for ruby 1.8 versions.
|
|
||||||
|
|
||||||
* Require rails >= 3.2.
|
|
||||||
|
|
||||||
# VERSION 0.8.1
|
|
||||||
|
|
||||||
* Fix bug whereby a serializer using 'options' would blow up.
|
|
||||||
|
|
||||||
# VERSION 0.8.0
|
|
||||||
|
|
||||||
* Attributes can now have optional types.
|
|
||||||
|
|
||||||
* A new DefaultSerializer ensures that POROs behave the same way as ActiveModels.
|
|
||||||
|
|
||||||
* If you wish to override ActiveRecord::Base#to\_Json, you can now require
|
|
||||||
'active\_record/serializer\_override'. We don't recommend you do this, but
|
|
||||||
many users do, so we've left it optional.
|
|
||||||
|
|
||||||
* Fixed a bug where ActionController wouldn't always have MimeResponds.
|
|
||||||
|
|
||||||
* An optinal caching feature allows you to cache JSON & hashes that AMS uses.
|
|
||||||
Adding 'cached true' to your Serializers will turn on this cache.
|
|
||||||
|
|
||||||
* URL helpers used inside of Engines now work properly.
|
|
||||||
|
|
||||||
* Serializers now can filter attributes with `only` and `except`:
|
|
||||||
|
|
||||||
```
|
|
||||||
UserSerializer.new(user, only: [:first_name, :last_name])
|
|
||||||
UserSerializer.new(user, except: :first_name)
|
|
||||||
```
|
|
||||||
|
|
||||||
* Basic Mongoid support. We now include our mixins in the right place.
|
|
||||||
|
|
||||||
* On Ruby 1.8, we now generate an `id` method that properly serializes `id`
|
|
||||||
columns. See issue #127 for more.
|
|
||||||
|
|
||||||
* Add an alias for `scope` method to be the name of the context. By default
|
|
||||||
this is `current_user`. The name is automatically set when using
|
|
||||||
`serialization_scope` in the controller.
|
|
||||||
|
|
||||||
* Pass through serialization options (such as `:include`) when a model
|
|
||||||
has no serializer defined.
|
|
||||||
|
|
||||||
# VERSION 0.7.0
|
|
||||||
|
|
||||||
* ```embed_key``` option to allow embedding by attributes other than IDs
|
|
||||||
* Fix rendering nil with custom serializer
|
|
||||||
* Fix global ```self.root = false```
|
|
||||||
* Add support for specifying the serializer for an association as a String
|
|
||||||
* Able to specify keys on the attributes method
|
|
||||||
* Serializer Reloading via ActiveSupport::DescendantsTracker
|
|
||||||
* Reduce double map to once; Fixes datamapper eager loading.
|
|
||||||
|
|
||||||
# VERSION 0.6.0
|
|
||||||
|
|
||||||
* Serialize sets properly
|
|
||||||
* Add root option to ArraySerializer
|
|
||||||
* Support polymorphic associations
|
|
||||||
* Support :each_serializer in ArraySerializer
|
|
||||||
* Add `scope` method to easily access the scope in the serializer
|
|
||||||
* Fix regression with Rails 3.2.6; add Rails 4 support
|
|
||||||
* Allow serialization_scope to be disabled with serialization_scope nil
|
|
||||||
* Array serializer should support pure ruby objects besides serializers
|
|
||||||
|
|
||||||
# VERSION 0.5.0 (May 16, 2012)
|
|
||||||
|
|
||||||
* First tagged version
|
|
||||||
* Changes generators to always generate an ApplicationSerializer
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
Contributing to AMS
|
|
||||||
===================
|
|
||||||
|
|
||||||
First of all, **thank you**!
|
|
||||||
|
|
||||||
Now, for the details:
|
|
||||||
|
|
||||||
Please file issues on the [GitHub Issues
|
|
||||||
list](https://github.com/rails-api/active_model_serializers/issues).
|
|
||||||
|
|
||||||
Please discuss new features or ask for feedback about a new feature [on
|
|
||||||
rails-api-core](https://groups.google.com/forum/#!forum/rails-api-core).
|
|
||||||
|
|
||||||
If you want a feature implemented, the best way to get it done is to submit a
|
|
||||||
pull request that implements it. Tests and docs would be nice.
|
|
||||||
|
|
||||||
Please include a CHANGELOG with all entries that change behavior.
|
|
||||||
|
|
||||||
:heart: :sparkling_heart: :heart:
|
|
||||||
|
|
||||||
586
DESIGN.textile
586
DESIGN.textile
@ -1,586 +0,0 @@
|
|||||||
<strong>This was the original design document for serializers.</strong> It is useful mostly for historical purposes as the public API has changed.
|
|
||||||
|
|
||||||
h2. Rails Serializers
|
|
||||||
|
|
||||||
This guide describes how to use Active Model serializers to build non-trivial JSON services in Rails. By reading this guide, you will learn:
|
|
||||||
|
|
||||||
* When to use the built-in Active Model serialization
|
|
||||||
* When to use a custom serializer for your models
|
|
||||||
* How to use serializers to encapsulate authorization concerns
|
|
||||||
* How to create serializer templates to describe the application-wide structure of your serialized JSON
|
|
||||||
* How to build resources not backed by a single database table for use with JSON services
|
|
||||||
|
|
||||||
This guide covers an intermediate topic and assumes familiarity with Rails conventions. It is suitable for applications that expose a
|
|
||||||
JSON API that may return different results based on the authorization status of the user.
|
|
||||||
|
|
||||||
h3. Serialization
|
|
||||||
|
|
||||||
By default, Active Record objects can serialize themselves into JSON by using the `to_json` method. This method takes a series of additional
|
|
||||||
parameter to control which properties and associations Rails should include in the serialized output.
|
|
||||||
|
|
||||||
When building a web application that uses JavaScript to retrieve JSON data from the server, this mechanism has historically been the primary
|
|
||||||
way that Rails developers prepared their responses. This works great for simple cases, as the logic for serializing an Active Record object
|
|
||||||
is neatly encapsulated in Active Record itself.
|
|
||||||
|
|
||||||
However, this solution quickly falls apart in the face of serialization requirements based on authorization. For instance, a web service
|
|
||||||
may choose to expose additional information about a resource only if the user is entitled to access it. In addition, a JavaScript front-end
|
|
||||||
may want information that is not neatly described in terms of serializing a single Active Record object, or in a different format than.
|
|
||||||
|
|
||||||
In addition, neither the controller nor the model seems like the correct place for logic that describes how to serialize an model object
|
|
||||||
*for the current user*.
|
|
||||||
|
|
||||||
Serializers solve these problems by encapsulating serialization in an object designed for this purpose. If the default +to_json+ semantics,
|
|
||||||
with at most a few configuration options serve your needs, by all means continue to use the built-in +to_json+. If you find yourself doing
|
|
||||||
hash-driven-development in your controllers, juggling authorization logic and other concerns, serializers are for you!
|
|
||||||
|
|
||||||
h3. The Most Basic Serializer
|
|
||||||
|
|
||||||
A basic serializer is a simple Ruby object named after the model class it is serializing.
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class PostSerializer
|
|
||||||
def initialize(post, scope)
|
|
||||||
@post, @scope = post, scope
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json
|
|
||||||
{ post: { title: @post.name, body: @post.body } }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
A serializer is initialized with two parameters: the model object it should serialize and an authorization scope. By default, the
|
|
||||||
authorization scope is the current user (+current_user+) but you can use a different object if you want. The serializer also
|
|
||||||
implements an +as_json+ method, which returns a Hash that will be sent to the JSON encoder.
|
|
||||||
|
|
||||||
Rails will transparently use your serializer when you use +render :json+ in your controller.
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class PostsController < ApplicationController
|
|
||||||
def show
|
|
||||||
@post = Post.find(params[:id])
|
|
||||||
render json: @post
|
|
||||||
end
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
Because +respond_with+ uses +render :json+ under the hood for JSON requests, Rails will automatically use your serializer when
|
|
||||||
you use +respond_with+ as well.
|
|
||||||
|
|
||||||
h4. +serializable_hash+
|
|
||||||
|
|
||||||
In general, you will want to implement +serializable_hash+ and +as_json+ to allow serializers to embed associated content
|
|
||||||
directly. The easiest way to implement these two methods is to have +as_json+ call +serializable_hash+ and insert the root.
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class PostSerializer
|
|
||||||
def initialize(post, scope)
|
|
||||||
@post, @scope = post, scope
|
|
||||||
end
|
|
||||||
|
|
||||||
def serializable_hash
|
|
||||||
{ title: @post.name, body: @post.body }
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json
|
|
||||||
{ post: serializable_hash }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
h4. Authorization
|
|
||||||
|
|
||||||
Let's update our serializer to include the email address of the author of the post, but only if the current user has superuser
|
|
||||||
access.
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class PostSerializer
|
|
||||||
def initialize(post, scope)
|
|
||||||
@post, @scope = post, scope
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json
|
|
||||||
{ post: serializable_hash }
|
|
||||||
end
|
|
||||||
|
|
||||||
def serializable_hash
|
|
||||||
hash = post
|
|
||||||
hash.merge!(super_data) if super?
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def post
|
|
||||||
{ title: @post.name, body: @post.body }
|
|
||||||
end
|
|
||||||
|
|
||||||
def super_data
|
|
||||||
{ email: @post.email }
|
|
||||||
end
|
|
||||||
|
|
||||||
def super?
|
|
||||||
@scope.superuser?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
h4. Testing
|
|
||||||
|
|
||||||
One benefit of encapsulating our objects this way is that it becomes extremely straight-forward to test the serialization
|
|
||||||
logic in isolation.
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
require "ostruct"
|
|
||||||
|
|
||||||
class PostSerializerTest < ActiveSupport::TestCase
|
|
||||||
# For now, we use a very simple authorization structure. These tests will need
|
|
||||||
# refactoring if we change that.
|
|
||||||
plebe = OpenStruct.new(super?: false)
|
|
||||||
god = OpenStruct.new(super?: true)
|
|
||||||
|
|
||||||
post = OpenStruct.new(title: "Welcome to my blog!", body: "Blah blah blah", email: "tenderlove@gmail.com")
|
|
||||||
|
|
||||||
test "a regular user sees just the title and body" do
|
|
||||||
json = PostSerializer.new(post, plebe).to_json
|
|
||||||
hash = JSON.parse(json)
|
|
||||||
|
|
||||||
assert_equal post.title, hash.delete("title")
|
|
||||||
assert_equal post.body, hash.delete("body")
|
|
||||||
assert_empty hash
|
|
||||||
end
|
|
||||||
|
|
||||||
test "a superuser sees the title, body and email" do
|
|
||||||
json = PostSerializer.new(post, god).to_json
|
|
||||||
hash = JSON.parse(json)
|
|
||||||
|
|
||||||
assert_equal post.title, hash.delete("title")
|
|
||||||
assert_equal post.body, hash.delete("body")
|
|
||||||
assert_equal post.email, hash.delete("email")
|
|
||||||
assert_empty hash
|
|
||||||
end
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
It's important to note that serializer objects define a clear interface specifically for serializing an existing object.
|
|
||||||
In this case, the serializer expects to receive a post object with +name+, +body+ and +email+ attributes and an authorization
|
|
||||||
scope with a +super?+ method.
|
|
||||||
|
|
||||||
By defining a clear interface, it's must easier to ensure that your authorization logic is behaving correctly. In this case,
|
|
||||||
the serializer doesn't need to concern itself with how the authorization scope decides whether to set the +super?+ flag, just
|
|
||||||
whether it is set. In general, you should document these requirements in your serializer files and programatically via tests.
|
|
||||||
The documentation library +YARD+ provides excellent tools for describing this kind of requirement:
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class PostSerializer
|
|
||||||
# @param [~body, ~title, ~email] post the post to serialize
|
|
||||||
# @param [~super] scope the authorization scope for this serializer
|
|
||||||
def initialize(post, scope)
|
|
||||||
@post, @scope = post, scope
|
|
||||||
end
|
|
||||||
|
|
||||||
# ...
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
h3. Attribute Sugar
|
|
||||||
|
|
||||||
To simplify this process for a number of common cases, Rails provides a default superclass named +ActiveModel::Serializer+
|
|
||||||
that you can use to implement your serializers.
|
|
||||||
|
|
||||||
For example, you will sometimes want to simply include a number of existing attributes from the source model into the outputted
|
|
||||||
JSON. In the above example, the +title+ and +body+ attributes were always included in the JSON. Let's see how to use
|
|
||||||
+ActiveModel::Serializer+ to simplify our post serializer.
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :title, :body
|
|
||||||
|
|
||||||
def serializable_hash
|
|
||||||
hash = attributes
|
|
||||||
hash.merge!(super_data) if super?
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def super_data
|
|
||||||
{ email: @post.email }
|
|
||||||
end
|
|
||||||
|
|
||||||
def super?
|
|
||||||
@scope.superuser?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
First, we specified the list of included attributes at the top of the class. This will create an instance method called
|
|
||||||
+attributes+ that extracts those attributes from the post model.
|
|
||||||
|
|
||||||
NOTE: Internally, +ActiveModel::Serializer+ uses +read_attribute_for_serialization+, which defaults to +read_attribute+, which defaults to +send+. So if you're rolling your own models for use with the serializer, you can use simple Ruby accessors for your attributes if you like.
|
|
||||||
|
|
||||||
Next, we use the attributes method in our +serializable_hash+ method, which allowed us to eliminate the +post+ method we hand-rolled
|
|
||||||
earlier. We could also eliminate the +as_json+ method, as +ActiveModel::Serializer+ provides a default +as_json+ method for
|
|
||||||
us that calls our +serializable_hash+ method and inserts a root. But we can go a step further!
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :title, :body
|
|
||||||
|
|
||||||
private
|
|
||||||
def attributes
|
|
||||||
hash = super
|
|
||||||
hash.merge!(email: post.email) if super?
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def super?
|
|
||||||
@scope.superuser?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
The superclass provides a default +initialize+ method as well as a default +serializable_hash+ method, which uses
|
|
||||||
+attributes+. We can call +super+ to get the hash based on the attributes we declared, and then add in any additional
|
|
||||||
attributes we want to use.
|
|
||||||
|
|
||||||
NOTE: +ActiveModel::Serializer+ will create an accessor matching the name of the current class for the resource you pass in. In this case, because we have defined a PostSerializer, we can access the resource with the +post+ accessor.
|
|
||||||
|
|
||||||
h3. Associations
|
|
||||||
|
|
||||||
In most JSON APIs, you will want to include associated objects with your serialized object. In this case, let's include
|
|
||||||
the comments with the current post.
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :title, :body
|
|
||||||
has_many :comments
|
|
||||||
|
|
||||||
private
|
|
||||||
def attributes
|
|
||||||
hash = super
|
|
||||||
hash.merge!(email: post.email) if super?
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def super?
|
|
||||||
@scope.superuser?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
The default +serializable_hash+ method will include the comments as embedded objects inside the post.
|
|
||||||
|
|
||||||
<pre lang="json">
|
|
||||||
{
|
|
||||||
post: {
|
|
||||||
title: "Hello Blog!",
|
|
||||||
body: "This is my first post. Isn't it fabulous!",
|
|
||||||
comments: [
|
|
||||||
{
|
|
||||||
title: "Awesome",
|
|
||||||
body: "Your first post is great"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
Rails uses the same logic to generate embedded serializations as it does when you use +render :json+. In this case,
|
|
||||||
because you didn't define a +CommentSerializer+, Rails used the default +as_json+ on your comment object.
|
|
||||||
|
|
||||||
If you define a serializer, Rails will automatically instantiate it with the existing authorization scope.
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class CommentSerializer
|
|
||||||
def initialize(comment, scope)
|
|
||||||
@comment, @scope = comment, scope
|
|
||||||
end
|
|
||||||
|
|
||||||
def serializable_hash
|
|
||||||
{ title: @comment.title }
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json
|
|
||||||
{ comment: serializable_hash }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
If we define the above comment serializer, the outputted JSON will change to:
|
|
||||||
|
|
||||||
<pre lang="json">
|
|
||||||
{
|
|
||||||
post: {
|
|
||||||
title: "Hello Blog!",
|
|
||||||
body: "This is my first post. Isn't it fabulous!",
|
|
||||||
comments: [{ title: "Awesome" }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
Let's imagine that our comment system allows an administrator to kill a comment, and we only want to allow
|
|
||||||
users to see the comments they're entitled to see. By default, +has_many :comments+ will simply use the
|
|
||||||
+comments+ accessor on the post object. We can override the +comments+ accessor to limit the comments used
|
|
||||||
to just the comments we want to allow for the current user.
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :title. :body
|
|
||||||
has_many :comments
|
|
||||||
|
|
||||||
private
|
|
||||||
def attributes
|
|
||||||
hash = super
|
|
||||||
hash.merge!(email: post.email) if super?
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def comments
|
|
||||||
post.comments_for(scope)
|
|
||||||
end
|
|
||||||
|
|
||||||
def super?
|
|
||||||
@scope.superuser?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
+ActiveModel::Serializer+ will still embed the comments, but this time it will use just the comments
|
|
||||||
for the current user.
|
|
||||||
|
|
||||||
NOTE: The logic for deciding which comments a user should see still belongs in the model layer. In general, you should encapsulate concerns that require making direct Active Record queries in scopes or public methods on your models.
|
|
||||||
|
|
||||||
h4. Modifying Associations
|
|
||||||
|
|
||||||
You can also rename associations if required. Say for example you have an association that
|
|
||||||
makes sense to be named one thing in your code, but another when data is serialized.
|
|
||||||
You can use the <code:key</code> option to specify a different name for an association.
|
|
||||||
Here is an example:
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class UserSerializer < ActiveModel::Serializer
|
|
||||||
has_many :followed_posts, key: :posts
|
|
||||||
has_one :owned_account, key: :account
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
Using the <code>:key</code> without a <code>:serializer</code> option will use implicit detection
|
|
||||||
to determine a serializer. In this example, you'd have to define two classes: <code>PostSerializer</code>
|
|
||||||
and <code>AccountSerializer</code>. You can also add the <code>:serializer</code> option
|
|
||||||
to set it explicitly:
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class UserSerializer < ActiveModel::Serializer
|
|
||||||
has_many :followed_posts, key: :posts, serializer: CustomPostSerializer
|
|
||||||
has_one :owne_account, key: :account, serializer: PrivateAccountSerializer
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
h3. Customizing Associations
|
|
||||||
|
|
||||||
Not all front-ends expect embedded documents in the same form. In these cases, you can override the
|
|
||||||
default +serializable_hash+, and use conveniences provided by +ActiveModel::Serializer+ to avoid having to
|
|
||||||
build up the hash manually.
|
|
||||||
|
|
||||||
For example, let's say our front-end expects the posts and comments in the following format:
|
|
||||||
|
|
||||||
<pre lang="json">
|
|
||||||
{
|
|
||||||
post: {
|
|
||||||
id: 1
|
|
||||||
title: "Hello Blog!",
|
|
||||||
body: "This is my first post. Isn't it fabulous!",
|
|
||||||
comments: [1,2]
|
|
||||||
},
|
|
||||||
comments: [
|
|
||||||
{
|
|
||||||
id: 1
|
|
||||||
title: "Awesome",
|
|
||||||
body: "Your first post is great"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2
|
|
||||||
title: "Not so awesome",
|
|
||||||
body: "Why is it so short!"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
We could achieve this with a custom +as_json+ method. We will also need to define a serializer for comments.
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class CommentSerializer < ActiveModel::Serializer
|
|
||||||
attributes :id, :title, :body
|
|
||||||
|
|
||||||
# define any logic for dealing with authorization-based attributes here
|
|
||||||
end
|
|
||||||
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :title, :body
|
|
||||||
has_many :comments
|
|
||||||
|
|
||||||
def as_json
|
|
||||||
{ post: serializable_hash }.merge!(associations)
|
|
||||||
end
|
|
||||||
|
|
||||||
def serializable_hash
|
|
||||||
post_hash = attributes
|
|
||||||
post_hash.merge!(association_ids)
|
|
||||||
post_hash
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def attributes
|
|
||||||
hash = super
|
|
||||||
hash.merge!(email: post.email) if super?
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def comments
|
|
||||||
post.comments_for(scope)
|
|
||||||
end
|
|
||||||
|
|
||||||
def super?
|
|
||||||
@scope.superuser?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
Here, we used two convenience methods: +associations+ and +association_ids+. The first,
|
|
||||||
+associations+, creates a hash of all of the define associations, using their defined
|
|
||||||
serializers. The second, +association_ids+, generates a hash whose key is the association
|
|
||||||
name and whose value is an Array of the association's keys.
|
|
||||||
|
|
||||||
The +association_ids+ helper will use the overridden version of the association, so in
|
|
||||||
this case, +association_ids+ will only include the ids of the comments provided by the
|
|
||||||
+comments+ method.
|
|
||||||
|
|
||||||
|
|
||||||
h3. Special Association Serializers
|
|
||||||
|
|
||||||
So far, associations defined in serializers use either the +as_json+ method on the model
|
|
||||||
or the defined serializer for the association type. Sometimes, you may want to serialize
|
|
||||||
associated models differently when they are requested as part of another resource than
|
|
||||||
when they are requested on their own.
|
|
||||||
|
|
||||||
For instance, we might want to provide the full comment when it is requested directly,
|
|
||||||
but only its title when requested as part of the post. To achieve this, you can define
|
|
||||||
a serializer for associated objects nested inside the main serializer.
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
class CommentSerializer < ActiveModel::Serializer
|
|
||||||
attributes :id, :title
|
|
||||||
end
|
|
||||||
|
|
||||||
# same as before
|
|
||||||
# ...
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
In other words, if a +PostSerializer+ is trying to serialize comments, it will first
|
|
||||||
look for +PostSerializer::CommentSerializer+ before falling back to +CommentSerializer+
|
|
||||||
and finally +comment.as_json+.
|
|
||||||
|
|
||||||
h3. Overriding the Defaults
|
|
||||||
|
|
||||||
h4. Authorization Scope
|
|
||||||
|
|
||||||
By default, the authorization scope for serializers is +:current_user+. This means
|
|
||||||
that when you call +render json: @post+, the controller will automatically call
|
|
||||||
its +current_user+ method and pass that along to the serializer's initializer.
|
|
||||||
|
|
||||||
If you want to change that behavior, simply use the +serialization_scope+ class
|
|
||||||
method.
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class PostsController < ApplicationController
|
|
||||||
serialization_scope :current_app
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
You can also implement an instance method called (no surprise) +serialization_scope+,
|
|
||||||
which allows you to define a dynamic authorization scope based on the current request.
|
|
||||||
|
|
||||||
WARNING: If you use different objects as authorization scopes, make sure that they all implement whatever interface you use in your serializers to control what the outputted JSON looks like.
|
|
||||||
|
|
||||||
h3. Using Serializers Outside of a Request
|
|
||||||
|
|
||||||
The serialization API encapsulates the concern of generating a JSON representation of
|
|
||||||
a particular model for a particular user. As a result, you should be able to easily use
|
|
||||||
serializers, whether you define them yourself or whether you use +ActiveModel::Serializer+
|
|
||||||
outside a request.
|
|
||||||
|
|
||||||
For instance, if you want to generate the JSON representation of a post for a user outside
|
|
||||||
of a request:
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
user = get_user # some logic to get the user in question
|
|
||||||
PostSerializer.new(post, user).to_json # reliably generate JSON output
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
If you want to generate JSON for an anonymous user, you should be able to use whatever
|
|
||||||
technique you use in your application to generate anonymous users outside of a request.
|
|
||||||
Typically, that means creating a new user and not saving it to the database:
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
user = User.new # create a new anonymous user
|
|
||||||
PostSerializer.new(post, user).to_json
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
In general, the better you encapsulate your authorization logic, the more easily you
|
|
||||||
will be able to use the serializer outside of the context of a request. For instance,
|
|
||||||
if you use an authorization library like Cancan, which uses a uniform +user.can?(action, model)+,
|
|
||||||
the authorization interface can very easily be replaced by a plain Ruby object for
|
|
||||||
testing or usage outside the context of a request.
|
|
||||||
|
|
||||||
h3. Collections
|
|
||||||
|
|
||||||
So far, we've talked about serializing individual model objects. By default, Rails
|
|
||||||
will serialize collections, including when using the +associations+ helper, by
|
|
||||||
looping over each element of the collection, calling +serializable_hash+ on the element,
|
|
||||||
and then grouping them by their type (using the plural version of their class name
|
|
||||||
as the root).
|
|
||||||
|
|
||||||
For example, an Array of post objects would serialize as:
|
|
||||||
|
|
||||||
<pre lang="json">
|
|
||||||
{
|
|
||||||
posts: [
|
|
||||||
{
|
|
||||||
title: "FIRST POST!",
|
|
||||||
body: "It's my first pooooost"
|
|
||||||
},
|
|
||||||
{ title: "Second post!",
|
|
||||||
body: "Zomg I made it to my second post"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
If you want to change the behavior of serialized Arrays, you need to create
|
|
||||||
a custom Array serializer.
|
|
||||||
|
|
||||||
<pre lang="ruby">
|
|
||||||
class ArraySerializer < ActiveModel::ArraySerializer
|
|
||||||
def serializable_array
|
|
||||||
serializers.map do |serializer|
|
|
||||||
serializer.serializable_hash
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json
|
|
||||||
hash = { root => serializable_array }
|
|
||||||
hash.merge!(associations)
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
When generating embedded associations using the +associations+ helper inside a
|
|
||||||
regular serializer, it will create a new <code>ArraySerializer</code> with the
|
|
||||||
associated content and call its +serializable_array+ method. In this case, those
|
|
||||||
embedded associations will not recursively include associations.
|
|
||||||
|
|
||||||
When generating an Array using +render json: posts+, the controller will invoke
|
|
||||||
the +as_json+ method, which will include its associations and its root.
|
|
||||||
6
Gemfile
6
Gemfile
@ -1,6 +0,0 @@
|
|||||||
source 'https://rubygems.org'
|
|
||||||
|
|
||||||
# Specify gem dependencies in active_model_serializers.gemspec
|
|
||||||
gemspec
|
|
||||||
|
|
||||||
gem "coveralls", require: false
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
source 'http://rubygems.org'
|
|
||||||
|
|
||||||
gemspec
|
|
||||||
|
|
||||||
gem 'rails', github: 'rails/rails'
|
|
||||||
|
|
||||||
# Current dependencies of edge rails
|
|
||||||
gem 'journey', github: 'rails/journey'
|
|
||||||
gem 'activerecord-deprecated_finders' , github: 'rails/activerecord-deprecated_finders'
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
Copyright (c) 2011-2012 José Valim & Yehuda Katz
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
716
README.md
716
README.md
@ -1,716 +0,0 @@
|
|||||||
[](https://travis-ci.org/rails-api/active_model_serializers) [](https://codeclimate.com/github/rails-api/active_model_serializers) [](https://coveralls.io/r/rails-api/active_model_serializers)
|
|
||||||
|
|
||||||
# Purpose
|
|
||||||
|
|
||||||
The purpose of `ActiveModel::Serializers` is to provide an object to
|
|
||||||
encapsulate serialization of `ActiveModel` objects, including `ActiveRecord`
|
|
||||||
objects.
|
|
||||||
|
|
||||||
Serializers know about both a model and the `current_user`, so you can
|
|
||||||
customize serialization based upon whether a user is authorized to see the
|
|
||||||
content.
|
|
||||||
|
|
||||||
In short, **serializers replace hash-driven development with object-oriented
|
|
||||||
development.**
|
|
||||||
|
|
||||||
# Installing
|
|
||||||
|
|
||||||
The easiest way to install `ActiveModel::Serializers` is to add it to your
|
|
||||||
`Gemfile`:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
gem "active_model_serializers"
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, install it on the command line:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ bundle install
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Ruby 1.8 is no longer supported!
|
|
||||||
|
|
||||||
If you must use a ruby 1.8 version (MRI 1.8.7, REE, Rubinius 1.8, or JRuby 1.8), you need to use version 0.8.x.
|
|
||||||
Versions after 0.9.0 do not support ruby 1.8. To specify version 0.8, include this in your Gemfile:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
gem "active_model_serializers", "~> 0.8.0"
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
# Creating a Serializer
|
|
||||||
|
|
||||||
The easiest way to create a new serializer is to generate a new resource, which
|
|
||||||
will generate a serializer at the same time:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ rails g resource post title:string body:string
|
|
||||||
```
|
|
||||||
|
|
||||||
This will generate a serializer in `app/serializers/post_serializer.rb` for
|
|
||||||
your new model. You can also generate a serializer for an existing model with
|
|
||||||
the serializer generator:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ rails g serializer post
|
|
||||||
```
|
|
||||||
|
|
||||||
### Support for POROs and other ORMs.
|
|
||||||
|
|
||||||
Currently `ActiveModel::Serializers` adds serialization support to all models
|
|
||||||
that descend from `ActiveRecord` or include `Mongoid::Document`. If you are
|
|
||||||
using another ORM, or if you are using objects that are `ActiveModel`
|
|
||||||
compliant but do not descend from `ActiveRecord` or include
|
|
||||||
`Mongoid::Document`, you must add an include statement for
|
|
||||||
`ActiveModel::SerializerSupport` to make models serializable. If you
|
|
||||||
also want to make collections serializable, you should include
|
|
||||||
`ActiveModel::ArraySerializerSupport` into your ORM's
|
|
||||||
relation/criteria class.
|
|
||||||
|
|
||||||
# ActiveModel::Serializer
|
|
||||||
|
|
||||||
All new serializers descend from ActiveModel::Serializer
|
|
||||||
|
|
||||||
# render :json
|
|
||||||
|
|
||||||
In your controllers, when you use `render :json`, Rails will now first search
|
|
||||||
for a serializer for the object and use it if available.
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostsController < ApplicationController
|
|
||||||
def show
|
|
||||||
@post = Post.find(params[:id])
|
|
||||||
render json: @post
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
In this case, Rails will look for a serializer named `PostSerializer`, and if
|
|
||||||
it exists, use it to serialize the `Post`.
|
|
||||||
|
|
||||||
This also works with `respond_with`, which uses `to_json` under the hood. Also
|
|
||||||
note that any options passed to `render :json` will be passed to your
|
|
||||||
serializer and available as `@options` inside.
|
|
||||||
|
|
||||||
To specify a custom serializer for an object, there are 2 options:
|
|
||||||
|
|
||||||
#### 1. Specify the serializer in your model:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class Post < ActiveRecord::Base
|
|
||||||
def active_model_serializer
|
|
||||||
FancyPostSerializer
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. Specify the serializer when you render the object:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
render json: @post, serializer: FancyPostSerializer
|
|
||||||
```
|
|
||||||
|
|
||||||
## Arrays
|
|
||||||
|
|
||||||
In your controllers, when you use `render :json` for an array of objects, AMS will
|
|
||||||
use `ActiveModel::ArraySerializer` (included in this project) as the base serializer,
|
|
||||||
and the individual `Serializer` for the objects contained in that array.
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :title, :body
|
|
||||||
end
|
|
||||||
|
|
||||||
class PostsController < ApplicationController
|
|
||||||
def index
|
|
||||||
@posts = Post.all
|
|
||||||
render json: @posts
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
Given the example above, the index action will return
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"posts":
|
|
||||||
[
|
|
||||||
{ "title": "Post 1", "body": "Hello!" },
|
|
||||||
{ "title": "Post 2", "body": "Goodbye!" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, the root element is the name of the controller. For example, `PostsController`
|
|
||||||
generates a root element "posts". To change it:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
render json: @posts, root: "some_posts"
|
|
||||||
```
|
|
||||||
|
|
||||||
You may disable the root element for arrays at the top level, which will result in
|
|
||||||
more concise json. See the next section for ways on how to do this. Disabling the
|
|
||||||
root element of the array with any of those methods will produce
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{ "title": "Post 1", "body": "Hello!" },
|
|
||||||
{ "title": "Post 2", "body": "Goodbye!" }
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
To specify a custom serializer for the items within an array:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
render json: @posts, each_serializer: FancyPostSerializer
|
|
||||||
```
|
|
||||||
|
|
||||||
## Disabling the root element
|
|
||||||
|
|
||||||
You have 4 options to disable the root element, each with a slightly different scope:
|
|
||||||
|
|
||||||
#### 1. Disable root globally for all, or per class
|
|
||||||
|
|
||||||
In an initializer:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
ActiveSupport.on_load(:active_model_serializers) do
|
|
||||||
# Disable for all serializers (except ArraySerializer)
|
|
||||||
ActiveModel::Serializer.root = false
|
|
||||||
|
|
||||||
# Disable for ArraySerializer
|
|
||||||
ActiveModel::ArraySerializer.root = false
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. Disable root per render call in your controller
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
render json: @posts, root: false
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. Subclass the serializer, and specify using it
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class CustomArraySerializer < ActiveModel::ArraySerializer
|
|
||||||
self.root = false
|
|
||||||
end
|
|
||||||
|
|
||||||
# controller:
|
|
||||||
render json: @posts, serializer: CustomArraySerializer
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4. Define default_serializer_options in your controller
|
|
||||||
|
|
||||||
If you define `default_serializer_options` method in your controller,
|
|
||||||
all serializers in actions of this controller and it's children will use them.
|
|
||||||
One of the options may be `root: false`
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
def default_serializer_options
|
|
||||||
{
|
|
||||||
root: false
|
|
||||||
}
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Getting the old version
|
|
||||||
|
|
||||||
If you find that your project is already relying on the old rails to_json
|
|
||||||
change `render :json` to `render json: @your_object.to_json`.
|
|
||||||
|
|
||||||
# Attributes and Associations
|
|
||||||
|
|
||||||
Once you have a serializer, you can specify which attributes and associations
|
|
||||||
you would like to include in the serialized form.
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :id, :title, :body
|
|
||||||
has_many :comments
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Attributes
|
|
||||||
|
|
||||||
For specified attributes, a serializer will look up the attribute on the
|
|
||||||
object you passed to `render :json`. It uses
|
|
||||||
`read_attribute_for_serialization`, which `ActiveRecord` objects implement as a
|
|
||||||
regular attribute lookup.
|
|
||||||
|
|
||||||
Before looking up the attribute on the object, a serializer will check for the
|
|
||||||
presence of a method with the name of the attribute. This allows serializers to
|
|
||||||
include properties beyond the simple attributes of the model. For example:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PersonSerializer < ActiveModel::Serializer
|
|
||||||
attributes :first_name, :last_name, :full_name
|
|
||||||
|
|
||||||
def full_name
|
|
||||||
"#{object.first_name} #{object.last_name}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
Within a serializer's methods, you can access the object being
|
|
||||||
serialized as `object`.
|
|
||||||
|
|
||||||
Since this shadows any attribute named `object`, you can include them through `object.object`. For example:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class VersionSerializer < ActiveModel::Serializer
|
|
||||||
attribute :version_object, key: :object
|
|
||||||
|
|
||||||
def version_object
|
|
||||||
object.object
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also access the `current_user` method, which provides an
|
|
||||||
authorization context to your serializer. By default, the context
|
|
||||||
is the current user of your application, but this
|
|
||||||
[can be customized](#customizing-scope).
|
|
||||||
|
|
||||||
Serializers will check for the presence of a method named
|
|
||||||
`include_[ATTRIBUTE]?` to determine whether a particular attribute should be
|
|
||||||
included in the output. This is typically used to customize output
|
|
||||||
based on `current_user`. For example:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :id, :title, :body, :author
|
|
||||||
|
|
||||||
def include_author?
|
|
||||||
current_user.admin?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
The type of a computed attribute (like :full_name above) is not easily
|
|
||||||
calculated without some sophisticated static code analysis. To specify the
|
|
||||||
type of a computed attribute:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PersonSerializer < ActiveModel::Serializer
|
|
||||||
attributes :first_name, :last_name, {full_name: :string}
|
|
||||||
|
|
||||||
def full_name
|
|
||||||
"#{object.first_name} #{object.last_name}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
If you would like the key in the outputted JSON to be different from its name
|
|
||||||
in ActiveRecord, you can use the `:key` option to customize it:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :id, :body
|
|
||||||
|
|
||||||
# look up :subject on the model, but use +title+ in the JSON
|
|
||||||
attribute :subject, key: :title
|
|
||||||
has_many :comments
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
If you would like to add meta information to the outputted JSON, use the `:meta`
|
|
||||||
option:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
render json: @posts, serializer: CustomArraySerializer, meta: {total: 10}
|
|
||||||
```
|
|
||||||
|
|
||||||
The above usage of `:meta` will produce the following:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"meta": { "total": 10 },
|
|
||||||
"posts": [
|
|
||||||
{ "title": "Post 1", "body": "Hello!" },
|
|
||||||
{ "title": "Post 2", "body": "Goodbye!" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you would like to change the meta key name you can use the `:meta_key` option:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
render json: @posts, serializer: CustomArraySerializer, meta: {total: 10}, meta_key: 'meta_object'
|
|
||||||
```
|
|
||||||
|
|
||||||
The above usage of `:meta_key` will produce the following:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"meta_object": { "total": 10 },
|
|
||||||
"posts": [
|
|
||||||
{ "title": "Post 1", "body": "Hello!" },
|
|
||||||
{ "title": "Post 2", "body": "Goodbye!" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you would like direct, low-level control of attribute serialization, you can
|
|
||||||
completely override the `attributes` method to return the hash you need:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PersonSerializer < ActiveModel::Serializer
|
|
||||||
attributes :first_name, :last_name
|
|
||||||
|
|
||||||
def attributes
|
|
||||||
hash = super
|
|
||||||
if current_user.admin?
|
|
||||||
hash["ssn"] = object.ssn
|
|
||||||
hash["secret"] = object.mothers_maiden_name
|
|
||||||
end
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Associations
|
|
||||||
|
|
||||||
For specified associations, the serializer will look up the association and
|
|
||||||
then serialize each element of the association. For instance, a `has_many
|
|
||||||
:comments` association will create a new `CommentSerializer` for each comment
|
|
||||||
and use it to serialize the comment.
|
|
||||||
|
|
||||||
By default, serializers simply look up the association on the original object.
|
|
||||||
You can customize this behavior by implementing a method with the name of the
|
|
||||||
association and returning a different Array. Often, you will do this to
|
|
||||||
customize the objects returned based on the current user.
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :id, :title, :body
|
|
||||||
has_many :comments
|
|
||||||
|
|
||||||
# only let the user see comments he created.
|
|
||||||
def comments
|
|
||||||
object.comments.where(created_by: current_user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
As with attributes, you can change the JSON key that the serializer should
|
|
||||||
use for a particular association.
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :id, :title, :body
|
|
||||||
|
|
||||||
# look up comments, but use +my_comments+ as the key in JSON
|
|
||||||
has_many :comments, key: :my_comments
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
Also, as with attributes, serializers will check for the presence
|
|
||||||
of a method named `include_[ASSOCIATION]?` to determine whether a particular association
|
|
||||||
should be included in the output. For example:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :id, :title, :body
|
|
||||||
has_many :comments
|
|
||||||
|
|
||||||
def include_comments?
|
|
||||||
!object.comments_disabled?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
If you would like lower-level control of association serialization, you can
|
|
||||||
override `include_associations!` to specify which associations should be included:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :id, :title, :body
|
|
||||||
has_one :author
|
|
||||||
has_many :comments
|
|
||||||
|
|
||||||
def include_associations!
|
|
||||||
include! :author if current_user.admin?
|
|
||||||
include! :comments unless object.comments_disabled?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
You may also use the `:serializer` option to specify a custom serializer class and the `:polymorphic` option to specify an association that is polymorphic (STI), e.g.:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
has_many :comments, serializer: CommentShortSerializer
|
|
||||||
has_one :reviewer, polymorphic: true
|
|
||||||
```
|
|
||||||
|
|
||||||
Serializers are only concerned with multiplicity, and not ownership. `belongs_to` ActiveRecord associations can be included using `has_one` in your serializer.
|
|
||||||
|
|
||||||
## Embedding Associations
|
|
||||||
|
|
||||||
By default, associations will be embedded inside the serialized object. So if
|
|
||||||
you have a post, the outputted JSON will look like:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"post": {
|
|
||||||
"id": 1,
|
|
||||||
"title": "New post",
|
|
||||||
"body": "A body!",
|
|
||||||
"comments": [
|
|
||||||
{ "id": 1, "body": "what a dumb post" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is convenient for simple use-cases, but for more complex clients, it is
|
|
||||||
better to supply an Array of IDs for the association. This makes your API more
|
|
||||||
flexible from a performance standpoint and avoids wasteful duplication.
|
|
||||||
|
|
||||||
To embed IDs instead of associations, simply use the `embed` class method:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
embed :ids
|
|
||||||
|
|
||||||
attributes :id, :title, :body
|
|
||||||
has_many :comments
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, any associations will be supplied as an Array of IDs:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"post": {
|
|
||||||
"id": 1,
|
|
||||||
"title": "New post",
|
|
||||||
"body": "A body!",
|
|
||||||
"comment_ids": [ 1, 2, 3 ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can choose to embed only the ids or the associated objects per association:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :id, :title, :body
|
|
||||||
|
|
||||||
has_many :comments, embed: :objects
|
|
||||||
has_many :tags, embed: :ids
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
The JSON will look like this:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"post": {
|
|
||||||
"id": 1,
|
|
||||||
"title": "New post",
|
|
||||||
"body": "A body!",
|
|
||||||
"comments": [
|
|
||||||
{ "id": 1, "body": "what a dumb post" }
|
|
||||||
],
|
|
||||||
"tag_ids": [ 1, 2, 3 ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In addition to supplying an Array of IDs, you may want to side-load the data
|
|
||||||
alongside the main object. This makes it easier to process the entire package
|
|
||||||
of data without having to recursively scan the tree looking for embedded
|
|
||||||
information. It also ensures that associations that are shared between several
|
|
||||||
objects (like tags), are only delivered once for the entire payload.
|
|
||||||
|
|
||||||
You can specify that the data be included like this:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
embed :ids, include: true
|
|
||||||
|
|
||||||
attributes :id, :title, :body
|
|
||||||
has_many :comments
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
Assuming that the comments also `has_many :tags`, you will get a JSON like
|
|
||||||
this:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"post": {
|
|
||||||
"id": 1,
|
|
||||||
"title": "New post",
|
|
||||||
"body": "A body!",
|
|
||||||
"comment_ids": [ 1, 2 ]
|
|
||||||
},
|
|
||||||
"comments": [
|
|
||||||
{ "id": 1, "body": "what a dumb post", "tag_ids": [ 1, 2 ] },
|
|
||||||
{ "id": 2, "body": "i liked it", "tag_ids": [ 1, 3 ] },
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
{ "id": 1, "name": "short" },
|
|
||||||
{ "id": 2, "name": "whiny" },
|
|
||||||
{ "id": 3, "name": "happy" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also specify a different root for the embedded objects than the key
|
|
||||||
used to reference them:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
embed :ids, include: true
|
|
||||||
|
|
||||||
attributes :id, :title, :body
|
|
||||||
has_many :comments, key: :comment_ids, root: :comment_objects
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
This would generate JSON that would look like this:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"post": {
|
|
||||||
"id": 1,
|
|
||||||
"title": "New post",
|
|
||||||
"body": "A body!",
|
|
||||||
"comment_ids": [ 1 ]
|
|
||||||
},
|
|
||||||
"comment_objects": [
|
|
||||||
{ "id": 1, "body": "what a dumb post" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also specify a different attribute to use rather than the ID of the
|
|
||||||
objects:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
embed :ids, include: true
|
|
||||||
|
|
||||||
attributes :id, :title, :body
|
|
||||||
has_many :comments, embed_key: :external_id
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
This would generate JSON that would look like this:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"post": {
|
|
||||||
"id": 1,
|
|
||||||
"title": "New post",
|
|
||||||
"body": "A body!",
|
|
||||||
"comment_ids": [ "COMM001" ]
|
|
||||||
},
|
|
||||||
"comments": [
|
|
||||||
{ "id": 1, "external_id": "COMM001", "body": "what a dumb post" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**NOTE**: The `embed :ids` mechanism is primary useful for clients that process
|
|
||||||
data in bulk and load it into a local store. For these clients, the ability to
|
|
||||||
easily see all of the data per type, rather than having to recursively scan the
|
|
||||||
data looking for information, is extremely useful.
|
|
||||||
|
|
||||||
If you are mostly working with the data in simple scenarios and manually making
|
|
||||||
Ajax requests, you probably just want to use the default embedded behavior.
|
|
||||||
|
|
||||||
## Customizing Scope
|
|
||||||
|
|
||||||
In a serializer, `current_user` is the current authorization scope which the controller
|
|
||||||
provides to the serializer when you call `render :json`. By default, this is
|
|
||||||
`current_user`, but can be customized in your controller by calling
|
|
||||||
`serialization_scope`:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class ApplicationController < ActionController::Base
|
|
||||||
serialization_scope :current_admin
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
The above example will also change the scope name from `current_user` to
|
|
||||||
`current_admin`.
|
|
||||||
|
|
||||||
Please note that, until now, `serialization_scope` doesn't accept a second
|
|
||||||
object with options for specifying which actions should or should not take a
|
|
||||||
given scope in consideration.
|
|
||||||
|
|
||||||
To be clear, it's not possible, yet, to do something like this:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class SomeController < ApplicationController
|
|
||||||
serialization_scope :current_admin, except: [:index, :show]
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
So, in order to have a fine grained control of what each action should take in
|
|
||||||
consideration for its scope, you may use something like this:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class CitiesController < ApplicationController
|
|
||||||
serialization_scope nil
|
|
||||||
|
|
||||||
def index
|
|
||||||
@cities = City.all
|
|
||||||
|
|
||||||
render json: @cities, each_serializer: CitySerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
def show
|
|
||||||
@city = City.find(params[:id])
|
|
||||||
|
|
||||||
render json: @city, scope: current_admin, scope_name: :current_admin
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
Assuming that the `current_admin` method needs to make a query in the database
|
|
||||||
for the current user, the advantage of this approach is that, by setting
|
|
||||||
`serialization_scope` to `nil`, the `index` action no longer will need to make
|
|
||||||
that query, only the `show` action will.
|
|
||||||
|
|
||||||
## Caching
|
|
||||||
|
|
||||||
To cache a serializer, call `cached` and define a `cache_key` method:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
cached # enables caching for this serializer
|
|
||||||
|
|
||||||
attributes :title, :body
|
|
||||||
|
|
||||||
def cache_key
|
|
||||||
[object, current_user]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
The caching interface uses `Rails.cache` under the hood.
|
|
||||||
|
|
||||||
# Design and Implementation
|
|
||||||
|
|
||||||
## Keep it Simple
|
|
||||||
|
|
||||||
ActiveModel::Serializers is capable of producing complex JSON views/large object
|
|
||||||
trees, and it may be tempting to design in this way so that your client can make
|
|
||||||
fewer requests to get data and so that related querying can be optimized.
|
|
||||||
However, keeping things simple in your serializers and controllers may
|
|
||||||
significantly reduce complexity and maintenance over the long-term development
|
|
||||||
of your application. Please consider reducing the complexity of the JSON views
|
|
||||||
you provide via the serializers as you build out your application, so that
|
|
||||||
controllers/services can be more easily reused without a lot of complexity
|
|
||||||
later.
|
|
||||||
|
|
||||||
## Performance
|
|
||||||
|
|
||||||
As you develop your controllers or other code that utilizes serializers, try to
|
|
||||||
avoid n+1 queries by ensuring that data loads in an optimal fashion, e.g. if you
|
|
||||||
are using ActiveRecord, you might want to use query includes or joins as needed
|
|
||||||
to make the data available that the serializer(s) need.
|
|
||||||
18
Rakefile
18
Rakefile
@ -1,18 +0,0 @@
|
|||||||
#!/usr/bin/env rake
|
|
||||||
require "bundler/gem_tasks"
|
|
||||||
require "rake/testtask"
|
|
||||||
|
|
||||||
desc 'Run tests'
|
|
||||||
Rake::TestTask.new(:test) do |t|
|
|
||||||
t.libs << 'lib'
|
|
||||||
t.libs << 'test'
|
|
||||||
t.pattern = 'test/**/*_test.rb'
|
|
||||||
t.verbose = true
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'Benchmark'
|
|
||||||
task :bench do
|
|
||||||
load 'bench/perf.rb'
|
|
||||||
end
|
|
||||||
|
|
||||||
task default: :test
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
$:.unshift File.expand_path("../lib", __FILE__)
|
|
||||||
require "active_model/serializer/version"
|
|
||||||
|
|
||||||
Gem::Specification.new do |gem|
|
|
||||||
gem.authors = ["José Valim", "Yehuda Katz"]
|
|
||||||
gem.email = ["jose.valim@gmail.com", "wycats@gmail.com"]
|
|
||||||
gem.description = %q{Making it easy to serialize models for client-side use}
|
|
||||||
gem.summary = %q{Bringing consistency and object orientation to model serialization. Works great for client-side MVC frameworks!}
|
|
||||||
gem.homepage = "https://github.com/rails-api/active_model_serializers"
|
|
||||||
|
|
||||||
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
||||||
gem.files = `git ls-files`.split("\n")
|
|
||||||
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
||||||
gem.name = "active_model_serializers"
|
|
||||||
gem.require_paths = ["lib"]
|
|
||||||
gem.version = ActiveModel::Serializer::VERSION
|
|
||||||
|
|
||||||
gem.required_ruby_version = ">= 1.9.3"
|
|
||||||
|
|
||||||
gem.add_dependency "activemodel", ">= 3.2"
|
|
||||||
|
|
||||||
gem.add_development_dependency "rails", ">= 3.2"
|
|
||||||
gem.add_development_dependency "pry"
|
|
||||||
gem.add_development_dependency "simplecov"
|
|
||||||
gem.add_development_dependency "coveralls"
|
|
||||||
end
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
require "rubygems"
|
|
||||||
require "bundler/setup"
|
|
||||||
require "active_model_serializers"
|
|
||||||
require "active_support/json"
|
|
||||||
require "benchmark"
|
|
||||||
|
|
||||||
class User < Struct.new(:id,:name,:age,:about)
|
|
||||||
include ActiveModel::SerializerSupport
|
|
||||||
|
|
||||||
def fast_hash
|
|
||||||
h = {
|
|
||||||
id: read_attribute_for_serialization(:id),
|
|
||||||
name: read_attribute_for_serialization(:name),
|
|
||||||
about: read_attribute_for_serialization(:about)
|
|
||||||
}
|
|
||||||
h[:age] = read_attribute_for_serialization(:age) if age > 18
|
|
||||||
h
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class UserSerializer < ActiveModel::Serializer
|
|
||||||
attributes :id, :name, :age, :about
|
|
||||||
|
|
||||||
def include_age?
|
|
||||||
object.age > 18
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
u = User.new(1, "sam", 10, "about")
|
|
||||||
s = UserSerializer.new(u)
|
|
||||||
|
|
||||||
n = 100000
|
|
||||||
|
|
||||||
Benchmark.bmbm {|x|
|
|
||||||
x.report("init") { n.times { UserSerializer.new(u) } }
|
|
||||||
x.report("fast_hash") { n.times { u.fast_hash } }
|
|
||||||
x.report("attributes") { n.times { UserSerializer.new(u).attributes } }
|
|
||||||
x.report("serializable_hash") { n.times { UserSerializer.new(u).serializable_hash } }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
19
cruft.md
19
cruft.md
@ -1,19 +0,0 @@
|
|||||||
As of Ruby 1.9.3, it is impossible to dynamically generate a Symbol
|
|
||||||
through interpolation without generating garbage. Theoretically, Ruby
|
|
||||||
should be able to take care of this by building up the String in C and
|
|
||||||
interning the C String.
|
|
||||||
|
|
||||||
Because of this, we avoid generating dynamic Symbols at runtime. For
|
|
||||||
example, instead of generating the instrumentation event dynamically, we
|
|
||||||
have a constant with a Hash of events:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
INSTRUMENT = {
|
|
||||||
serialize: :"serialize.serializer",
|
|
||||||
associations: :"associations.serializer"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If Ruby ever fixes this issue and avoids generating garbage with dynamic
|
|
||||||
symbols, this code can be removed.
|
|
||||||
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
module ActionController
|
|
||||||
# Action Controller Serialization
|
|
||||||
#
|
|
||||||
# Overrides render :json to check if the given object implements +active_model_serializer+
|
|
||||||
# as a method. If so, use the returned serializer instead of calling +to_json+ on the object.
|
|
||||||
#
|
|
||||||
# This module also provides a serialization_scope method that allows you to configure the
|
|
||||||
# +serialization_scope+ of the serializer. Most apps will likely set the +serialization_scope+
|
|
||||||
# to the current user:
|
|
||||||
#
|
|
||||||
# class ApplicationController < ActionController::Base
|
|
||||||
# serialization_scope :current_user
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# If you need more complex scope rules, you can simply override the serialization_scope:
|
|
||||||
#
|
|
||||||
# class ApplicationController < ActionController::Base
|
|
||||||
# private
|
|
||||||
#
|
|
||||||
# def serialization_scope
|
|
||||||
# current_user
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
module Serialization
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
include ActionController::Renderers
|
|
||||||
|
|
||||||
included do
|
|
||||||
class_attribute :_serialization_scope
|
|
||||||
self._serialization_scope = :current_user
|
|
||||||
end
|
|
||||||
|
|
||||||
def serialization_scope
|
|
||||||
send(_serialization_scope) if _serialization_scope && respond_to?(_serialization_scope, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def default_serializer_options
|
|
||||||
end
|
|
||||||
|
|
||||||
def _render_option_json(resource, options)
|
|
||||||
json = ActiveModel::Serializer.build_json(self, resource, options)
|
|
||||||
|
|
||||||
if json
|
|
||||||
super(json, options)
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
def serialization_scope(scope)
|
|
||||||
self._serialization_scope = scope
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
require 'active_model/serializable'
|
|
||||||
require 'active_model/serializer/caching'
|
|
||||||
require "active_support/core_ext/class/attribute"
|
|
||||||
require 'active_support/dependencies'
|
|
||||||
require 'active_support/descendants_tracker'
|
|
||||||
|
|
||||||
module ActiveModel
|
|
||||||
# Active Model Array Serializer
|
|
||||||
#
|
|
||||||
# Serializes an Array, checking if each element implements
|
|
||||||
# the +active_model_serializer+ method.
|
|
||||||
#
|
|
||||||
# To disable serialization of root elements:
|
|
||||||
#
|
|
||||||
# ActiveModel::ArraySerializer.root = false
|
|
||||||
#
|
|
||||||
class ArraySerializer
|
|
||||||
extend ActiveSupport::DescendantsTracker
|
|
||||||
|
|
||||||
include ActiveModel::Serializable
|
|
||||||
include ActiveModel::Serializer::Caching
|
|
||||||
|
|
||||||
attr_reader :object, :options
|
|
||||||
|
|
||||||
class_attribute :root
|
|
||||||
|
|
||||||
class_attribute :cache
|
|
||||||
class_attribute :perform_caching
|
|
||||||
|
|
||||||
class << self
|
|
||||||
# set perform caching like root
|
|
||||||
def cached(value = true)
|
|
||||||
self.perform_caching = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(object, options={})
|
|
||||||
@object = object
|
|
||||||
@options = options
|
|
||||||
end
|
|
||||||
|
|
||||||
def serialize_object
|
|
||||||
serializable_array
|
|
||||||
end
|
|
||||||
|
|
||||||
def serializable_array
|
|
||||||
object.map do |item|
|
|
||||||
if options.has_key? :each_serializer
|
|
||||||
serializer = options[:each_serializer]
|
|
||||||
elsif item.respond_to?(:active_model_serializer)
|
|
||||||
serializer = item.active_model_serializer
|
|
||||||
end
|
|
||||||
serializer ||= DefaultSerializer
|
|
||||||
|
|
||||||
serializable = serializer.new(item, options.merge(root: nil))
|
|
||||||
|
|
||||||
if serializable.respond_to?(:serializable_hash)
|
|
||||||
serializable.serializable_hash
|
|
||||||
else
|
|
||||||
serializable.as_json
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
require 'active_support/core_ext/object/to_json'
|
|
||||||
|
|
||||||
module ActiveModel
|
|
||||||
# Enable classes to Classes including this module to serialize themselves by implementing a serialize method and an options method.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
#
|
|
||||||
# require 'active_model_serializers'
|
|
||||||
#
|
|
||||||
# class MySerializer
|
|
||||||
# include ActiveModel::Serializable
|
|
||||||
#
|
|
||||||
# def initialize
|
|
||||||
# @options = {}
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# attr_reader :options
|
|
||||||
#
|
|
||||||
# def serialize
|
|
||||||
# { a: 1 }
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# puts MySerializer.new.to_json
|
|
||||||
module Serializable
|
|
||||||
def as_json(args={})
|
|
||||||
if root = args[:root] || options[:root]
|
|
||||||
options[:hash] = hash = {}
|
|
||||||
options[:unique_values] = {}
|
|
||||||
|
|
||||||
hash.merge!(root => serialize)
|
|
||||||
include_meta hash
|
|
||||||
hash
|
|
||||||
else
|
|
||||||
serialize
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def include_meta(hash)
|
|
||||||
hash[meta_key] = options[:meta] if options.has_key?(:meta)
|
|
||||||
end
|
|
||||||
|
|
||||||
def meta_key
|
|
||||||
options[:meta_key].try(:to_sym) || :meta
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,475 +0,0 @@
|
|||||||
require 'active_model/serializable'
|
|
||||||
require 'active_model/serializer/caching'
|
|
||||||
require "active_support/core_ext/class/attribute"
|
|
||||||
require "active_support/core_ext/module/anonymous"
|
|
||||||
require 'active_support/dependencies'
|
|
||||||
require 'active_support/descendants_tracker'
|
|
||||||
|
|
||||||
module ActiveModel
|
|
||||||
# Active Model Serializer
|
|
||||||
#
|
|
||||||
# Provides a basic serializer implementation that allows you to easily
|
|
||||||
# control how a given object is going to be serialized. On initialization,
|
|
||||||
# it expects two objects as arguments, a resource and options. For example,
|
|
||||||
# one may do in a controller:
|
|
||||||
#
|
|
||||||
# PostSerializer.new(@post, scope: current_user).to_json
|
|
||||||
#
|
|
||||||
# The object to be serialized is the +@post+ and the current user is passed
|
|
||||||
# in for authorization purposes.
|
|
||||||
#
|
|
||||||
# We use the scope to check if a given attribute should be serialized or not.
|
|
||||||
# For example, some attributes may only be returned if +current_user+ is the
|
|
||||||
# author of the post:
|
|
||||||
#
|
|
||||||
# class PostSerializer < ActiveModel::Serializer
|
|
||||||
# attributes :title, :body
|
|
||||||
# has_many :comments
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
#
|
|
||||||
# def attributes
|
|
||||||
# hash = super
|
|
||||||
# hash.merge!(email: post.email) if author?
|
|
||||||
# hash
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def author?
|
|
||||||
# post.author == scope
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
class Serializer
|
|
||||||
extend ActiveSupport::DescendantsTracker
|
|
||||||
|
|
||||||
include ActiveModel::Serializable
|
|
||||||
include ActiveModel::Serializer::Caching
|
|
||||||
|
|
||||||
INCLUDE_METHODS = {}
|
|
||||||
INSTRUMENT = { serialize: :"serialize.serializer", associations: :"associations.serializer" }
|
|
||||||
|
|
||||||
class IncludeError < StandardError
|
|
||||||
attr_reader :source, :association
|
|
||||||
|
|
||||||
def initialize(source, association)
|
|
||||||
@source, @association = source, association
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
"Cannot serialize #{association} when #{source} does not have a root!"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class_attribute :_attributes
|
|
||||||
self._attributes = {}
|
|
||||||
|
|
||||||
class_attribute :_associations
|
|
||||||
self._associations = {}
|
|
||||||
|
|
||||||
class_attribute :_root
|
|
||||||
class_attribute :_embed
|
|
||||||
self._embed = :objects
|
|
||||||
class_attribute :_root_embed
|
|
||||||
|
|
||||||
class_attribute :cache
|
|
||||||
class_attribute :perform_caching
|
|
||||||
|
|
||||||
class << self
|
|
||||||
def cached(value = true)
|
|
||||||
self.perform_caching = value
|
|
||||||
end
|
|
||||||
|
|
||||||
# Define attributes to be used in the serialization.
|
|
||||||
def attributes(*attrs)
|
|
||||||
|
|
||||||
self._attributes = _attributes.dup
|
|
||||||
|
|
||||||
attrs.each do |attr|
|
|
||||||
if Hash === attr
|
|
||||||
attr.each {|attr_real, key| attribute(attr_real, key: key) }
|
|
||||||
else
|
|
||||||
attribute attr
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def attribute(attr, options={})
|
|
||||||
self._attributes = _attributes.merge(attr.is_a?(Hash) ? attr : {attr => options[:key] || attr.to_s.gsub(/\?$/, '').to_sym})
|
|
||||||
|
|
||||||
attr = attr.keys[0] if attr.is_a? Hash
|
|
||||||
|
|
||||||
unless method_defined?(attr)
|
|
||||||
define_method attr do
|
|
||||||
object.read_attribute_for_serialization(attr.to_sym)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
define_include_method attr
|
|
||||||
|
|
||||||
# protect inheritance chains and open classes
|
|
||||||
# if a serializer inherits from another OR
|
|
||||||
# attributes are added later in a classes lifecycle
|
|
||||||
# poison the cache
|
|
||||||
define_method :_fast_attributes do
|
|
||||||
raise NameError
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
def associate(klass, attrs) #:nodoc:
|
|
||||||
options = attrs.extract_options!
|
|
||||||
self._associations = _associations.dup
|
|
||||||
|
|
||||||
attrs.each do |attr|
|
|
||||||
unless method_defined?(attr)
|
|
||||||
define_method attr do
|
|
||||||
object.send attr
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
define_include_method attr
|
|
||||||
|
|
||||||
self._associations[attr] = [klass, options]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def define_include_method(name)
|
|
||||||
method = "include_#{name}?".to_sym
|
|
||||||
|
|
||||||
INCLUDE_METHODS[name] = method
|
|
||||||
|
|
||||||
unless method_defined?(method)
|
|
||||||
define_method method do
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Defines an association in the object should be rendered.
|
|
||||||
#
|
|
||||||
# The serializer object should implement the association name
|
|
||||||
# as a method which should return an array when invoked. If a method
|
|
||||||
# with the association name does not exist, the association name is
|
|
||||||
# dispatched to the serialized object.
|
|
||||||
def has_many(*attrs)
|
|
||||||
associate(Association::HasMany, attrs)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Defines an association in the object should be rendered.
|
|
||||||
#
|
|
||||||
# The serializer object should implement the association name
|
|
||||||
# as a method which should return an object when invoked. If a method
|
|
||||||
# with the association name does not exist, the association name is
|
|
||||||
# dispatched to the serialized object.
|
|
||||||
def has_one(*attrs)
|
|
||||||
associate(Association::HasOne, attrs)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return a schema hash for the current serializer. This information
|
|
||||||
# can be used to generate clients for the serialized output.
|
|
||||||
#
|
|
||||||
# The schema hash has two keys: +attributes+ and +associations+.
|
|
||||||
#
|
|
||||||
# The +attributes+ hash looks like this:
|
|
||||||
#
|
|
||||||
# { name: :string, age: :integer }
|
|
||||||
#
|
|
||||||
# The +associations+ hash looks like this:
|
|
||||||
# { posts: { has_many: :posts } }
|
|
||||||
#
|
|
||||||
# If :key is used:
|
|
||||||
#
|
|
||||||
# class PostsSerializer < ActiveModel::Serializer
|
|
||||||
# has_many :posts, key: :my_posts
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# the hash looks like this:
|
|
||||||
#
|
|
||||||
# { my_posts: { has_many: :posts }
|
|
||||||
#
|
|
||||||
# This information is extracted from the serializer's model class,
|
|
||||||
# which is provided by +SerializerClass.model_class+.
|
|
||||||
#
|
|
||||||
# The schema method uses the +columns_hash+ and +reflect_on_association+
|
|
||||||
# methods, provided by default by ActiveRecord. You can implement these
|
|
||||||
# methods on your custom models if you want the serializer's schema method
|
|
||||||
# to work.
|
|
||||||
#
|
|
||||||
# TODO: This is currently coupled to Active Record. We need to
|
|
||||||
# figure out a way to decouple those two.
|
|
||||||
def schema
|
|
||||||
klass = model_class
|
|
||||||
columns = klass.columns_hash
|
|
||||||
|
|
||||||
attrs = {}
|
|
||||||
_attributes.each do |name, key|
|
|
||||||
if column = columns[name.to_s]
|
|
||||||
attrs[key] = column.type
|
|
||||||
else
|
|
||||||
# Computed attribute (method on serializer or model). We cannot
|
|
||||||
# infer the type, so we put nil, unless specified in the attribute declaration
|
|
||||||
if name != key
|
|
||||||
attrs[name] = key
|
|
||||||
else
|
|
||||||
attrs[key] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
associations = {}
|
|
||||||
_associations.each do |attr, (association_class, options)|
|
|
||||||
association = association_class.new(attr, options)
|
|
||||||
|
|
||||||
if model_association = klass.reflect_on_association(association.name)
|
|
||||||
# Real association.
|
|
||||||
associations[association.key] = { model_association.macro => model_association.name }
|
|
||||||
else
|
|
||||||
# Computed association. We could infer has_many vs. has_one from
|
|
||||||
# the association class, but that would make it different from
|
|
||||||
# real associations, which read has_one vs. belongs_to from the
|
|
||||||
# model.
|
|
||||||
associations[association.key] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
{ attributes: attrs, associations: associations }
|
|
||||||
end
|
|
||||||
|
|
||||||
# The model class associated with this serializer.
|
|
||||||
def model_class
|
|
||||||
name.sub(/Serializer$/, '').constantize
|
|
||||||
end
|
|
||||||
|
|
||||||
# Define how associations should be embedded.
|
|
||||||
#
|
|
||||||
# embed :objects # Embed associations as full objects
|
|
||||||
# embed :ids # Embed only the association ids
|
|
||||||
# embed :ids, include: true # Embed the association ids and include objects in the root
|
|
||||||
#
|
|
||||||
def embed(type, options={})
|
|
||||||
self._embed = type
|
|
||||||
self._root_embed = true if options[:include]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Defines the root used on serialization. If false, disables the root.
|
|
||||||
def root(name)
|
|
||||||
self._root = name
|
|
||||||
end
|
|
||||||
alias_method :root=, :root
|
|
||||||
|
|
||||||
# Used internally to create a new serializer object based on controller
|
|
||||||
# settings and options for a given resource. These settings are typically
|
|
||||||
# set during the request lifecycle or by the controller class, and should
|
|
||||||
# not be manually defined for this method.
|
|
||||||
def build_json(controller, resource, options)
|
|
||||||
default_options = controller.send(:default_serializer_options) || {}
|
|
||||||
options = default_options.merge(options || {})
|
|
||||||
|
|
||||||
serializer = options.delete(:serializer) ||
|
|
||||||
(resource.respond_to?(:active_model_serializer) &&
|
|
||||||
resource.active_model_serializer)
|
|
||||||
|
|
||||||
return serializer unless serializer
|
|
||||||
|
|
||||||
if resource.respond_to?(:to_ary)
|
|
||||||
unless serializer <= ActiveModel::ArraySerializer
|
|
||||||
raise ArgumentError.new("#{serializer.name} is not an ArraySerializer. " +
|
|
||||||
"You may want to use the :each_serializer option instead.")
|
|
||||||
end
|
|
||||||
|
|
||||||
if options[:root] != false && serializer.root != false
|
|
||||||
# the serializer for an Array is ActiveModel::ArraySerializer
|
|
||||||
options[:root] ||= serializer.root || controller.controller_name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
options[:scope] = controller.serialization_scope unless options.has_key?(:scope)
|
|
||||||
options[:scope_name] = controller._serialization_scope unless options.has_key?(:scope_name)
|
|
||||||
options[:url_options] = controller.url_options
|
|
||||||
|
|
||||||
serializer.new(resource, options)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :object, :options
|
|
||||||
|
|
||||||
def initialize(object, options={})
|
|
||||||
@object, @options = object, options
|
|
||||||
|
|
||||||
scope_name = @options[:scope_name]
|
|
||||||
if scope_name && !respond_to?(scope_name)
|
|
||||||
self.class.class_eval do
|
|
||||||
define_method scope_name, lambda { scope }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def root_name
|
|
||||||
return false if self._root == false
|
|
||||||
|
|
||||||
class_name = self.class.name.demodulize.underscore.sub(/_serializer$/, '').to_sym unless self.class.name.blank?
|
|
||||||
|
|
||||||
if self._root == true
|
|
||||||
class_name
|
|
||||||
else
|
|
||||||
self._root || class_name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def url_options
|
|
||||||
@options[:url_options] || {}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a json representation of the serializable
|
|
||||||
# object including the root.
|
|
||||||
def as_json(args={})
|
|
||||||
super(root: args.fetch(:root, options.fetch(:root, root_name)))
|
|
||||||
end
|
|
||||||
|
|
||||||
def serialize_object
|
|
||||||
serializable_hash
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a hash representation of the serializable
|
|
||||||
# object without the root.
|
|
||||||
def serializable_hash
|
|
||||||
return nil if @object.nil?
|
|
||||||
@node = attributes
|
|
||||||
include_associations! if _embed
|
|
||||||
@node
|
|
||||||
end
|
|
||||||
|
|
||||||
def include_associations!
|
|
||||||
_associations.each_key do |name|
|
|
||||||
include!(name) if include?(name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def include?(name)
|
|
||||||
return false if @options.key?(:only) && !Array(@options[:only]).include?(name)
|
|
||||||
return false if @options.key?(:except) && Array(@options[:except]).include?(name)
|
|
||||||
send INCLUDE_METHODS[name]
|
|
||||||
end
|
|
||||||
|
|
||||||
def include!(name, options={})
|
|
||||||
hash = @options[:hash]
|
|
||||||
unique_values = @options[:unique_values] ||= {}
|
|
||||||
|
|
||||||
node = options[:node] ||= @node
|
|
||||||
value = options[:value]
|
|
||||||
|
|
||||||
if options[:include] == nil
|
|
||||||
if @options.key?(:include)
|
|
||||||
options[:include] = @options[:include].include?(name)
|
|
||||||
elsif @options.include?(:exclude)
|
|
||||||
options[:include] = !@options[:exclude].include?(name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
klass, klass_options = _associations[name]
|
|
||||||
association_class =
|
|
||||||
if klass
|
|
||||||
options = klass_options.merge options
|
|
||||||
klass
|
|
||||||
elsif value.respond_to?(:to_ary)
|
|
||||||
Association::HasMany
|
|
||||||
else
|
|
||||||
Association::HasOne
|
|
||||||
end
|
|
||||||
|
|
||||||
options = default_embed_options.merge!(options)
|
|
||||||
options[:value] ||= send(name)
|
|
||||||
association = association_class.new(name, options, self.options)
|
|
||||||
|
|
||||||
if association.embed_ids?
|
|
||||||
node[association.key] = association.serialize_ids
|
|
||||||
|
|
||||||
if association.embed_in_root? && hash.nil?
|
|
||||||
raise IncludeError.new(self.class, association.name)
|
|
||||||
elsif association.embed_in_root? && association.embeddable?
|
|
||||||
merge_association hash, association.root, association.serializables, unique_values
|
|
||||||
end
|
|
||||||
elsif association.embed_objects?
|
|
||||||
node[association.key] = association.serialize
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# In some cases, an Array of associations is built by merging the associated
|
|
||||||
# content for all of the children. For instance, if a Post has_many comments,
|
|
||||||
# which has_many tags, the top-level :tags key will contain the merged list
|
|
||||||
# of all tags for all comments of the post.
|
|
||||||
#
|
|
||||||
# In order to make this efficient, we store a :unique_values hash containing
|
|
||||||
# a unique list of all of the objects that are already in the Array. This
|
|
||||||
# avoids the need to scan through the Array looking for entries every time
|
|
||||||
# we want to merge a new list of values.
|
|
||||||
def merge_association(hash, key, serializables, unique_values)
|
|
||||||
already_serialized = (unique_values[key] ||= {})
|
|
||||||
serializable_hashes = (hash[key] ||= [])
|
|
||||||
|
|
||||||
serializables.each do |serializable|
|
|
||||||
unless already_serialized.include? serializable.object
|
|
||||||
already_serialized[serializable.object] = true
|
|
||||||
serializable_hashes << serializable.serializable_hash
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a hash representation of the serializable
|
|
||||||
# object attributes.
|
|
||||||
def attributes
|
|
||||||
_fast_attributes
|
|
||||||
rescue NameError
|
|
||||||
method = "def _fast_attributes\n"
|
|
||||||
|
|
||||||
method << " h = {}\n"
|
|
||||||
|
|
||||||
_attributes.each do |name,key|
|
|
||||||
method << " h[:\"#{key}\"] = read_attribute_for_serialization(:\"#{name}\") if include?(:\"#{name}\")\n"
|
|
||||||
end
|
|
||||||
method << " h\nend"
|
|
||||||
|
|
||||||
self.class.class_eval method
|
|
||||||
_fast_attributes
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns options[:scope]
|
|
||||||
def scope
|
|
||||||
@options[:scope]
|
|
||||||
end
|
|
||||||
|
|
||||||
alias :read_attribute_for_serialization :send
|
|
||||||
|
|
||||||
# Use ActiveSupport::Notifications to send events to external systems.
|
|
||||||
# The event name is: name.class_name.serializer
|
|
||||||
def instrument(name, payload = {}, &block)
|
|
||||||
event_name = INSTRUMENT[name]
|
|
||||||
ActiveSupport::Notifications.instrument(event_name, payload, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def default_embed_options
|
|
||||||
{
|
|
||||||
embed: _embed,
|
|
||||||
include: _root_embed
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# DefaultSerializer
|
|
||||||
#
|
|
||||||
# Provides a constant interface for all items, particularly
|
|
||||||
# for ArraySerializer.
|
|
||||||
class DefaultSerializer
|
|
||||||
attr_reader :object, :options
|
|
||||||
|
|
||||||
def initialize(object, options={})
|
|
||||||
@object, @options = object, options
|
|
||||||
end
|
|
||||||
|
|
||||||
def serializable_hash
|
|
||||||
@object.as_json(@options)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,185 +0,0 @@
|
|||||||
module ActiveModel
|
|
||||||
class Serializer
|
|
||||||
class Association #:nodoc:
|
|
||||||
# name: The name of the association.
|
|
||||||
#
|
|
||||||
# options: A hash. These keys are accepted:
|
|
||||||
#
|
|
||||||
# value: The object we're associating with.
|
|
||||||
#
|
|
||||||
# serializer: The class used to serialize the association.
|
|
||||||
#
|
|
||||||
# embed: Define how associations should be embedded.
|
|
||||||
# - :objects # Embed associations as full objects.
|
|
||||||
# - :ids # Embed only the association ids.
|
|
||||||
# - :ids, include: true # Embed the association ids and include objects in the root.
|
|
||||||
#
|
|
||||||
# include: Used in conjunction with embed :ids. Includes the objects in the root.
|
|
||||||
#
|
|
||||||
# root: Used in conjunction with include: true. Defines the key used to embed the objects.
|
|
||||||
#
|
|
||||||
# key: Key name used to store the ids in.
|
|
||||||
#
|
|
||||||
# embed_key: Method used to fetch ids. Defaults to :id.
|
|
||||||
#
|
|
||||||
# polymorphic: Is the association is polymorphic?. Values: true or false.
|
|
||||||
def initialize(name, options={}, serializer_options={})
|
|
||||||
@name = name
|
|
||||||
@object = options[:value]
|
|
||||||
|
|
||||||
embed = options[:embed]
|
|
||||||
@embed_ids = embed == :id || embed == :ids
|
|
||||||
@embed_objects = embed == :object || embed == :objects
|
|
||||||
@embed_key = options[:embed_key] || :id
|
|
||||||
@embed_in_root = options[:include]
|
|
||||||
|
|
||||||
serializer = options[:serializer]
|
|
||||||
@serializer_class = serializer.is_a?(String) ? serializer.constantize : serializer
|
|
||||||
|
|
||||||
@options = options
|
|
||||||
@serializer_options = serializer_options
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :object, :root, :name, :embed_ids, :embed_objects, :embed_in_root
|
|
||||||
alias embeddable? object
|
|
||||||
alias embed_objects? embed_objects
|
|
||||||
alias embed_ids? embed_ids
|
|
||||||
alias use_id_key? embed_ids?
|
|
||||||
alias embed_in_root? embed_in_root
|
|
||||||
|
|
||||||
def key
|
|
||||||
if key = options[:key]
|
|
||||||
key
|
|
||||||
elsif use_id_key?
|
|
||||||
id_key
|
|
||||||
else
|
|
||||||
name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
attr_reader :embed_key, :serializer_class, :options, :serializer_options
|
|
||||||
|
|
||||||
def find_serializable(object)
|
|
||||||
if serializer_class
|
|
||||||
serializer_class.new(object, serializer_options)
|
|
||||||
elsif object.respond_to?(:active_model_serializer) && (ams = object.active_model_serializer)
|
|
||||||
ams.new(object, serializer_options)
|
|
||||||
else
|
|
||||||
object
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class HasMany < Association #:nodoc:
|
|
||||||
def root
|
|
||||||
options[:root] || name
|
|
||||||
end
|
|
||||||
|
|
||||||
def id_key
|
|
||||||
"#{name.to_s.singularize}_ids".to_sym
|
|
||||||
end
|
|
||||||
|
|
||||||
def serializables
|
|
||||||
object.map do |item|
|
|
||||||
find_serializable(item)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def serialize
|
|
||||||
object.map do |item|
|
|
||||||
find_serializable(item).serializable_hash
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def serialize_ids
|
|
||||||
object.map do |item|
|
|
||||||
serializer = find_serializable(item)
|
|
||||||
if serializer.respond_to?(embed_key)
|
|
||||||
serializer.send(embed_key)
|
|
||||||
else
|
|
||||||
item.read_attribute_for_serialization(embed_key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class HasOne < Association #:nodoc:
|
|
||||||
def initialize(name, options={}, serializer_options={})
|
|
||||||
super
|
|
||||||
@polymorphic = options[:polymorphic]
|
|
||||||
end
|
|
||||||
|
|
||||||
def root
|
|
||||||
if root = options[:root]
|
|
||||||
root
|
|
||||||
elsif polymorphic?
|
|
||||||
object.class.to_s.pluralize.demodulize.underscore.to_sym
|
|
||||||
else
|
|
||||||
name.to_s.pluralize.to_sym
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def id_key
|
|
||||||
"#{name}_id".to_sym
|
|
||||||
end
|
|
||||||
|
|
||||||
def embeddable?
|
|
||||||
super || !polymorphic?
|
|
||||||
end
|
|
||||||
|
|
||||||
def serializables
|
|
||||||
value = object && find_serializable(object)
|
|
||||||
value ? [value] : []
|
|
||||||
end
|
|
||||||
|
|
||||||
def serialize
|
|
||||||
if object
|
|
||||||
if polymorphic?
|
|
||||||
{
|
|
||||||
:type => polymorphic_key,
|
|
||||||
polymorphic_key => find_serializable(object).serializable_hash
|
|
||||||
}
|
|
||||||
else
|
|
||||||
find_serializable(object).serializable_hash
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def serialize_ids
|
|
||||||
if object
|
|
||||||
serializer = find_serializable(object)
|
|
||||||
id =
|
|
||||||
if serializer.respond_to?(embed_key)
|
|
||||||
serializer.send(embed_key)
|
|
||||||
else
|
|
||||||
object.read_attribute_for_serialization(embed_key)
|
|
||||||
end
|
|
||||||
|
|
||||||
if polymorphic?
|
|
||||||
{
|
|
||||||
type: polymorphic_key,
|
|
||||||
id: id
|
|
||||||
}
|
|
||||||
else
|
|
||||||
id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
attr_reader :polymorphic
|
|
||||||
alias polymorphic? polymorphic
|
|
||||||
|
|
||||||
def use_id_key?
|
|
||||||
embed_ids? && !polymorphic?
|
|
||||||
end
|
|
||||||
|
|
||||||
def polymorphic_key
|
|
||||||
object.class.to_s.demodulize.underscore.to_sym
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
module ActiveModel
|
|
||||||
class Serializer
|
|
||||||
module Caching
|
|
||||||
def to_json(*args)
|
|
||||||
if caching_enabled?
|
|
||||||
key = expand_cache_key([self.class.to_s.underscore, cache_key, 'to-json'])
|
|
||||||
cache.fetch key do
|
|
||||||
super
|
|
||||||
end
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def serialize(*args)
|
|
||||||
if caching_enabled?
|
|
||||||
key = expand_cache_key([self.class.to_s.underscore, cache_key, 'serialize'])
|
|
||||||
cache.fetch key do
|
|
||||||
serialize_object
|
|
||||||
end
|
|
||||||
else
|
|
||||||
serialize_object
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def caching_enabled?
|
|
||||||
perform_caching && cache && respond_to?(:cache_key)
|
|
||||||
end
|
|
||||||
|
|
||||||
def expand_cache_key(*args)
|
|
||||||
ActiveSupport::Cache.expand_cache_key(args)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
module ActiveModel
|
|
||||||
class Serializer
|
|
||||||
VERSION = "0.8.1"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
require "active_support"
|
|
||||||
require "active_support/core_ext/string/inflections"
|
|
||||||
require "active_support/notifications"
|
|
||||||
require "active_model"
|
|
||||||
require "active_model/array_serializer"
|
|
||||||
require "active_model/serializer"
|
|
||||||
require "active_model/serializer/associations"
|
|
||||||
require "set"
|
|
||||||
|
|
||||||
if defined?(Rails)
|
|
||||||
module ActiveModel
|
|
||||||
class Railtie < Rails::Railtie
|
|
||||||
generators do |app|
|
|
||||||
Rails::Generators.configure!(app.config.generators)
|
|
||||||
Rails::Generators.hidden_namespaces.uniq!
|
|
||||||
require_relative "generators/resource_override"
|
|
||||||
end
|
|
||||||
|
|
||||||
initializer "include_routes.active_model_serializer" do |app|
|
|
||||||
ActiveSupport.on_load(:active_model_serializers) do
|
|
||||||
include AbstractController::UrlFor
|
|
||||||
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
|
|
||||||
include app.routes.mounted_helpers
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
initializer "caching.active_model_serializer" do |app|
|
|
||||||
ActiveModel::Serializer.perform_caching = app.config.action_controller.perform_caching
|
|
||||||
ActiveModel::ArraySerializer.perform_caching = app.config.action_controller.perform_caching
|
|
||||||
|
|
||||||
ActiveModel::Serializer.cache = Rails.cache
|
|
||||||
ActiveModel::ArraySerializer.cache = Rails.cache
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module ActiveModel::SerializerSupport
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
module ClassMethods #:nodoc:
|
|
||||||
if "".respond_to?(:safe_constantize)
|
|
||||||
def active_model_serializer
|
|
||||||
"#{self.name}Serializer".safe_constantize
|
|
||||||
end
|
|
||||||
else
|
|
||||||
def active_model_serializer
|
|
||||||
begin
|
|
||||||
"#{self.name}Serializer".constantize
|
|
||||||
rescue NameError => e
|
|
||||||
raise unless e.message =~ /uninitialized constant/
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a model serializer for this object considering its namespace.
|
|
||||||
def active_model_serializer
|
|
||||||
self.class.active_model_serializer
|
|
||||||
end
|
|
||||||
|
|
||||||
alias :read_attribute_for_serialization :send
|
|
||||||
end
|
|
||||||
|
|
||||||
module ActiveModel::ArraySerializerSupport
|
|
||||||
def active_model_serializer
|
|
||||||
ActiveModel::ArraySerializer
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Array.send(:include, ActiveModel::ArraySerializerSupport)
|
|
||||||
Set.send(:include, ActiveModel::ArraySerializerSupport)
|
|
||||||
|
|
||||||
{
|
|
||||||
active_record: 'ActiveRecord::Relation',
|
|
||||||
mongoid: 'Mongoid::Criteria'
|
|
||||||
}.each do |orm, rel_class|
|
|
||||||
ActiveSupport.on_load(orm) do
|
|
||||||
include ActiveModel::SerializerSupport
|
|
||||||
rel_class.constantize.send(:include, ActiveModel::ArraySerializerSupport)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
|
||||||
require 'action_controller'
|
|
||||||
require 'action_controller/serialization'
|
|
||||||
|
|
||||||
ActiveSupport.on_load(:action_controller) do
|
|
||||||
include ::ActionController::Serialization
|
|
||||||
end
|
|
||||||
rescue LoadError => ex
|
|
||||||
# rails on installed, continuing
|
|
||||||
end
|
|
||||||
|
|
||||||
ActiveSupport.run_load_hooks(:active_model_serializers, ActiveModel::Serializer)
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
# We do not recommend that you use AM::S in this way, but if you must, here
|
|
||||||
# is a mixin that overrides ActiveRecord::Base#to_json and #as_json.
|
|
||||||
|
|
||||||
module ActiveRecord
|
|
||||||
module SerializerOverride
|
|
||||||
def to_json options = {}
|
|
||||||
active_model_serializer.new(self).to_json options
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json options={}
|
|
||||||
active_model_serializer.new(self).as_json options
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Base.send(:include, SerializerOverride)
|
|
||||||
end
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
require "rails/generators"
|
|
||||||
require "rails/generators/rails/resource/resource_generator"
|
|
||||||
|
|
||||||
module Rails
|
|
||||||
module Generators
|
|
||||||
ResourceGenerator.class_eval do
|
|
||||||
def add_serializer
|
|
||||||
invoke "serializer"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
Description:
|
|
||||||
Generates a serializer for the given resource with tests.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
`rails generate serializer Account name created_at`
|
|
||||||
|
|
||||||
For TestUnit it creates:
|
|
||||||
Serializer: app/serializers/account_serializer.rb
|
|
||||||
TestUnit: test/unit/account_serializer_test.rb
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
module Rails
|
|
||||||
module Generators
|
|
||||||
class SerializerGenerator < NamedBase
|
|
||||||
source_root File.expand_path("../templates", __FILE__)
|
|
||||||
check_class_collision suffix: "Serializer"
|
|
||||||
|
|
||||||
argument :attributes, type: :array, default: [], banner: "field:type field:type"
|
|
||||||
|
|
||||||
class_option :parent, type: :string, desc: "The parent class for the generated serializer"
|
|
||||||
|
|
||||||
def create_serializer_file
|
|
||||||
template 'serializer.rb', File.join('app/serializers', class_path, "#{file_name}_serializer.rb")
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def attributes_names
|
|
||||||
[:id] + attributes.select { |attr| !attr.reference? }.map { |a| a.name.to_sym }
|
|
||||||
end
|
|
||||||
|
|
||||||
def association_names
|
|
||||||
attributes.select { |attr| attr.reference? }.map { |a| a.name.to_sym }
|
|
||||||
end
|
|
||||||
|
|
||||||
def parent_class_name
|
|
||||||
if options[:parent]
|
|
||||||
options[:parent]
|
|
||||||
elsif defined?(::ApplicationSerializer)
|
|
||||||
"ApplicationSerializer"
|
|
||||||
else
|
|
||||||
"ActiveModel::Serializer"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
<% module_namespacing do -%>
|
|
||||||
class <%= class_name %>Serializer < <%= parent_class_name %>
|
|
||||||
attributes <%= attributes_names.map(&:inspect).join(", ") %>
|
|
||||||
<% association_names.each do |attribute| -%>
|
|
||||||
has_one :<%= attribute %>
|
|
||||||
<% end -%>
|
|
||||||
end
|
|
||||||
<% end -%>
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
require "test_helper"
|
|
||||||
require "test_fakes"
|
|
||||||
|
|
||||||
class ArraySerializerTest < ActiveModel::TestCase
|
|
||||||
# serialize different typed objects
|
|
||||||
def test_array_serializer
|
|
||||||
model = Model.new
|
|
||||||
user = User.new
|
|
||||||
comments = Comment.new(title: "Comment1", id: 1)
|
|
||||||
|
|
||||||
array = [model, user, comments]
|
|
||||||
serializer = array.active_model_serializer.new(array, scope: { scope: true })
|
|
||||||
assert_equal([
|
|
||||||
{ model: "Model" },
|
|
||||||
{ last_name: "Valim", ok: true, first_name: "Jose", scope: true },
|
|
||||||
{ title: "Comment1" }
|
|
||||||
], serializer.as_json)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_array_serializer_with_root
|
|
||||||
comment1 = Comment.new(title: "Comment1", id: 1)
|
|
||||||
comment2 = Comment.new(title: "Comment2", id: 2)
|
|
||||||
|
|
||||||
array = [ comment1, comment2 ]
|
|
||||||
|
|
||||||
serializer = array.active_model_serializer.new(array, root: :comments)
|
|
||||||
|
|
||||||
assert_equal({ comments: [
|
|
||||||
{ title: "Comment1" },
|
|
||||||
{ title: "Comment2" }
|
|
||||||
]}, serializer.as_json)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_active_model_with_root
|
|
||||||
comment1 = ModelWithActiveModelSerializer.new(title: "Comment1")
|
|
||||||
comment2 = ModelWithActiveModelSerializer.new(title: "Comment2")
|
|
||||||
|
|
||||||
array = [ comment1, comment2 ]
|
|
||||||
|
|
||||||
serializer = array.active_model_serializer.new(array, root: :comments)
|
|
||||||
|
|
||||||
assert_equal({ comments: [
|
|
||||||
{ title: "Comment1" },
|
|
||||||
{ title: "Comment2" }
|
|
||||||
]}, serializer.as_json)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_array_serializer_with_hash
|
|
||||||
hash = { value: "something" }
|
|
||||||
array = [hash]
|
|
||||||
serializer = array.active_model_serializer.new(array, root: :items)
|
|
||||||
assert_equal({ items: [hash.as_json] }, serializer.as_json)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_array_serializer_with_specified_serializer
|
|
||||||
post1 = Post.new(title: "Post1", author: "Author1", id: 1)
|
|
||||||
post2 = Post.new(title: "Post2", author: "Author2", id: 2)
|
|
||||||
|
|
||||||
array = [ post1, post2 ]
|
|
||||||
|
|
||||||
serializer = array.active_model_serializer.new array, each_serializer: CustomPostSerializer
|
|
||||||
|
|
||||||
assert_equal([
|
|
||||||
{ title: "Post1" },
|
|
||||||
{ title: "Post2" }
|
|
||||||
], serializer.as_json)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_array_serializer_using_default_serializer
|
|
||||||
hash = { "value" => "something" }
|
|
||||||
class << hash
|
|
||||||
def active_model_serializer
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
array = [hash]
|
|
||||||
|
|
||||||
serializer = array.active_model_serializer.new array
|
|
||||||
|
|
||||||
assert_equal([
|
|
||||||
{ "value" => "something" }
|
|
||||||
], serializer.as_json)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,592 +0,0 @@
|
|||||||
require "test_helper"
|
|
||||||
|
|
||||||
class AssociationTest < ActiveModel::TestCase
|
|
||||||
def def_serializer(&block)
|
|
||||||
Class.new(ActiveModel::Serializer, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
class Model
|
|
||||||
def initialize(hash={})
|
|
||||||
@attributes = hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_attribute_for_serialization(name)
|
|
||||||
@attributes[name]
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json(*)
|
|
||||||
{ model: "Model" }
|
|
||||||
end
|
|
||||||
|
|
||||||
def method_missing(meth, *args)
|
|
||||||
if meth.to_s =~ /^(.*)=$/
|
|
||||||
@attributes[$1.to_sym] = args[0]
|
|
||||||
elsif @attributes.key?(meth)
|
|
||||||
@attributes[meth]
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup
|
|
||||||
@hash = {}
|
|
||||||
@root_hash = {}
|
|
||||||
|
|
||||||
@post = Model.new(title: "New Post", body: "Body")
|
|
||||||
@comment = Model.new(id: 1, external_id: "COMM001", body: "ZOMG A COMMENT")
|
|
||||||
@post.comments = [ @comment ]
|
|
||||||
@post.comment = @comment
|
|
||||||
|
|
||||||
@comment_serializer_class = def_serializer do
|
|
||||||
attributes :id, :external_id, :body
|
|
||||||
end
|
|
||||||
|
|
||||||
@post_serializer_class = def_serializer do
|
|
||||||
attributes :title, :body
|
|
||||||
end
|
|
||||||
|
|
||||||
@post_serializer = @post_serializer_class.new(@post, hash: @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def include!(key, options={})
|
|
||||||
@post_serializer.include! key, {
|
|
||||||
embed: :ids,
|
|
||||||
include: true,
|
|
||||||
node: @hash,
|
|
||||||
serializer: @comment_serializer_class
|
|
||||||
}.merge(options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def include_bare!(key, options={})
|
|
||||||
@post_serializer.include! key, {
|
|
||||||
node: @hash,
|
|
||||||
serializer: @comment_serializer_class
|
|
||||||
}.merge(options)
|
|
||||||
end
|
|
||||||
|
|
||||||
class NoDefaults < AssociationTest
|
|
||||||
def test_include_bang_has_many_associations
|
|
||||||
include! :comments, value: @post.comments
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comment_ids: [ 1 ]
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comments: [
|
|
||||||
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_include_bang_with_embed_false
|
|
||||||
include! :comments, value: @post.comments, embed: false
|
|
||||||
|
|
||||||
assert_equal({}, @hash)
|
|
||||||
assert_equal({}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_include_bang_with_embed_ids_include_false
|
|
||||||
include! :comments, value: @post.comments, embed: :ids, include: false
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comment_ids: [ 1 ]
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_include_bang_has_one_associations
|
|
||||||
include! :comment, value: @post.comment
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comment_id: 1
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comments: [{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }]
|
|
||||||
}, @root_hash)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class DefaultsTest < AssociationTest
|
|
||||||
def test_with_default_has_many
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_many :comments
|
|
||||||
end
|
|
||||||
|
|
||||||
include! :comments
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comment_ids: [ 1 ]
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comments: [
|
|
||||||
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_with_default_has_one
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_one :comment
|
|
||||||
end
|
|
||||||
|
|
||||||
include! :comment
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comment_id: 1
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comments: [
|
|
||||||
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_with_default_has_many_with_custom_key
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_many :comments, key: :custom_comments
|
|
||||||
end
|
|
||||||
|
|
||||||
include! :comments
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
custom_comments: [ 1 ]
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comments: [
|
|
||||||
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_with_default_has_one_with_custom_key
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_one :comment, key: :custom_comment_id
|
|
||||||
end
|
|
||||||
|
|
||||||
include! :comment
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
custom_comment_id: 1
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comments: [
|
|
||||||
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_with_default_has_many_with_custom_embed_key
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_many :comments, embed_key: :external_id
|
|
||||||
end
|
|
||||||
|
|
||||||
include! :comments
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comment_ids: [ "COMM001" ]
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comments: [
|
|
||||||
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_with_default_has_one_with_custom_embed_key
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_one :comment, embed_key: :external_id
|
|
||||||
end
|
|
||||||
|
|
||||||
include! :comment
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comment_id: "COMM001"
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comments: [
|
|
||||||
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_with_default_has_many_with_custom_key_and_custom_embed_key
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_many :comments, key: :custom_comments, embed_key: :external_id
|
|
||||||
end
|
|
||||||
|
|
||||||
include! :comments
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
custom_comments: [ "COMM001" ]
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comments: [
|
|
||||||
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_with_default_has_one_with_custom_key_and_custom_embed_key
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_one :comment, key: :custom_comment, embed_key: :external_id
|
|
||||||
end
|
|
||||||
|
|
||||||
include! :comment
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
custom_comment: "COMM001"
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comments: [
|
|
||||||
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_embed_objects_for_has_many_associations
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_many :comments, embed: :objects
|
|
||||||
end
|
|
||||||
|
|
||||||
include_bare! :comments
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comments: [
|
|
||||||
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_embed_ids_for_has_many_associations
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_many :comments, embed: :ids
|
|
||||||
end
|
|
||||||
|
|
||||||
include_bare! :comments
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comment_ids: [ 1 ]
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_embed_false_for_has_many_associations
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_many :comments, embed: false
|
|
||||||
end
|
|
||||||
|
|
||||||
include_bare! :comments
|
|
||||||
|
|
||||||
assert_equal({}, @hash)
|
|
||||||
assert_equal({}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_embed_ids_include_true_for_has_many_associations
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_many :comments, embed: :ids, include: true
|
|
||||||
end
|
|
||||||
|
|
||||||
include_bare! :comments
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comment_ids: [ 1 ]
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comments: [
|
|
||||||
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_embed_ids_for_has_one_associations
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_one :comment, embed: :ids
|
|
||||||
end
|
|
||||||
|
|
||||||
include_bare! :comment
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comment_id: 1
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_embed_false_for_has_one_associations
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_one :comment, embed: false
|
|
||||||
end
|
|
||||||
|
|
||||||
include_bare! :comment
|
|
||||||
|
|
||||||
assert_equal({}, @hash)
|
|
||||||
assert_equal({}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_embed_ids_include_true_for_has_one_associations
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_one :comment, embed: :ids, include: true
|
|
||||||
end
|
|
||||||
|
|
||||||
include_bare! :comment
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comment_id: 1
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comments: [
|
|
||||||
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, @root_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_embed_ids_include_true_does_not_serialize_multiple_times
|
|
||||||
@post.recent_comment = @comment
|
|
||||||
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_one :comment, embed: :ids, include: true
|
|
||||||
has_one :recent_comment, embed: :ids, include: true, root: :comments
|
|
||||||
end
|
|
||||||
|
|
||||||
# Count how often the @comment record is serialized.
|
|
||||||
serialized_times = 0
|
|
||||||
@comment.class_eval do
|
|
||||||
define_method :read_attribute_for_serialization, lambda { |name|
|
|
||||||
serialized_times += 1 if name == :body
|
|
||||||
super(name)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
include_bare! :comment
|
|
||||||
include_bare! :recent_comment
|
|
||||||
|
|
||||||
assert_equal 1, serialized_times
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_include_with_read_association_id_for_serialization_hook
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_one :comment, embed: :ids, include: true
|
|
||||||
end
|
|
||||||
|
|
||||||
association_name = nil
|
|
||||||
@post.class_eval do
|
|
||||||
define_method :read_attribute_for_serialization, lambda { |name|
|
|
||||||
association_name = name
|
|
||||||
send(name)
|
|
||||||
}
|
|
||||||
define_method :comment_id, lambda {
|
|
||||||
@attributes[:comment].id
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
include_bare! :comment
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comment_id: 1
|
|
||||||
}, @hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_include_with_read_association_ids_for_serialization_hook
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_many :comments, embed: :ids, include: false
|
|
||||||
end
|
|
||||||
|
|
||||||
association_name = nil
|
|
||||||
@post.class_eval do
|
|
||||||
define_method :read_attribute_for_serialization, lambda { |name|
|
|
||||||
association_name = name
|
|
||||||
send(name)
|
|
||||||
}
|
|
||||||
define_method :comment_ids, lambda {
|
|
||||||
@attributes[:comments].map(&:id)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
include_bare! :comments
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comment_ids: [1]
|
|
||||||
}, @hash)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class RecursiveTest < AssociationTest
|
|
||||||
class BarSerializer < ActiveModel::Serializer; end
|
|
||||||
|
|
||||||
class FooSerializer < ActiveModel::Serializer
|
|
||||||
root :foos
|
|
||||||
attributes :id
|
|
||||||
has_many :bars, serializer: BarSerializer, root: :bars, embed: :ids, include: true
|
|
||||||
end
|
|
||||||
|
|
||||||
class BarSerializer < ActiveModel::Serializer
|
|
||||||
root :bars
|
|
||||||
attributes :id
|
|
||||||
has_many :foos, serializer: FooSerializer, root: :foos, embed: :ids, include: true
|
|
||||||
end
|
|
||||||
|
|
||||||
class Foo < Model
|
|
||||||
def active_model_serializer; FooSerializer; end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Bar < Model
|
|
||||||
def active_model_serializer; BarSerializer; end
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup
|
|
||||||
super
|
|
||||||
|
|
||||||
foo = Foo.new(id: 1)
|
|
||||||
bar = Bar.new(id: 2)
|
|
||||||
|
|
||||||
foo.bars = [ bar ]
|
|
||||||
bar.foos = [ foo ]
|
|
||||||
|
|
||||||
collection = [ foo ]
|
|
||||||
|
|
||||||
@serializer = collection.active_model_serializer.new(collection, root: :foos)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_mutual_relation_result
|
|
||||||
assert_equal({
|
|
||||||
foos: [{
|
|
||||||
bar_ids: [ 2 ],
|
|
||||||
id: 1
|
|
||||||
}],
|
|
||||||
bars: [{
|
|
||||||
foo_ids: [ 1 ],
|
|
||||||
id: 2
|
|
||||||
}]
|
|
||||||
}, @serializer.as_json)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_mutual_relation_does_not_raise_error
|
|
||||||
assert_nothing_raised SystemStackError, 'stack level too deep' do
|
|
||||||
@serializer.as_json
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class InclusionTest < AssociationTest
|
|
||||||
def setup
|
|
||||||
super
|
|
||||||
|
|
||||||
comment_serializer_class = @comment_serializer_class
|
|
||||||
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
root :post
|
|
||||||
embed :ids, include: true
|
|
||||||
has_many :comments, serializer: comment_serializer_class
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_when_it_is_included
|
|
||||||
post_serializer = @post_serializer_class.new(
|
|
||||||
@post, include: [:comments]
|
|
||||||
)
|
|
||||||
|
|
||||||
json = post_serializer.as_json
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
post: {
|
|
||||||
title: "New Post",
|
|
||||||
body: "Body",
|
|
||||||
comment_ids: [ 1 ]
|
|
||||||
},
|
|
||||||
comments: [
|
|
||||||
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, json)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_when_it_is_not_included
|
|
||||||
post_serializer = @post_serializer_class.new(
|
|
||||||
@post, include: []
|
|
||||||
)
|
|
||||||
|
|
||||||
json = post_serializer.as_json
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
post: {
|
|
||||||
title: "New Post",
|
|
||||||
body: "Body",
|
|
||||||
comment_ids: [ 1 ]
|
|
||||||
}
|
|
||||||
}, json)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_when_it_is_excluded
|
|
||||||
post_serializer = @post_serializer_class.new(
|
|
||||||
@post, exclude: [:comments]
|
|
||||||
)
|
|
||||||
|
|
||||||
json = post_serializer.as_json
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
post: {
|
|
||||||
title: "New Post",
|
|
||||||
body: "Body",
|
|
||||||
comment_ids: [ 1 ]
|
|
||||||
}
|
|
||||||
}, json)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_when_it_is_not_excluded
|
|
||||||
post_serializer = @post_serializer_class.new(
|
|
||||||
@post, exclude: []
|
|
||||||
)
|
|
||||||
|
|
||||||
json = post_serializer.as_json
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
post: {
|
|
||||||
title: "New Post",
|
|
||||||
body: "Body",
|
|
||||||
comment_ids: [ 1 ]
|
|
||||||
},
|
|
||||||
comments: [
|
|
||||||
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, json)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class StringSerializerOption < AssociationTest
|
|
||||||
class StringSerializer < ActiveModel::Serializer
|
|
||||||
attributes :id, :body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_specifying_serializer_class_as_string
|
|
||||||
@post_serializer_class.class_eval do
|
|
||||||
has_many :comments, embed: :objects
|
|
||||||
end
|
|
||||||
|
|
||||||
include_bare! :comments, serializer: "AssociationTest::StringSerializerOption::StringSerializer"
|
|
||||||
|
|
||||||
assert_equal({
|
|
||||||
comments: [
|
|
||||||
{ id: 1, body: "ZOMG A COMMENT" }
|
|
||||||
]
|
|
||||||
}, @hash)
|
|
||||||
|
|
||||||
assert_equal({}, @root_hash)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
require "test_helper"
|
|
||||||
|
|
||||||
class CachingTest < ActiveModel::TestCase
|
|
||||||
class NullStore
|
|
||||||
def fetch(key)
|
|
||||||
return store[key] if store[key]
|
|
||||||
|
|
||||||
store[key] = yield
|
|
||||||
end
|
|
||||||
|
|
||||||
def clear
|
|
||||||
store.clear
|
|
||||||
end
|
|
||||||
|
|
||||||
def store
|
|
||||||
@store ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def read(key)
|
|
||||||
store[key]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Programmer
|
|
||||||
def name
|
|
||||||
'Adam'
|
|
||||||
end
|
|
||||||
|
|
||||||
def skills
|
|
||||||
%w(ruby)
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_attribute_for_serialization(name)
|
|
||||||
send name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_serializers_have_a_cache_store
|
|
||||||
ActiveModel::Serializer.cache = NullStore.new
|
|
||||||
|
|
||||||
assert_kind_of NullStore, ActiveModel::Serializer.cache
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_serializers_can_enable_caching
|
|
||||||
serializer = Class.new(ActiveModel::Serializer) do
|
|
||||||
cached true
|
|
||||||
end
|
|
||||||
|
|
||||||
assert serializer.perform_caching
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_serializers_use_cache
|
|
||||||
serializer = Class.new(ActiveModel::Serializer) do
|
|
||||||
cached true
|
|
||||||
attributes :name, :skills
|
|
||||||
|
|
||||||
def self.to_s
|
|
||||||
'serializer'
|
|
||||||
end
|
|
||||||
|
|
||||||
def cache_key
|
|
||||||
object.name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
serializer.cache = NullStore.new
|
|
||||||
instance = serializer.new Programmer.new
|
|
||||||
|
|
||||||
instance.to_json
|
|
||||||
|
|
||||||
assert_equal(instance.serializable_hash, serializer.cache.read('serializer/Adam/serialize'))
|
|
||||||
assert_equal(instance.to_json, serializer.cache.read('serializer/Adam/to-json'))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_array_serializer_uses_cache
|
|
||||||
serializer = Class.new(ActiveModel::ArraySerializer) do
|
|
||||||
cached true
|
|
||||||
|
|
||||||
def self.to_s
|
|
||||||
'array_serializer'
|
|
||||||
end
|
|
||||||
|
|
||||||
def cache_key
|
|
||||||
'cache-key'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
serializer.cache = NullStore.new
|
|
||||||
instance = serializer.new [Programmer.new]
|
|
||||||
|
|
||||||
instance.to_json
|
|
||||||
|
|
||||||
assert_equal instance.serializable_array, serializer.cache.read('array_serializer/cache-key/serialize')
|
|
||||||
assert_equal instance.to_json, serializer.cache.read('array_serializer/cache-key/to-json')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
require 'test_helper'
|
|
||||||
|
|
||||||
class Foo < Rails::Application
|
|
||||||
if Rails.version.to_s.start_with? '4'
|
|
||||||
config.eager_load = false
|
|
||||||
config.secret_key_base = 'abc123'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Rails.application.load_generators
|
|
||||||
|
|
||||||
require 'generators/serializer/serializer_generator'
|
|
||||||
|
|
||||||
class SerializerGeneratorTest < Rails::Generators::TestCase
|
|
||||||
destination File.expand_path("../tmp", __FILE__)
|
|
||||||
setup :prepare_destination
|
|
||||||
|
|
||||||
tests Rails::Generators::SerializerGenerator
|
|
||||||
arguments %w(account name:string description:text business:references)
|
|
||||||
|
|
||||||
def test_generates_a_serializer
|
|
||||||
run_generator
|
|
||||||
assert_file "app/serializers/account_serializer.rb", /class AccountSerializer < ActiveModel::Serializer/
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_generates_a_namespaced_serializer
|
|
||||||
run_generator ["admin/account"]
|
|
||||||
assert_file "app/serializers/admin/account_serializer.rb", /class Admin::AccountSerializer < ActiveModel::Serializer/
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_uses_application_serializer_if_one_exists
|
|
||||||
Object.const_set(:ApplicationSerializer, Class.new)
|
|
||||||
run_generator
|
|
||||||
assert_file "app/serializers/account_serializer.rb", /class AccountSerializer < ApplicationSerializer/
|
|
||||||
ensure
|
|
||||||
Object.send :remove_const, :ApplicationSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
# def test_uses_namespace_application_serializer_if_one_exists
|
|
||||||
# Object.const_set(:SerializerNamespace, Module.new)
|
|
||||||
# SerializerNamespace.const_set(:ApplicationSerializer, Class.new)
|
|
||||||
# Rails::Generators.namespace = SerializerNamespace
|
|
||||||
# run_generator
|
|
||||||
# assert_file "app/serializers/serializer_namespace/account_serializer.rb",
|
|
||||||
# /module SerializerNamespace\n class AccountSerializer < ApplicationSerializer/
|
|
||||||
# ensure
|
|
||||||
# Object.send :remove_const, :SerializerNamespace
|
|
||||||
# Rails::Generators.namespace = nil
|
|
||||||
# end
|
|
||||||
|
|
||||||
def test_uses_given_parent
|
|
||||||
Object.const_set(:ApplicationSerializer, Class.new)
|
|
||||||
run_generator ["Account", "--parent=MySerializer"]
|
|
||||||
assert_file "app/serializers/account_serializer.rb", /class AccountSerializer < MySerializer/
|
|
||||||
ensure
|
|
||||||
Object.send :remove_const, :ApplicationSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_generates_attributes_and_associations
|
|
||||||
run_generator
|
|
||||||
assert_file "app/serializers/account_serializer.rb" do |serializer|
|
|
||||||
assert_match(/^ attributes :id, :name, :description$/, serializer)
|
|
||||||
assert_match(/^ has_one :business$/, serializer)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_with_no_attributes_does_not_add_extra_space
|
|
||||||
run_generator ["account"]
|
|
||||||
assert_file "app/serializers/account_serializer.rb" do |content|
|
|
||||||
assert_no_match /\n\nend/, content
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
require "test_helper"
|
|
||||||
|
|
||||||
class NoSerializationScopeTest < ActionController::TestCase
|
|
||||||
class ScopeSerializer
|
|
||||||
def initialize(object, options)
|
|
||||||
@object, @options = object, options
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json(*)
|
|
||||||
{ scope: @options[:scope].as_json }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class ScopeSerializable
|
|
||||||
def active_model_serializer
|
|
||||||
ScopeSerializer
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class NoSerializationScopeController < ActionController::Base
|
|
||||||
serialization_scope nil
|
|
||||||
|
|
||||||
def index
|
|
||||||
render json: ScopeSerializable.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
tests NoSerializationScopeController
|
|
||||||
|
|
||||||
def test_disabled_serialization_scope
|
|
||||||
get :index, format: :json
|
|
||||||
assert_equal '{"scope":null}', @response.body
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
require 'test_helper'
|
|
||||||
require 'pathname'
|
|
||||||
|
|
||||||
class DefaultScopeNameTest < ActionController::TestCase
|
|
||||||
TestUser = Struct.new(:name, :admin)
|
|
||||||
|
|
||||||
class UserSerializer < ActiveModel::Serializer
|
|
||||||
attributes :admin?
|
|
||||||
def admin?
|
|
||||||
current_user.admin
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class UserTestController < ActionController::Base
|
|
||||||
protect_from_forgery
|
|
||||||
|
|
||||||
before_filter { request.format = :json }
|
|
||||||
|
|
||||||
def current_user
|
|
||||||
TestUser.new('Pete', false)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_new_user
|
|
||||||
render json: TestUser.new('pete', false), serializer: UserSerializer
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
tests UserTestController
|
|
||||||
|
|
||||||
def test_default_scope_name
|
|
||||||
get :render_new_user
|
|
||||||
assert_equal '{"user":{"admin":false}}', @response.body
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class SerializationScopeNameTest < ActionController::TestCase
|
|
||||||
TestUser = Struct.new(:name, :admin)
|
|
||||||
|
|
||||||
class AdminUserSerializer < ActiveModel::Serializer
|
|
||||||
attributes :admin?
|
|
||||||
def admin?
|
|
||||||
current_admin.admin
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class AdminUserTestController < ActionController::Base
|
|
||||||
protect_from_forgery
|
|
||||||
|
|
||||||
serialization_scope :current_admin
|
|
||||||
before_filter { request.format = :json }
|
|
||||||
|
|
||||||
def current_admin
|
|
||||||
TestUser.new('Bob', true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_new_user
|
|
||||||
render json: TestUser.new('pete', false), serializer: AdminUserSerializer
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
tests AdminUserTestController
|
|
||||||
|
|
||||||
def test_override_scope_name_with_controller
|
|
||||||
get :render_new_user
|
|
||||||
assert_equal '{"admin_user":{"admin":true}}', @response.body
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class SerializationActionScopeOverrideTest < ActionController::TestCase
|
|
||||||
TestUser = Struct.new(:name, :admin)
|
|
||||||
|
|
||||||
class AdminUserSerializer < ActiveModel::Serializer
|
|
||||||
attributes :admin?
|
|
||||||
def admin?
|
|
||||||
current_admin.admin
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class AdminUserTestController < ActionController::Base
|
|
||||||
protect_from_forgery
|
|
||||||
before_filter { request.format = :json }
|
|
||||||
|
|
||||||
def current_admin
|
|
||||||
TestUser.new('Bob', true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_new_user
|
|
||||||
render json: TestUser.new('pete', false), serializer: AdminUserSerializer, scope: current_admin, scope_name: :current_admin
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
tests AdminUserTestController
|
|
||||||
|
|
||||||
def test_override_scope_name_with_controller
|
|
||||||
get :render_new_user
|
|
||||||
assert_equal '{"admin_user":{"admin":true}}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@ -1,394 +0,0 @@
|
|||||||
require 'test_helper'
|
|
||||||
require 'pathname'
|
|
||||||
|
|
||||||
class RenderJsonTest < ActionController::TestCase
|
|
||||||
class JsonRenderable
|
|
||||||
def as_json(options={})
|
|
||||||
hash = { a: :b, c: :d, e: :f }
|
|
||||||
hash.except!(*options[:except]) if options[:except]
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_json(options = {})
|
|
||||||
super except: [:c, :e]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class JsonSerializer
|
|
||||||
def initialize(object, options={})
|
|
||||||
@object, @options = object, options
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json(*)
|
|
||||||
hash = { object: serializable_hash, scope: @options[:scope].as_json }
|
|
||||||
hash.merge!(options: true) if @options[:options]
|
|
||||||
hash.merge!(check_defaults: true) if @options[:check_defaults]
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def serializable_hash
|
|
||||||
@object.as_json
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class JsonSerializable
|
|
||||||
def initialize(skip=false)
|
|
||||||
@skip = skip
|
|
||||||
end
|
|
||||||
|
|
||||||
def active_model_serializer
|
|
||||||
JsonSerializer unless @skip
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json(*)
|
|
||||||
{ serializable_object: true }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class CustomSerializer
|
|
||||||
def initialize(*)
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json(*)
|
|
||||||
{ hello: true }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class AnotherCustomSerializer
|
|
||||||
def initialize(*)
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json(*)
|
|
||||||
{ rails: 'rocks' }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class DummyCustomSerializer < ActiveModel::Serializer
|
|
||||||
attributes :id
|
|
||||||
end
|
|
||||||
|
|
||||||
class HypermediaSerializable
|
|
||||||
def active_model_serializer
|
|
||||||
HypermediaSerializer
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class HypermediaSerializer < ActiveModel::Serializer
|
|
||||||
def as_json(*)
|
|
||||||
{ link: hypermedia_url }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class CustomArraySerializer < ActiveModel::ArraySerializer
|
|
||||||
self.root = "items"
|
|
||||||
end
|
|
||||||
|
|
||||||
class TestController < ActionController::Base
|
|
||||||
protect_from_forgery
|
|
||||||
|
|
||||||
serialization_scope :current_user
|
|
||||||
attr_reader :current_user
|
|
||||||
|
|
||||||
def self.controller_path
|
|
||||||
'test'
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_nil
|
|
||||||
render json: nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_render_to_string
|
|
||||||
render text: render_to_string(json: '[]')
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_hello_world
|
|
||||||
render json: ActiveSupport::JSON.encode(hello: 'world')
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_hello_world_with_status
|
|
||||||
render json: ActiveSupport::JSON.encode(hello: 'world'), status: 401
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_hello_world_with_callback
|
|
||||||
render json: ActiveSupport::JSON.encode(hello: 'world'), callback: 'alert'
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_with_custom_content_type
|
|
||||||
render json: ActiveSupport::JSON.encode(hello: 'world'), content_type: 'text/javascript'
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_symbol_json
|
|
||||||
render json: ActiveSupport::JSON.encode(hello: 'world')
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_nil_with_custom_serializer
|
|
||||||
render json: nil, serializer: DummyCustomSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def render_json_with_extra_options
|
|
||||||
render json: JsonRenderable.new, except: [:c, :e]
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_without_options
|
|
||||||
render json: JsonRenderable.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_with_serializer
|
|
||||||
@current_user = Struct.new(:as_json).new(current_user: true)
|
|
||||||
render json: JsonSerializable.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_with_serializer_and_implicit_root
|
|
||||||
@current_user = Struct.new(:as_json).new(current_user: true)
|
|
||||||
render json: [JsonSerializable.new]
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_with_serializer_and_options
|
|
||||||
@current_user = Struct.new(:as_json).new(current_user: true)
|
|
||||||
render json: JsonSerializable.new, options: true
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_with_serializer_and_scope_option
|
|
||||||
@current_user = Struct.new(:as_json).new(current_user: true)
|
|
||||||
scope = Struct.new(:as_json).new(current_user: false)
|
|
||||||
render json: JsonSerializable.new, scope: scope
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_with_serializer_api_but_without_serializer
|
|
||||||
@current_user = Struct.new(:as_json).new(current_user: true)
|
|
||||||
render json: JsonSerializable.new(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
# To specify a custom serializer for an object, use :serializer.
|
|
||||||
def render_json_with_custom_serializer
|
|
||||||
render json: Object.new, serializer: CustomSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
# To specify a custom serializer for each item in the Array, use :each_serializer.
|
|
||||||
def render_json_array_with_custom_serializer
|
|
||||||
render json: [Object.new], each_serializer: CustomSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_array_with_wrong_option
|
|
||||||
render json: [Object.new], serializer: CustomSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_with_links
|
|
||||||
render json: HypermediaSerializable.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_array_with_no_root
|
|
||||||
render json: [], root: false
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_empty_array
|
|
||||||
render json: []
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_json_array_with_custom_array_serializer
|
|
||||||
render json: [], serializer: CustomArraySerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
private
|
|
||||||
def default_serializer_options
|
|
||||||
defaults = {}
|
|
||||||
defaults.merge!(check_defaults: true) if params[:check_defaults]
|
|
||||||
defaults.merge!(root: :awesome) if params[:check_default_root]
|
|
||||||
defaults.merge!(scope: :current_admin) if params[:check_default_scope]
|
|
||||||
defaults.merge!(serializer: AnotherCustomSerializer) if params[:check_default_serializer]
|
|
||||||
defaults.merge!(each_serializer: AnotherCustomSerializer) if params[:check_default_each_serializer]
|
|
||||||
defaults
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
tests TestController
|
|
||||||
|
|
||||||
def setup
|
|
||||||
# enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
|
|
||||||
# a more accurate simulation of what happens in "real life".
|
|
||||||
super
|
|
||||||
@controller.logger = Logger.new(nil)
|
|
||||||
|
|
||||||
@request.host = "www.nextangle.com"
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_nil
|
|
||||||
get :render_json_nil
|
|
||||||
assert_equal 'null', @response.body
|
|
||||||
assert_equal 'application/json', @response.content_type
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_render_to_string
|
|
||||||
get :render_json_render_to_string
|
|
||||||
assert_equal '[]', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_nil_with_custom_serializer
|
|
||||||
get :render_json_nil_with_custom_serializer
|
|
||||||
assert_equal "{\"dummy_custom\":null}", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json
|
|
||||||
get :render_json_hello_world
|
|
||||||
assert_equal '{"hello":"world"}', @response.body
|
|
||||||
assert_equal 'application/json', @response.content_type
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_status
|
|
||||||
get :render_json_hello_world_with_status
|
|
||||||
assert_equal '{"hello":"world"}', @response.body
|
|
||||||
assert_equal 401, @response.status
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_callback
|
|
||||||
get :render_json_hello_world_with_callback
|
|
||||||
assert_equal 'alert({"hello":"world"})', @response.body
|
|
||||||
# For JSONP, Rails 3 uses application/json, but Rails 4 uses text/javascript
|
|
||||||
assert_match %r(application/json|text/javascript), @response.content_type.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_custom_content_type
|
|
||||||
get :render_json_with_custom_content_type
|
|
||||||
assert_equal '{"hello":"world"}', @response.body
|
|
||||||
assert_equal 'text/javascript', @response.content_type
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_symbol_json
|
|
||||||
get :render_symbol_json
|
|
||||||
assert_equal '{"hello":"world"}', @response.body
|
|
||||||
assert_equal 'application/json', @response.content_type
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_forwards_extra_options
|
|
||||||
get :render_json_with_extra_options
|
|
||||||
assert_equal '{"a":"b"}', @response.body
|
|
||||||
assert_equal 'application/json', @response.content_type
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_calls_to_json_from_object
|
|
||||||
get :render_json_without_options
|
|
||||||
assert_equal '{"a":"b"}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_serializer
|
|
||||||
get :render_json_with_serializer
|
|
||||||
assert_match '"scope":{"current_user":true}', @response.body
|
|
||||||
assert_match '"object":{"serializable_object":true}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_serializer_checking_defaults
|
|
||||||
get :render_json_with_serializer, check_defaults: true
|
|
||||||
assert_match '"scope":{"current_user":true}', @response.body
|
|
||||||
assert_match '"object":{"serializable_object":true}', @response.body
|
|
||||||
assert_match '"check_defaults":true', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_serializer_checking_default_serailizer
|
|
||||||
get :render_json_with_serializer, check_default_serializer: true
|
|
||||||
assert_match '{"rails":"rocks"}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_serializer_checking_default_scope
|
|
||||||
get :render_json_with_serializer, check_default_scope: true
|
|
||||||
assert_match '"scope":"current_admin"', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_serializer_and_implicit_root
|
|
||||||
get :render_json_with_serializer_and_implicit_root
|
|
||||||
assert_match '"test":[{"serializable_object":true}]', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_serializer_and_implicit_root_checking_default_each_serailizer
|
|
||||||
get :render_json_with_serializer_and_implicit_root, check_default_each_serializer: true
|
|
||||||
assert_match '"test":[{"rails":"rocks"}]', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_serializer_and_options
|
|
||||||
get :render_json_with_serializer_and_options
|
|
||||||
assert_match '"scope":{"current_user":true}', @response.body
|
|
||||||
assert_match '"object":{"serializable_object":true}', @response.body
|
|
||||||
assert_match '"options":true', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_serializer_and_scope_option
|
|
||||||
get :render_json_with_serializer_and_scope_option
|
|
||||||
assert_match '"scope":{"current_user":false}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_serializer_and_scope_option_checking_default_scope
|
|
||||||
get :render_json_with_serializer_and_scope_option, check_default_scope: true
|
|
||||||
assert_match '"scope":{"current_user":false}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_serializer_api_but_without_serializer
|
|
||||||
get :render_json_with_serializer_api_but_without_serializer
|
|
||||||
assert_match '{"serializable_object":true}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_custom_serializer
|
|
||||||
get :render_json_with_custom_serializer
|
|
||||||
assert_match '{"hello":true}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_custom_serializer_checking_default_serailizer
|
|
||||||
get :render_json_with_custom_serializer, check_default_serializer: true
|
|
||||||
assert_match '{"hello":true}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_array_with_custom_serializer
|
|
||||||
get :render_json_array_with_custom_serializer
|
|
||||||
assert_match '{"test":[{"hello":true}]}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_array_with_wrong_option
|
|
||||||
assert_raise ArgumentError do
|
|
||||||
get :render_json_array_with_wrong_option
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_array_with_custom_serializer_checking_default_each_serailizer
|
|
||||||
get :render_json_array_with_custom_serializer, check_default_each_serializer: true
|
|
||||||
assert_match '{"test":[{"hello":true}]}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_with_links
|
|
||||||
get :render_json_with_links
|
|
||||||
assert_match '{"link":"http://www.nextangle.com/hypermedia"}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_array_with_no_root
|
|
||||||
get :render_json_array_with_no_root
|
|
||||||
assert_equal '[]', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_array_with_no_root_checking_default_root
|
|
||||||
get :render_json_array_with_no_root, check_default_root: true
|
|
||||||
assert_equal '[]', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_empty_array
|
|
||||||
get :render_json_empty_array
|
|
||||||
assert_equal '{"test":[]}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_empty_array_checking_default_root
|
|
||||||
get :render_json_empty_array, check_default_root: true
|
|
||||||
assert_equal '{"awesome":[]}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_empty_array_with_array_serializer_root_false
|
|
||||||
ActiveModel::ArraySerializer.root = false
|
|
||||||
get :render_json_empty_array
|
|
||||||
assert_equal '[]', @response.body
|
|
||||||
ensure # teardown
|
|
||||||
ActiveModel::ArraySerializer.root = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_json_array_with_custom_array_serializer
|
|
||||||
get :render_json_array_with_custom_array_serializer
|
|
||||||
assert_equal '{"items":[]}', @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
require "test_helper"
|
|
||||||
|
|
||||||
class RandomModel
|
|
||||||
include ActiveModel::SerializerSupport
|
|
||||||
end
|
|
||||||
|
|
||||||
class OtherRandomModel
|
|
||||||
include ActiveModel::SerializerSupport
|
|
||||||
end
|
|
||||||
|
|
||||||
class OtherRandomModelSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
class RandomModelCollection
|
|
||||||
include ActiveModel::ArraySerializerSupport
|
|
||||||
end
|
|
||||||
|
|
||||||
module ActiveRecord
|
|
||||||
class Relation
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module Mongoid
|
|
||||||
class Criteria
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class SerializerSupportTest < ActiveModel::TestCase
|
|
||||||
test "it returns nil if no serializer exists" do
|
|
||||||
assert_equal nil, RandomModel.new.active_model_serializer
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it returns a deducted serializer if it exists exists" do
|
|
||||||
assert_equal OtherRandomModelSerializer, OtherRandomModel.new.active_model_serializer
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it returns ArraySerializer for a collection" do
|
|
||||||
assert_equal ActiveModel::ArraySerializer, RandomModelCollection.new.active_model_serializer
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it automatically includes array_serializer in active_record/relation" do
|
|
||||||
ActiveSupport.run_load_hooks(:active_record)
|
|
||||||
assert_equal ActiveModel::ArraySerializer, ActiveRecord::Relation.new.active_model_serializer
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it automatically includes array_serializer in mongoid/criteria" do
|
|
||||||
ActiveSupport.run_load_hooks(:mongoid)
|
|
||||||
assert_equal ActiveModel::ArraySerializer, Mongoid::Criteria.new.active_model_serializer
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,202 +0,0 @@
|
|||||||
class Model
|
|
||||||
def initialize(hash={})
|
|
||||||
@attributes = hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_attribute_for_serialization(name)
|
|
||||||
@attributes[name]
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json(*)
|
|
||||||
{ model: "Model" }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class ModelWithActiveModelSerializer < Model
|
|
||||||
include ActiveModel::Serializers::JSON
|
|
||||||
attr_accessor :attributes
|
|
||||||
def read_attribute_for_serialization(name)
|
|
||||||
@attributes[name]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class User
|
|
||||||
include ActiveModel::SerializerSupport
|
|
||||||
|
|
||||||
attr_accessor :superuser
|
|
||||||
|
|
||||||
def initialize(hash={})
|
|
||||||
@attributes = hash.merge(first_name: "Jose", last_name: "Valim", password: "oh noes yugive my password")
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_attribute_for_serialization(name)
|
|
||||||
@attributes[name]
|
|
||||||
end
|
|
||||||
|
|
||||||
def super_user?
|
|
||||||
@superuser
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Post < Model
|
|
||||||
def initialize(attributes)
|
|
||||||
super(attributes)
|
|
||||||
self.comments ||= []
|
|
||||||
self.comments_disabled = false
|
|
||||||
self.author = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_accessor :comments, :comments_disabled, :author
|
|
||||||
def active_model_serializer; PostSerializer; end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Comment < Model
|
|
||||||
def active_model_serializer; CommentSerializer; end
|
|
||||||
end
|
|
||||||
|
|
||||||
class UserSerializer < ActiveModel::Serializer
|
|
||||||
attributes :first_name, :last_name
|
|
||||||
|
|
||||||
def serializable_hash
|
|
||||||
attributes.merge(ok: true).merge(options[:scope])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class UserAttributesWithKeySerializer < ActiveModel::Serializer
|
|
||||||
attributes first_name: :f_name, last_name: :l_name
|
|
||||||
|
|
||||||
def serializable_hash
|
|
||||||
attributes.merge(ok: true).merge(options[:scope])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class UserAttributesWithSomeKeySerializer < ActiveModel::Serializer
|
|
||||||
attributes :first_name, last_name: :l_name
|
|
||||||
|
|
||||||
def serializable_hash
|
|
||||||
attributes.merge(ok: true).merge(options[:scope])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class UserAttributesWithUnsymbolizableKeySerializer < ActiveModel::Serializer
|
|
||||||
attributes :first_name, last_name: :"last-name"
|
|
||||||
|
|
||||||
def serializable_hash
|
|
||||||
attributes.merge(ok: true).merge(options[:scope])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class DefaultUserSerializer < ActiveModel::Serializer
|
|
||||||
attributes :first_name, :last_name
|
|
||||||
end
|
|
||||||
|
|
||||||
class MyUserSerializer < ActiveModel::Serializer
|
|
||||||
attributes :first_name, :last_name
|
|
||||||
|
|
||||||
def serializable_hash
|
|
||||||
hash = attributes
|
|
||||||
hash = hash.merge(super_user: true) if object.super_user?
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class CommentSerializer
|
|
||||||
def initialize(comment, options={})
|
|
||||||
@object = comment
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :object
|
|
||||||
|
|
||||||
def serializable_hash
|
|
||||||
{ title: @object.read_attribute_for_serialization(:title) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json(options=nil)
|
|
||||||
options ||= {}
|
|
||||||
if options[:root] == false
|
|
||||||
serializable_hash
|
|
||||||
else
|
|
||||||
{ comment: serializable_hash }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :title, :body
|
|
||||||
has_many :comments, serializer: CommentSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
class PostWithConditionalCommentsSerializer < ActiveModel::Serializer
|
|
||||||
root :post
|
|
||||||
attributes :title, :body
|
|
||||||
has_many :comments, serializer: CommentSerializer
|
|
||||||
|
|
||||||
def include_associations!
|
|
||||||
include! :comments unless object.comments_disabled
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class PostWithMultipleConditionalsSerializer < ActiveModel::Serializer
|
|
||||||
root :post
|
|
||||||
attributes :title, :body, :author
|
|
||||||
has_many :comments, serializer: CommentSerializer
|
|
||||||
|
|
||||||
def include_comments?
|
|
||||||
!object.comments_disabled
|
|
||||||
end
|
|
||||||
|
|
||||||
def include_author?
|
|
||||||
scope.super_user?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Blog < Model
|
|
||||||
attr_accessor :author
|
|
||||||
end
|
|
||||||
|
|
||||||
class AuthorSerializer < ActiveModel::Serializer
|
|
||||||
attributes :first_name, :last_name
|
|
||||||
end
|
|
||||||
|
|
||||||
class BlogSerializer < ActiveModel::Serializer
|
|
||||||
has_one :author, serializer: AuthorSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
class BlogWithRootSerializer < BlogSerializer
|
|
||||||
root true
|
|
||||||
end
|
|
||||||
|
|
||||||
class CustomPostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :title
|
|
||||||
end
|
|
||||||
|
|
||||||
class CustomBlog < Blog
|
|
||||||
attr_accessor :public_posts, :public_user
|
|
||||||
end
|
|
||||||
|
|
||||||
class CustomBlogSerializer < ActiveModel::Serializer
|
|
||||||
has_many :public_posts, key: :posts, serializer: PostSerializer
|
|
||||||
has_one :public_user, key: :user, serializer: UserSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
class SomeSerializer < ActiveModel::Serializer
|
|
||||||
attributes :some
|
|
||||||
end
|
|
||||||
|
|
||||||
class SomeObject < Struct.new(:some)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Set up some classes for polymorphic testing
|
|
||||||
class Attachment < Model
|
|
||||||
def attachable
|
|
||||||
@attributes[:attachable]
|
|
||||||
end
|
|
||||||
|
|
||||||
def readable
|
|
||||||
@attributes[:readable]
|
|
||||||
end
|
|
||||||
|
|
||||||
def edible
|
|
||||||
@attributes[:edible]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
require "rubygems"
|
|
||||||
require "bundler/setup"
|
|
||||||
|
|
||||||
require 'simplecov'
|
|
||||||
SimpleCov.start do
|
|
||||||
add_group "lib", "lib"
|
|
||||||
add_group "spec", "spec"
|
|
||||||
end
|
|
||||||
|
|
||||||
require 'coveralls'
|
|
||||||
Coveralls.wear!
|
|
||||||
|
|
||||||
require "pry"
|
|
||||||
|
|
||||||
require "active_model_serializers"
|
|
||||||
require "active_support/json"
|
|
||||||
require "minitest/autorun"
|
|
||||||
|
|
||||||
require 'rails'
|
|
||||||
|
|
||||||
module TestHelper
|
|
||||||
Routes = ActionDispatch::Routing::RouteSet.new
|
|
||||||
Routes.draw do
|
|
||||||
resource :hypermedia
|
|
||||||
get ':controller(/:action(/:id))'
|
|
||||||
get ':controller(/:action)'
|
|
||||||
end
|
|
||||||
|
|
||||||
ActionController::Base.send :include, Routes.url_helpers
|
|
||||||
ActiveModel::Serializer.send :include, Routes.url_helpers
|
|
||||||
end
|
|
||||||
|
|
||||||
ActiveSupport::TestCase.class_eval do
|
|
||||||
setup do
|
|
||||||
@routes = ::TestHelper::Routes
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Object
|
|
||||||
undef_method :id if respond_to?(:id)
|
|
||||||
end
|
|
||||||
Loading…
Reference in New Issue
Block a user