mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Break up concerns for choosing what attributes should be serialized and the actual serializer
This commit is contained in:
parent
7a26c21d8e
commit
c6bc8e6626
8 changed files with 135 additions and 162 deletions
|
@ -35,7 +35,7 @@ module ActiveModel
|
|||
autoload :Naming, 'active_model/naming'
|
||||
autoload :Observer, 'active_model/observing'
|
||||
autoload :Observing, 'active_model/observing'
|
||||
autoload :Serializer, 'active_model/serializer'
|
||||
autoload :Serialization, 'active_model/serialization'
|
||||
autoload :StateMachine, 'active_model/state_machine'
|
||||
autoload :TestCase, 'active_model/test_case'
|
||||
autoload :Validations, 'active_model/validations'
|
||||
|
|
30
activemodel/lib/active_model/serialization.rb
Normal file
30
activemodel/lib/active_model/serialization.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
require 'active_support/core_ext/hash/except'
|
||||
require 'active_support/core_ext/hash/slice'
|
||||
|
||||
module ActiveModel
|
||||
module Serialization
|
||||
def serializable_hash(options = nil)
|
||||
options ||= {}
|
||||
|
||||
options[:only] = Array.wrap(options[:only]).map { |n| n.to_s }
|
||||
options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
|
||||
|
||||
attribute_names = attributes.keys.sort
|
||||
if options[:only].any?
|
||||
attribute_names &= options[:only]
|
||||
elsif options[:except].any?
|
||||
attribute_names -= options[:except]
|
||||
end
|
||||
|
||||
method_names = Array.wrap(options[:methods]).inject([]) do |methods, name|
|
||||
methods << name if respond_to?(name.to_s)
|
||||
methods
|
||||
end
|
||||
|
||||
(attribute_names + method_names).inject({}) { |hash, name|
|
||||
hash[name] = send(name)
|
||||
hash
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,60 +0,0 @@
|
|||
require 'active_support/core_ext/hash/except'
|
||||
require 'active_support/core_ext/hash/slice'
|
||||
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
attr_reader :options
|
||||
|
||||
def initialize(serializable, options = nil)
|
||||
@serializable = serializable
|
||||
@options = options ? options.dup : {}
|
||||
|
||||
@options[:only] = Array.wrap(@options[:only]).map { |n| n.to_s }
|
||||
@options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s }
|
||||
end
|
||||
|
||||
def serialize
|
||||
raise NotImplemented
|
||||
end
|
||||
|
||||
def to_s(&block)
|
||||
serialize(&block)
|
||||
end
|
||||
|
||||
# To replicate the behavior in ActiveRecord#attributes,
|
||||
# <tt>:except</tt> takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set
|
||||
# for a N level model but is set for the N+1 level models,
|
||||
# then because <tt>:except</tt> is set to a default value, the second
|
||||
# level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if
|
||||
# <tt>:only</tt> is set, always delete <tt>:except</tt>.
|
||||
def serializable_attribute_names
|
||||
attribute_names = @serializable.attributes.keys.sort
|
||||
|
||||
if options[:only].any?
|
||||
attribute_names &= options[:only]
|
||||
elsif options[:except].any?
|
||||
attribute_names -= options[:except]
|
||||
end
|
||||
|
||||
attribute_names
|
||||
end
|
||||
|
||||
def serializable_method_names
|
||||
Array.wrap(options[:methods]).inject([]) do |methods, name|
|
||||
methods << name if @serializable.respond_to?(name.to_s)
|
||||
methods
|
||||
end
|
||||
end
|
||||
|
||||
def serializable_names
|
||||
serializable_attribute_names + serializable_method_names
|
||||
end
|
||||
|
||||
def serializable_hash
|
||||
serializable_names.inject({}) { |hash, name|
|
||||
hash[name] = @serializable.send(name)
|
||||
hash
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,6 +5,7 @@ module ActiveModel
|
|||
module Serializers
|
||||
module JSON
|
||||
extend ActiveSupport::Concern
|
||||
include ActiveModel::Serialization
|
||||
|
||||
included do
|
||||
extend ActiveModel::Naming
|
||||
|
@ -12,19 +13,6 @@ module ActiveModel
|
|||
cattr_accessor :include_root_in_json, :instance_writer => false
|
||||
end
|
||||
|
||||
class Serializer < ActiveModel::Serializer
|
||||
def serializable_hash
|
||||
model = super
|
||||
@serializable.include_root_in_json ?
|
||||
{ @serializable.class.model_name.element => model } :
|
||||
model
|
||||
end
|
||||
|
||||
def serialize
|
||||
ActiveSupport::JSON.encode(serializable_hash)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a JSON string representing the model. Some configuration is
|
||||
# available through +options+.
|
||||
#
|
||||
|
@ -92,7 +80,9 @@ module ActiveModel
|
|||
# {"comments": [{"body": "Don't think too hard"}],
|
||||
# "title": "So I was thinking"}]}
|
||||
def encode_json(encoder)
|
||||
Serializer.new(self, encoder.options).to_s
|
||||
hash = serializable_hash(encoder.options)
|
||||
hash = { self.class.model_name.element => hash } if include_root_in_json
|
||||
ActiveSupport::JSON.encode(hash)
|
||||
end
|
||||
|
||||
def as_json(options = nil)
|
||||
|
|
|
@ -5,8 +5,9 @@ module ActiveModel
|
|||
module Serializers
|
||||
module Xml
|
||||
extend ActiveSupport::Concern
|
||||
include ActiveModel::Serialization
|
||||
|
||||
class Serializer < ActiveModel::Serializer #:nodoc:
|
||||
class Serializer #:nodoc:
|
||||
class Attribute #:nodoc:
|
||||
attr_reader :name, :value, :type
|
||||
|
||||
|
@ -74,32 +75,32 @@ module ActiveModel
|
|||
end
|
||||
end
|
||||
|
||||
def builder
|
||||
@builder ||= begin
|
||||
require 'builder' unless defined? ::Builder
|
||||
options[:indent] ||= 2
|
||||
builder = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
|
||||
attr_reader :options
|
||||
|
||||
unless options[:skip_instruct]
|
||||
builder.instruct!
|
||||
options[:skip_instruct] = true
|
||||
end
|
||||
def initialize(serializable, options = nil)
|
||||
@serializable = serializable
|
||||
@options = options ? options.dup : {}
|
||||
|
||||
builder
|
||||
@options[:only] = Array.wrap(@options[:only]).map { |n| n.to_s }
|
||||
@options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s }
|
||||
end
|
||||
|
||||
# To replicate the behavior in ActiveRecord#attributes,
|
||||
# <tt>:except</tt> takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set
|
||||
# for a N level model but is set for the N+1 level models,
|
||||
# then because <tt>:except</tt> is set to a default value, the second
|
||||
# level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if
|
||||
# <tt>:only</tt> is set, always delete <tt>:except</tt>.
|
||||
def serializable_attribute_names
|
||||
attribute_names = @serializable.attributes.keys.sort
|
||||
|
||||
if options[:only].any?
|
||||
attribute_names &= options[:only]
|
||||
elsif options[:except].any?
|
||||
attribute_names -= options[:except]
|
||||
end
|
||||
end
|
||||
|
||||
def root
|
||||
root = (options[:root] || @serializable.class.model_name.singular).to_s
|
||||
reformat_name(root)
|
||||
end
|
||||
|
||||
def dasherize?
|
||||
!options.has_key?(:dasherize) || options[:dasherize]
|
||||
end
|
||||
|
||||
def camelize?
|
||||
options.has_key?(:camelize) && options[:camelize]
|
||||
attribute_names
|
||||
end
|
||||
|
||||
def serializable_attributes
|
||||
|
@ -134,6 +135,34 @@ module ActiveModel
|
|||
end
|
||||
|
||||
private
|
||||
def builder
|
||||
@builder ||= begin
|
||||
require 'builder' unless defined? ::Builder
|
||||
options[:indent] ||= 2
|
||||
builder = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
|
||||
|
||||
unless options[:skip_instruct]
|
||||
builder.instruct!
|
||||
options[:skip_instruct] = true
|
||||
end
|
||||
|
||||
builder
|
||||
end
|
||||
end
|
||||
|
||||
def root
|
||||
root = (options[:root] || @serializable.class.model_name.singular).to_s
|
||||
reformat_name(root)
|
||||
end
|
||||
|
||||
def dasherize?
|
||||
!options.has_key?(:dasherize) || options[:dasherize]
|
||||
end
|
||||
|
||||
def camelize?
|
||||
options.has_key?(:camelize) && options[:camelize]
|
||||
end
|
||||
|
||||
def reformat_name(name)
|
||||
name = name.camelize if camelize?
|
||||
dasherize? ? name.dasherize : name
|
||||
|
@ -163,8 +192,7 @@ module ActiveModel
|
|||
end
|
||||
|
||||
def to_xml(options = {}, &block)
|
||||
serializer = Serializer.new(self, options)
|
||||
block_given? ? serializer.to_s(&block) : serializer.to_s
|
||||
Serializer.new(self, options).serialize(&block)
|
||||
end
|
||||
|
||||
def from_xml(xml)
|
||||
|
|
|
@ -1,60 +1,58 @@
|
|||
module ActiveRecord #:nodoc:
|
||||
module Serialization
|
||||
module RecordSerializer #:nodoc:
|
||||
def initialize(*args)
|
||||
super
|
||||
options[:except] |= Array.wrap(@serializable.class.inheritance_column)
|
||||
extend ActiveSupport::Concern
|
||||
include ActiveModel::Serializers::JSON
|
||||
|
||||
def serializable_hash(options = nil)
|
||||
options ||= {}
|
||||
|
||||
options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
|
||||
options[:except] |= Array.wrap(self.class.inheritance_column)
|
||||
|
||||
hash = super(options)
|
||||
|
||||
serializable_add_includes(options) do |association, records, opts|
|
||||
hash[association] = records.is_a?(Enumerable) ?
|
||||
records.map { |r| r.serializable_hash(opts) } :
|
||||
records.serializable_hash(opts)
|
||||
end
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
private
|
||||
# Add associations specified via the <tt>:includes</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 add_includes(&block)
|
||||
if include_associations = options.delete(:include)
|
||||
base_only_or_except = { :except => options[:except],
|
||||
:only => options[:only] }
|
||||
def serializable_add_includes(options = {})
|
||||
return unless include_associations = options.delete(:include)
|
||||
|
||||
include_has_options = include_associations.is_a?(Hash)
|
||||
associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
|
||||
base_only_or_except = { :except => options[:except],
|
||||
:only => options[:only] }
|
||||
|
||||
for association in associations
|
||||
records = case @serializable.class.reflect_on_association(association).macro
|
||||
when :has_many, :has_and_belongs_to_many
|
||||
@serializable.send(association).to_a
|
||||
when :has_one, :belongs_to
|
||||
@serializable.send(association)
|
||||
end
|
||||
include_has_options = include_associations.is_a?(Hash)
|
||||
associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
|
||||
|
||||
unless records.nil?
|
||||
association_options = include_has_options ? include_associations[association] : base_only_or_except
|
||||
opts = options.merge(association_options)
|
||||
yield(association, records, opts)
|
||||
end
|
||||
for association in associations
|
||||
records = case self.class.reflect_on_association(association).macro
|
||||
when :has_many, :has_and_belongs_to_many
|
||||
send(association).to_a
|
||||
when :has_one, :belongs_to
|
||||
send(association)
|
||||
end
|
||||
|
||||
options[:include] = include_associations
|
||||
end
|
||||
end
|
||||
|
||||
def serializable_hash
|
||||
hash = super
|
||||
|
||||
add_includes do |association, records, opts|
|
||||
hash[association] =
|
||||
if records.is_a?(Enumerable)
|
||||
records.collect { |r| self.class.new(r, opts).serializable_hash }
|
||||
else
|
||||
self.class.new(records, opts).serializable_hash
|
||||
end
|
||||
unless records.nil?
|
||||
association_options = include_has_options ? include_associations[association] : base_only_or_except
|
||||
opts = options.merge(association_options)
|
||||
yield(association, records, opts)
|
||||
end
|
||||
end
|
||||
|
||||
hash
|
||||
options[:include] = include_associations
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'active_record/serializers/xml_serializer'
|
||||
require 'active_record/serializers/json_serializer'
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
module ActiveRecord #:nodoc:
|
||||
module Serialization
|
||||
extend ActiveSupport::Concern
|
||||
include ActiveModel::Serializers::JSON
|
||||
|
||||
class JSONSerializer < ActiveModel::Serializers::JSON::Serializer
|
||||
include Serialization::RecordSerializer
|
||||
end
|
||||
|
||||
def encode_json(encoder)
|
||||
JSONSerializer.new(self, encoder.options).to_s
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,6 +2,8 @@ require 'active_support/core_ext/hash/conversions'
|
|||
|
||||
module ActiveRecord #:nodoc:
|
||||
module Serialization
|
||||
include ActiveModel::Serializers::Xml
|
||||
|
||||
# Builds an XML document to represent the model. Some configuration is
|
||||
# available through +options+. However more complicated cases should
|
||||
# override ActiveRecord::Base#to_xml.
|
||||
|
@ -169,18 +171,15 @@ module ActiveRecord #:nodoc:
|
|||
# end
|
||||
# end
|
||||
def to_xml(options = {}, &block)
|
||||
serializer = XmlSerializer.new(self, options)
|
||||
block_given? ? serializer.to_s(&block) : serializer.to_s
|
||||
end
|
||||
|
||||
def from_xml(xml)
|
||||
self.attributes = Hash.from_xml(xml).values.first
|
||||
self
|
||||
XmlSerializer.new(self, options).serialize(&block)
|
||||
end
|
||||
end
|
||||
|
||||
class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
|
||||
include Serialization::RecordSerializer
|
||||
def initialize(*args)
|
||||
super
|
||||
options[:except] |= Array.wrap(@serializable.class.inheritance_column)
|
||||
end
|
||||
|
||||
def serializable_attributes
|
||||
serializable_attribute_names.collect { |name| Attribute.new(name, @serializable) }
|
||||
|
@ -235,7 +234,9 @@ module ActiveRecord #:nodoc:
|
|||
builder.tag!(*args) do
|
||||
add_attributes
|
||||
procs = options.delete(:procs)
|
||||
add_includes { |association, records, opts| add_associations(association, records, opts) }
|
||||
@serializable.send(:serializable_add_includes, options) { |association, records, opts|
|
||||
add_associations(association, records, opts)
|
||||
}
|
||||
options[:procs] = procs
|
||||
add_procs
|
||||
yield builder if block_given?
|
||||
|
|
Loading…
Reference in a new issue