1
0
Fork 0
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:
Joshua Peek 2009-08-13 22:27:09 -05:00
parent 7a26c21d8e
commit c6bc8e6626
8 changed files with 135 additions and 162 deletions

View file

@ -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'

View 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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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'

View file

@ -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

View file

@ -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?