mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-24 06:46:50 +00:00
Merge branch 'master' of github.com:josevalim/active_model_serializers
This commit is contained in:
commit
9da0176d2b
5
Gemfile
5
Gemfile
@ -1,7 +1,4 @@
|
|||||||
source 'http://rubygems.org'
|
source 'http://rubygems.org'
|
||||||
|
|
||||||
# Specify your gem's dependencies in active_model_serializers.gemspec
|
# Specify gem dependencies in active_model_serializers.gemspec
|
||||||
gemspec
|
gemspec
|
||||||
|
|
||||||
gem "pry"
|
|
||||||
gem "simplecov", :require => false
|
|
||||||
|
|||||||
150
README.md
150
README.md
@ -187,11 +187,49 @@ end
|
|||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
For specified attributes, the serializer will look up the attribute on the
|
For specified attributes, a serializer will look up the attribute on the
|
||||||
object you passed to `render :json`. It uses
|
object you passed to `render :json`. It uses
|
||||||
`read_attribute_for_serialization`, which `ActiveRecord` objects implement as a
|
`read_attribute_for_serialization`, which `ActiveRecord` objects implement as a
|
||||||
regular attribute lookup.
|
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 either `object` or the name of the serialized object
|
||||||
|
(e.g. `admin_comment` for the `AdminCommentSerializer`).
|
||||||
|
|
||||||
|
You can also access the `scope` method, which provides an
|
||||||
|
authorization context to your serializer. By default, scope
|
||||||
|
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 `scope`. For example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class PostSerializer < ActiveModel::Serializer
|
||||||
|
attributes :id, :title, :body, :author
|
||||||
|
|
||||||
|
def include_author?
|
||||||
|
scope.admin?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
If you would like the key in the outputted JSON to be different from its name
|
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:
|
in ActiveRecord, you can use the `:key` option to customize it:
|
||||||
|
|
||||||
@ -205,45 +243,24 @@ class PostSerializer < ActiveModel::Serializer
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
## Custom Attributes
|
If you would like direct, low-level control of attribute serialization, you can
|
||||||
|
completely override the `attributes` method to return the hash you need:
|
||||||
If you would like customize your JSON to include things beyond the simple
|
|
||||||
attributes of the model, you can override its `attributes` method
|
|
||||||
to return anything you need.
|
|
||||||
|
|
||||||
The most common scenario to use this feature is when an attribute
|
|
||||||
depends on a serialization scope. By default, the current user of your
|
|
||||||
application will be available in your serializer under the method
|
|
||||||
`scope`. This allows you to check for permissions before adding
|
|
||||||
an attribute. For example:
|
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class Person < ActiveRecord::Base
|
|
||||||
def full_name
|
|
||||||
"#{first_name} #{last_name}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class PersonSerializer < ActiveModel::Serializer
|
class PersonSerializer < ActiveModel::Serializer
|
||||||
attributes :first_name, :last_name
|
attributes :first_name, :last_name
|
||||||
|
|
||||||
def attributes
|
def attributes
|
||||||
hash = super
|
hash = super
|
||||||
hash["full_name"] = object.full_name if scope.admin?
|
if scope.admin?
|
||||||
|
hash["ssn"] = object.ssn
|
||||||
|
hash["secret"] = object.mothers_maiden_name
|
||||||
|
end
|
||||||
hash
|
hash
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
The serialization scope can be customized in your controller by
|
|
||||||
calling `serialization_scope`:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class ApplicationController < ActionController::Base
|
|
||||||
serialization_scope :current_admin
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Associations
|
## Associations
|
||||||
|
|
||||||
For specified associations, the serializer will look up the association and
|
For specified associations, the serializer will look up the association and
|
||||||
@ -268,11 +285,7 @@ class PostSerializer < ActiveModel::Serializer
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
In a serializer, `scope` is the current authorization scope (usually
|
As with attributes, you can change the JSON key that the serializer should
|
||||||
`current_user`), which the controller gives to the serializer when you call
|
|
||||||
`render :json`
|
|
||||||
|
|
||||||
As with attributes, you can also change the JSON key that the serializer should
|
|
||||||
use for a particular association.
|
use for a particular association.
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
@ -284,6 +297,37 @@ class PostSerializer < ActiveModel::Serializer
|
|||||||
end
|
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?
|
||||||
|
!post.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 scope.admin?
|
||||||
|
include! :comments unless object.comments_disabled?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
## Embedding Associations
|
## Embedding Associations
|
||||||
|
|
||||||
By default, associations will be embedded inside the serialized object. So if
|
By default, associations will be embedded inside the serialized object. So if
|
||||||
@ -330,6 +374,33 @@ Now, any associations will be supplied as an Array of IDs:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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" }
|
||||||
|
],
|
||||||
|
"tags": [ 1, 2, 3 ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
In addition to supplying an Array of IDs, you may want to side-load the data
|
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
|
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
|
of data without having to recursively scan the tree looking for embedded
|
||||||
@ -405,3 +476,16 @@ data looking for information, is extremely useful.
|
|||||||
|
|
||||||
If you are mostly working with the data in simple scenarios and manually making
|
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.
|
Ajax requests, you probably just want to use the default embedded behavior.
|
||||||
|
|
||||||
|
## Customizing Scope
|
||||||
|
|
||||||
|
In a serializer, `scope` 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
|
||||||
|
```
|
||||||
|
|||||||
@ -8,7 +8,7 @@ Gem::Specification.new do |gem|
|
|||||||
gem.email = ["jose.valim@gmail.com", "wycats@gmail.com"]
|
gem.email = ["jose.valim@gmail.com", "wycats@gmail.com"]
|
||||||
gem.description = %q{Making it easy to serialize models for client-side use}
|
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.summary = %q{Bringing consistency and object orientation to model serialization. Works great for client-side MVC frameworks!}
|
||||||
gem.homepage = ""
|
gem.homepage = "https://github.com/josevalim/active_model_serializers"
|
||||||
|
|
||||||
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
||||||
gem.files = `git ls-files`.split("\n")
|
gem.files = `git ls-files`.split("\n")
|
||||||
@ -19,4 +19,6 @@ Gem::Specification.new do |gem|
|
|||||||
|
|
||||||
gem.add_dependency 'activemodel', '~> 3.0'
|
gem.add_dependency 'activemodel', '~> 3.0'
|
||||||
gem.add_development_dependency "rails", "~> 3.0"
|
gem.add_development_dependency "rails", "~> 3.0"
|
||||||
|
gem.add_development_dependency "pry"
|
||||||
|
gem.add_development_dependency "simplecov"
|
||||||
end
|
end
|
||||||
|
|||||||
@ -328,6 +328,8 @@ module ActiveModel
|
|||||||
object.read_attribute_for_serialization(attr.to_sym)
|
object.read_attribute_for_serialization(attr.to_sym)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
define_include_method attr
|
||||||
end
|
end
|
||||||
|
|
||||||
def associate(klass, attrs) #:nodoc:
|
def associate(klass, attrs) #:nodoc:
|
||||||
@ -341,10 +343,21 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
define_include_method attr
|
||||||
|
|
||||||
self._associations[attr] = klass.refine(attr, options)
|
self._associations[attr] = klass.refine(attr, options)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def define_include_method(name)
|
||||||
|
method = "include_#{name}?".to_sym
|
||||||
|
unless method_defined?(method)
|
||||||
|
define_method method do
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Defines an association in the object should be rendered.
|
# Defines an association in the object should be rendered.
|
||||||
#
|
#
|
||||||
# The serializer object should implement the association name
|
# The serializer object should implement the association name
|
||||||
@ -460,8 +473,7 @@ module ActiveModel
|
|||||||
|
|
||||||
# Returns a json representation of the serializable
|
# Returns a json representation of the serializable
|
||||||
# object including the root.
|
# object including the root.
|
||||||
def as_json(options=nil)
|
def as_json(options={})
|
||||||
options ||= {}
|
|
||||||
if root = options.fetch(:root, @options.fetch(:root, _root))
|
if root = options.fetch(:root, @options.fetch(:root, _root))
|
||||||
@options[:hash] = hash = {}
|
@options[:hash] = hash = {}
|
||||||
@options[:unique_values] = {}
|
@options[:unique_values] = {}
|
||||||
@ -477,34 +489,22 @@ module ActiveModel
|
|||||||
# object without the root.
|
# object without the root.
|
||||||
def serializable_hash
|
def serializable_hash
|
||||||
instrument(:serialize, :serializer => self.class.name) do
|
instrument(:serialize, :serializer => self.class.name) do
|
||||||
node = attributes
|
@node = attributes
|
||||||
instrument :associations do
|
instrument :associations do
|
||||||
include_associations!(node) if _embed
|
include_associations! if _embed
|
||||||
end
|
end
|
||||||
node
|
@node
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def include_associations!(node)
|
def include_associations!
|
||||||
_associations.each do |attr, klass|
|
_associations.each_key do |name|
|
||||||
opts = { :node => node }
|
include!(name) if include?(name)
|
||||||
|
|
||||||
if options.include?(:include) || options.include?(:exclude)
|
|
||||||
opts[:include] = included_association?(attr)
|
|
||||||
end
|
|
||||||
|
|
||||||
include! attr, opts
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def included_association?(name)
|
def include?(name)
|
||||||
if options.key?(:include)
|
send "include_#{name}?".to_sym
|
||||||
options[:include].include?(name)
|
|
||||||
elsif options.key?(:exclude)
|
|
||||||
!options[:exclude].include?(name)
|
|
||||||
else
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def include!(name, options={})
|
def include!(name, options={})
|
||||||
@ -526,9 +526,17 @@ module ActiveModel
|
|||||||
@options[:unique_values] ||= {}
|
@options[:unique_values] ||= {}
|
||||||
end
|
end
|
||||||
|
|
||||||
node = options[:node]
|
node = options[:node] ||= @node
|
||||||
value = options[:value]
|
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
|
||||||
|
|
||||||
association_class =
|
association_class =
|
||||||
if klass = _associations[name]
|
if klass = _associations[name]
|
||||||
klass
|
klass
|
||||||
@ -578,7 +586,7 @@ module ActiveModel
|
|||||||
hash = {}
|
hash = {}
|
||||||
|
|
||||||
_attributes.each do |name,key|
|
_attributes.each do |name,key|
|
||||||
hash[key] = read_attribute_for_serialization(name)
|
hash[key] = read_attribute_for_serialization(name) if include?(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
hash
|
hash
|
||||||
|
|||||||
@ -37,10 +37,11 @@ class SerializerTest < ActiveModel::TestCase
|
|||||||
def initialize(attributes)
|
def initialize(attributes)
|
||||||
super(attributes)
|
super(attributes)
|
||||||
self.comments ||= []
|
self.comments ||= []
|
||||||
|
self.comments_disabled = false
|
||||||
self.author = nil
|
self.author = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor :comments, :author
|
attr_accessor :comments, :comments_disabled, :author
|
||||||
def active_model_serializer; PostSerializer; end
|
def active_model_serializer; PostSerializer; end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -89,11 +90,6 @@ class SerializerTest < ActiveModel::TestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
|
||||||
attributes :title, :body
|
|
||||||
has_many :comments, :serializer => CommentSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_scope_works_correct
|
def test_scope_works_correct
|
||||||
serializer = ActiveModel::Serializer.new :foo, :scope => :bar
|
serializer = ActiveModel::Serializer.new :foo, :scope => :bar
|
||||||
assert_equal serializer.scope, :bar
|
assert_equal serializer.scope, :bar
|
||||||
@ -163,6 +159,11 @@ class SerializerTest < ActiveModel::TestCase
|
|||||||
}, hash)
|
}, hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class PostSerializer < ActiveModel::Serializer
|
||||||
|
attributes :title, :body
|
||||||
|
has_many :comments, :serializer => CommentSerializer
|
||||||
|
end
|
||||||
|
|
||||||
def test_has_many
|
def test_has_many
|
||||||
user = User.new
|
user = User.new
|
||||||
|
|
||||||
@ -184,6 +185,104 @@ class SerializerTest < ActiveModel::TestCase
|
|||||||
}, post_serializer.as_json)
|
}, post_serializer.as_json)
|
||||||
end
|
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
|
||||||
|
|
||||||
|
def test_conditionally_included_associations
|
||||||
|
user = User.new
|
||||||
|
|
||||||
|
post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com")
|
||||||
|
comments = [Comment.new(:title => "Comment1"), Comment.new(:title => "Comment2")]
|
||||||
|
post.comments = comments
|
||||||
|
|
||||||
|
post_serializer = PostWithConditionalCommentsSerializer.new(post, :scope => user)
|
||||||
|
|
||||||
|
# comments enabled
|
||||||
|
post.comments_disabled = false
|
||||||
|
assert_equal({
|
||||||
|
:post => {
|
||||||
|
:title => "New Post",
|
||||||
|
:body => "Body of new post",
|
||||||
|
:comments => [
|
||||||
|
{ :title => "Comment1" },
|
||||||
|
{ :title => "Comment2" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}, post_serializer.as_json)
|
||||||
|
|
||||||
|
# comments disabled
|
||||||
|
post.comments_disabled = true
|
||||||
|
assert_equal({
|
||||||
|
:post => {
|
||||||
|
:title => "New Post",
|
||||||
|
:body => "Body of new post"
|
||||||
|
}
|
||||||
|
}, post_serializer.as_json)
|
||||||
|
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
|
||||||
|
|
||||||
|
def test_conditionally_included_associations_and_attributes
|
||||||
|
user = User.new
|
||||||
|
|
||||||
|
post = Post.new(:title => "New Post", :body => "Body of new post", :author => 'Sausage King', :email => "tenderlove@tenderlove.com")
|
||||||
|
comments = [Comment.new(:title => "Comment1"), Comment.new(:title => "Comment2")]
|
||||||
|
post.comments = comments
|
||||||
|
|
||||||
|
post_serializer = PostWithMultipleConditionalsSerializer.new(post, :scope => user)
|
||||||
|
|
||||||
|
# comments enabled
|
||||||
|
post.comments_disabled = false
|
||||||
|
assert_equal({
|
||||||
|
:post => {
|
||||||
|
:title => "New Post",
|
||||||
|
:body => "Body of new post",
|
||||||
|
:comments => [
|
||||||
|
{ :title => "Comment1" },
|
||||||
|
{ :title => "Comment2" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}, post_serializer.as_json)
|
||||||
|
|
||||||
|
# comments disabled
|
||||||
|
post.comments_disabled = true
|
||||||
|
assert_equal({
|
||||||
|
:post => {
|
||||||
|
:title => "New Post",
|
||||||
|
:body => "Body of new post"
|
||||||
|
}
|
||||||
|
}, post_serializer.as_json)
|
||||||
|
|
||||||
|
# superuser - should see author
|
||||||
|
user.superuser = true
|
||||||
|
assert_equal({
|
||||||
|
:post => {
|
||||||
|
:title => "New Post",
|
||||||
|
:body => "Body of new post",
|
||||||
|
:author => "Sausage King"
|
||||||
|
}
|
||||||
|
}, post_serializer.as_json)
|
||||||
|
end
|
||||||
|
|
||||||
class Blog < Model
|
class Blog < Model
|
||||||
attr_accessor :author
|
attr_accessor :author
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user