forked from rails/rails
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement ArraySerializer and move old serialization API to a new nam…
…espace. The following constants were renamed: ActiveModel::Serialization => ActiveModel::Serializable ActiveModel::Serializers::JSON => ActiveModel::Serializable::JSON ActiveModel::Serializers::Xml => ActiveModel::Serializable::XML The main motivation for such a change is that `ActiveModel::Serializers::JSON` was not actually a serializer, but a module that when included allows the target to be serializable to JSON. With such changes, we were able to clean up the namespace to add true serializers as the ArraySerializer.
- Loading branch information
Showing
15 changed files
with
610 additions
and
436 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
require 'active_support/core_ext/hash/except' | ||
require 'active_support/core_ext/hash/slice' | ||
require 'active_support/core_ext/array/wrap' | ||
|
||
module ActiveModel | ||
# == Active Model Serializable | ||
# | ||
# Provides a basic serialization to a serializable_hash for your object. | ||
# | ||
# A minimal implementation could be: | ||
# | ||
# class Person | ||
# | ||
# include ActiveModel::Serializable | ||
# | ||
# attr_accessor :name | ||
# | ||
# def attributes | ||
# {'name' => name} | ||
# end | ||
# | ||
# end | ||
# | ||
# Which would provide you with: | ||
# | ||
# person = Person.new | ||
# person.serializable_hash # => {"name"=>nil} | ||
# person.name = "Bob" | ||
# person.serializable_hash # => {"name"=>"Bob"} | ||
# | ||
# You need to declare some sort of attributes hash which contains the attributes | ||
# you want to serialize and their current value. | ||
# | ||
# Most of the time though, you will want to include the JSON or XML | ||
# serializations. Both of these modules automatically include the | ||
# ActiveModel::Serialization module, so there is no need to explicitly | ||
# include it. | ||
# | ||
# So a minimal implementation including XML and JSON would be: | ||
# | ||
# class Person | ||
# | ||
# include ActiveModel::Serializable::JSON | ||
# include ActiveModel::Serializable::XML | ||
# | ||
# attr_accessor :name | ||
# | ||
# def attributes | ||
# {'name' => name} | ||
# end | ||
# | ||
# end | ||
# | ||
# Which would provide you with: | ||
# | ||
# person = Person.new | ||
# person.serializable_hash # => {"name"=>nil} | ||
# person.as_json # => {"name"=>nil} | ||
# person.to_json # => "{\"name\":null}" | ||
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person... | ||
# | ||
# person.name = "Bob" | ||
# person.serializable_hash # => {"name"=>"Bob"} | ||
# person.as_json # => {"name"=>"Bob"} | ||
# person.to_json # => "{\"name\":\"Bob\"}" | ||
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person... | ||
# | ||
# Valid options are <tt>:only</tt>, <tt>:except</tt> and <tt>:methods</tt> . | ||
module Serializable | ||
extend ActiveSupport::Concern | ||
|
||
autoload :JSON, "active_model/serializable/json" | ||
autoload :XML, "active_model/serializable/xml" | ||
|
||
include ActiveModel::Serializer::Scope | ||
|
||
module ClassMethods #:nodoc: | ||
def _model_serializer | ||
@_model_serializer ||= ActiveModel::Serializer::Finder.find(self, self) | ||
end | ||
end | ||
|
||
def serializable_hash(options = nil) | ||
options ||= {} | ||
|
||
attribute_names = attributes.keys.sort | ||
if only = options[:only] | ||
attribute_names &= Array.wrap(only).map(&:to_s) | ||
elsif except = options[:except] | ||
attribute_names -= Array.wrap(except).map(&:to_s) | ||
end | ||
|
||
hash = {} | ||
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) } | ||
|
||
method_names = Array.wrap(options[:methods]).select { |n| respond_to?(n) } | ||
method_names.each { |n| hash[n] = send(n) } | ||
|
||
serializable_add_includes(options) do |association, records, opts| | ||
hash[association] = if records.is_a?(Enumerable) | ||
records.map { |a| a.serializable_hash(opts) } | ||
else | ||
records.serializable_hash(opts) | ||
end | ||
end | ||
|
||
hash | ||
end | ||
|
||
# Returns a model serializer for this object considering its namespace. | ||
def model_serializer | ||
self.class._model_serializer | ||
end | ||
|
||
private | ||
|
||
# Hook method defining how an attribute value should be retrieved for | ||
# serialization. By default this is assumed to be an instance named after | ||
# the attribute. Override this method in subclasses should you need to | ||
# retrieve the value for a given attribute differently: | ||
# | ||
# class MyClass | ||
# include ActiveModel::Validations | ||
# | ||
# def initialize(data = {}) | ||
# @data = data | ||
# end | ||
# | ||
# def read_attribute_for_serialization(key) | ||
# @data[key] | ||
# end | ||
# end | ||
# | ||
alias :read_attribute_for_serialization :send | ||
|
||
# Add associations specified via the <tt>:include</tt> option. | ||
# | ||
# Expects a block that takes as arguments: | ||
# +association+ - name of the association | ||
# +records+ - the association record(s) to be serialized | ||
# +opts+ - options for the association records | ||
def serializable_add_includes(options = {}) #:nodoc: | ||
return unless include = options[:include] | ||
|
||
unless include.is_a?(Hash) | ||
include = Hash[Array.wrap(include).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }] | ||
end | ||
|
||
include.each do |association, opts| | ||
if records = send(association) | ||
yield association, records, opts | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
require 'active_support/json' | ||
require 'active_support/core_ext/class/attribute' | ||
|
||
module ActiveModel | ||
# == Active Model Serializable as JSON | ||
module Serializable | ||
module JSON | ||
extend ActiveSupport::Concern | ||
include ActiveModel::Serializable | ||
|
||
included do | ||
extend ActiveModel::Naming | ||
|
||
class_attribute :include_root_in_json | ||
self.include_root_in_json = true | ||
end | ||
|
||
# Returns a hash representing the model. Some configuration can be | ||
# passed through +options+. | ||
# | ||
# The option <tt>include_root_in_json</tt> controls the top-level behavior | ||
# of +as_json+. If true (the default) +as_json+ will emit a single root | ||
# node named after the object's type. For example: | ||
# | ||
# user = User.find(1) | ||
# user.as_json | ||
# # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16, | ||
# "created_at": "2006/08/01", "awesome": true} } | ||
# | ||
# ActiveRecord::Base.include_root_in_json = false | ||
# user.as_json | ||
# # => {"id": 1, "name": "Konata Izumi", "age": 16, | ||
# "created_at": "2006/08/01", "awesome": true} | ||
# | ||
# This behavior can also be achieved by setting the <tt>:root</tt> option to +false+ as in: | ||
# | ||
# user = User.find(1) | ||
# user.as_json(root: false) | ||
# # => {"id": 1, "name": "Konata Izumi", "age": 16, | ||
# "created_at": "2006/08/01", "awesome": true} | ||
# | ||
# The remainder of the examples in this section assume include_root_in_json is set to | ||
# <tt>false</tt>. | ||
# | ||
# Without any +options+, the returned Hash will include all the model's | ||
# attributes. For example: | ||
# | ||
# user = User.find(1) | ||
# user.as_json | ||
# # => {"id": 1, "name": "Konata Izumi", "age": 16, | ||
# "created_at": "2006/08/01", "awesome": true} | ||
# | ||
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes | ||
# included, and work similar to the +attributes+ method. For example: | ||
# | ||
# user.as_json(:only => [ :id, :name ]) | ||
# # => {"id": 1, "name": "Konata Izumi"} | ||
# | ||
# user.as_json(:except => [ :id, :created_at, :age ]) | ||
# # => {"name": "Konata Izumi", "awesome": true} | ||
# | ||
# To include the result of some method calls on the model use <tt>:methods</tt>: | ||
# | ||
# user.as_json(:methods => :permalink) | ||
# # => {"id": 1, "name": "Konata Izumi", "age": 16, | ||
# "created_at": "2006/08/01", "awesome": true, | ||
# "permalink": "1-konata-izumi"} | ||
# | ||
# To include associations use <tt>:include</tt>: | ||
# | ||
# user.as_json(:include => :posts) | ||
# # => {"id": 1, "name": "Konata Izumi", "age": 16, | ||
# "created_at": "2006/08/01", "awesome": true, | ||
# "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"}, | ||
# {"id": 2, author_id: 1, "title": "So I was thinking"}]} | ||
# | ||
# Second level and higher order associations work as well: | ||
# | ||
# user.as_json(:include => { :posts => { | ||
# :include => { :comments => { | ||
# :only => :body } }, | ||
# :only => :title } }) | ||
# # => {"id": 1, "name": "Konata Izumi", "age": 16, | ||
# "created_at": "2006/08/01", "awesome": true, | ||
# "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}], | ||
# "title": "Welcome to the weblog"}, | ||
# {"comments": [{"body": "Don't think too hard"}], | ||
# "title": "So I was thinking"}]} | ||
def as_json(options = nil) | ||
root = include_root_in_json | ||
root = options[:root] if options.try(:key?, :root) | ||
if root | ||
root = self.class.model_name.element if root == true | ||
{ root => serializable_hash(options) } | ||
else | ||
serializable_hash(options) | ||
end | ||
end | ||
|
||
def from_json(json, include_root=include_root_in_json) | ||
hash = ActiveSupport::JSON.decode(json) | ||
hash = hash.values.first if include_root | ||
self.attributes = hash | ||
self | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.