diff options
Diffstat (limited to 'activemodel/lib/active_model/serializers')
-rw-r--r-- | activemodel/lib/active_model/serializers/json.rb | 102 | ||||
-rw-r--r-- | activemodel/lib/active_model/serializers/xml.rb | 191 |
2 files changed, 285 insertions, 8 deletions
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index 9efd7c5f69..c845440120 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -1,12 +1,108 @@ +require 'active_support/json' +require 'active_support/core_ext/class/attribute' + module ActiveModel + # == Active Model JSON Serializer module Serializers module JSON extend ActiveSupport::Concern - include ActiveModel::Serializable::JSON + include ActiveModel::Serialization included do - ActiveSupport::Deprecation.warn "ActiveModel::Serializers::JSON is deprecated in favor of ActiveModel::Serializable::JSON" + 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
\ No newline at end of file +end diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index 620390da6b..d61d9d7119 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -1,14 +1,195 @@ +require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/array/conversions' +require 'active_support/core_ext/hash/conversions' +require 'active_support/core_ext/hash/slice' + module ActiveModel + # == Active Model XML Serializer module Serializers module Xml extend ActiveSupport::Concern - include ActiveModel::Serializable::XML + include ActiveModel::Serialization + + class Serializer #:nodoc: + class Attribute #:nodoc: + attr_reader :name, :value, :type + + def initialize(name, serializable, value) + @name, @serializable = name, serializable + value = value.in_time_zone if value.respond_to?(:in_time_zone) + @value = value + @type = compute_type + end + + def decorations + decorations = {} + decorations[:encoding] = 'base64' if type == :binary + decorations[:type] = (type == :string) ? nil : type + decorations[:nil] = true if value.nil? + decorations + end + + protected + + def compute_type + return if value.nil? + type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] + type ||= :string if value.respond_to?(:to_str) + type ||= :yaml + type + end + end + + class MethodAttribute < Attribute #:nodoc: + end + + attr_reader :options + + def initialize(serializable, options = nil) + @serializable = serializable + @options = options ? options.dup : {} + end + + def serializable_hash + @serializable.serializable_hash(@options.except(:include)) + end + + def serializable_collection + methods = Array.wrap(options[:methods]).map(&:to_s) + serializable_hash.map do |name, value| + name = name.to_s + if methods.include?(name) + self.class::MethodAttribute.new(name, @serializable, value) + else + self.class::Attribute.new(name, @serializable, value) + end + end + end + + def serialize + require 'builder' unless defined? ::Builder + + options[:indent] ||= 2 + options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent]) + + @builder = options[:builder] + @builder.instruct! unless options[:skip_instruct] - Serializer = ActiveModel::Serializable::XML::Serializer + root = (options[:root] || @serializable.class.model_name.element).to_s + root = ActiveSupport::XmlMini.rename_key(root, options) + + args = [root] + args << {:xmlns => options[:namespace]} if options[:namespace] + args << {:type => options[:type]} if options[:type] && !options[:skip_types] + + @builder.tag!(*args) do + add_attributes_and_methods + add_includes + add_extra_behavior + add_procs + yield @builder if block_given? + end + end + + private + + def add_extra_behavior + end + + def add_attributes_and_methods + serializable_collection.each do |attribute| + key = ActiveSupport::XmlMini.rename_key(attribute.name, options) + ActiveSupport::XmlMini.to_tag(key, attribute.value, + options.merge(attribute.decorations)) + end + end + + def add_includes + @serializable.send(:serializable_add_includes, options) do |association, records, opts| + add_associations(association, records, opts) + end + end + + # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well. + def add_associations(association, records, opts) + merged_options = opts.merge(options.slice(:builder, :indent)) + merged_options[:skip_instruct] = true + + if records.is_a?(Enumerable) + tag = ActiveSupport::XmlMini.rename_key(association.to_s, options) + type = options[:skip_types] ? { } : {:type => "array"} + association_name = association.to_s.singularize + merged_options[:root] = association_name + + if records.empty? + @builder.tag!(tag, type) + else + @builder.tag!(tag, type) do + records.each do |record| + if options[:skip_types] + record_type = {} + else + record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name + record_type = {:type => record_class} + end + + record.to_xml merged_options.merge(record_type) + end + end + end + else + merged_options[:root] = association.to_s + records.to_xml(merged_options) + end + end + + def add_procs + if procs = options.delete(:procs) + Array.wrap(procs).each do |proc| + if proc.arity == 1 + proc.call(options) + else + proc.call(options, @serializable) + end + end + end + end + end + + # Returns XML representing the model. Configuration can be + # passed through +options+. + # + # Without any +options+, the returned XML string will include all the model's + # attributes. For example: + # + # user = User.find(1) + # user.to_xml + # + # <?xml version="1.0" encoding="UTF-8"?> + # <user> + # <id type="integer">1</id> + # <name>David</name> + # <age type="integer">16</age> + # <created-at type="datetime">2011-01-30T22:29:23Z</created-at> + # </user> + # + # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes + # included, and work similar to the +attributes+ method. + # + # To include the result of some method calls on the model use <tt>:methods</tt>. + # + # To include associations use <tt>:include</tt>. + # + # For further documentation see activerecord/lib/active_record/serializers/xml_serializer.xml. + def to_xml(options = {}, &block) + Serializer.new(self, options).serialize(&block) + end - included do - ActiveSupport::Deprecation.warn "ActiveModel::Serializers::Xml is deprecated in favor of ActiveModel::Serializable::XML" + def from_xml(xml) + self.attributes = Hash.from_xml(xml).values.first + self end end end -end
\ No newline at end of file +end |