diff options
-rw-r--r-- | activemodel/lib/active_model.rb | 2 | ||||
-rw-r--r-- | activemodel/lib/active_model/serialization.rb | 30 | ||||
-rw-r--r-- | activemodel/lib/active_model/serializer.rb | 60 | ||||
-rw-r--r-- | activemodel/lib/active_model/serializers/json.rb | 18 | ||||
-rw-r--r-- | activemodel/lib/active_model/serializers/xml.rb | 76 | ||||
-rw-r--r-- | activerecord/lib/active_record/serialization.rb | 80 | ||||
-rw-r--r-- | activerecord/lib/active_record/serializers/json_serializer.rb | 14 | ||||
-rw-r--r-- | activerecord/lib/active_record/serializers/xml_serializer.rb | 19 |
8 files changed, 136 insertions, 163 deletions
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index b24a929ff5..244f3a546e 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -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' diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb new file mode 100644 index 0000000000..4c0073f687 --- /dev/null +++ b/activemodel/lib/active_model/serialization.rb @@ -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 diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb deleted file mode 100644 index 5b603bdbd7..0000000000 --- a/activemodel/lib/active_model/serializer.rb +++ /dev/null @@ -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 diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index e94512fd64..ee6d48bfc6 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -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) diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index 4508a39347..86149f1e5f 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -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 - end + @options[:only] = Array.wrap(@options[:only]).map { |n| n.to_s } + @options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s } 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 + # 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 - 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) diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb index 94f1e8f1fd..b49471f7ab 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -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] } - - include_has_options = include_associations.is_a?(Hash) - associations = include_has_options ? include_associations.keys : Array.wrap(include_associations) - - 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 - - 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 + def serializable_add_includes(options = {}) + return unless include_associations = options.delete(:include) - options[:include] = include_associations - end - end + base_only_or_except = { :except => options[:except], + :only => options[:only] } + + include_has_options = include_associations.is_a?(Hash) + associations = include_has_options ? include_associations.keys : Array.wrap(include_associations) - def serializable_hash - hash = super + 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 - 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' diff --git a/activerecord/lib/active_record/serializers/json_serializer.rb b/activerecord/lib/active_record/serializers/json_serializer.rb deleted file mode 100644 index 63bf42c09d..0000000000 --- a/activerecord/lib/active_record/serializers/json_serializer.rb +++ /dev/null @@ -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 diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index 4e172bd2b6..b19920741e 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -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? |