mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-22 22:06: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