serialization.rb 5.3 KB
Newer Older
1 2 3
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/slice'

4
module ActiveModel
5
  # == Active \Model \Serialization
6 7 8 9 10 11 12 13 14 15 16
  #
  # Provides a basic serialization to a serializable_hash for your object.
  #
  # A minimal implementation could be:
  #
  #   class Person
  #     include ActiveModel::Serialization
  #
  #     attr_accessor :name
  #
  #     def attributes
17
  #       {'name' => nil}
18 19 20 21 22 23 24 25 26 27
  #     end
  #   end
  #
  # Which would provide you with:
  #
  #   person = Person.new
  #   person.serializable_hash   # => {"name"=>nil}
  #   person.name = "Bob"
  #   person.serializable_hash   # => {"name"=>"Bob"}
  #
28 29 30 31 32
  # You need to declare an attributes hash which contains the attributes you
  # want to serialize. Attributes must be strings, not symbols. When called,
  # serializable hash will use instance methods that match the name of the
  # attributes hash's keys. In order to override this behavior, take a look at
  # the private method +read_attribute_for_serialization+.
33 34 35
  #
  # Most of the time though, you will want to include the JSON or XML
  # serializations. Both of these modules automatically include the
36 37
  # <tt>ActiveModel::Serialization</tt> module, so there is no need to
  # explicitly include it.
38
  #
O
Oscar Del Ben 已提交
39
  # A minimal implementation including XML and JSON would be:
40 41 42 43 44 45 46 47
  #
  #   class Person
  #     include ActiveModel::Serializers::JSON
  #     include ActiveModel::Serializers::Xml
  #
  #     attr_accessor :name
  #
  #     def attributes
48
  #       {'name' => nil}
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
  #     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...
  #
66 67
  # Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and
  # <tt>:include</tt>. The following are all valid examples:
68
  #
69 70 71
  #   person.serializable_hash(only: 'name')
  #   person.serializable_hash(include: :address)
  #   person.serializable_hash(include: { address: { only: 'city' }})
72
  module Serialization
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
    # Returns a serialized hash of your object.
    #
    #   class Person
    #     include ActiveModel::Serialization
    #
    #     attr_accessor :name, :age
    #
    #     def attributes
    #       {'name' => nil, 'age' => nil}
    #     end
    #
    #     def capitalized_name
    #       name.capitalize
    #     end
    #   end
    #
    #   person = Person.new
    #   person.name = 'bob'
    #   person.age  = 22
    #   person.serializable_hash                # => {"name"=>"bob", "age"=>22}
93
    #   person.serializable_hash(only: :name)   # => {"name"=>"bob"}
94 95 96
    #   person.serializable_hash(except: :name) # => {"age"=>22}
    #   person.serializable_hash(methods: :capitalized_name)
    #   # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"}
97 98 99
    def serializable_hash(options = nil)
      options ||= {}

T
Troy Kruthoff 已提交
100
      attribute_names = attributes.keys
101
      if only = options[:only]
102
        attribute_names &= Array(only).map(&:to_s)
103
      elsif except = options[:except]
104
        attribute_names -= Array(except).map(&:to_s)
105 106 107 108 109
      end

      hash = {}
      attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }

110
      Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) }
111 112

      serializable_add_includes(options) do |association, records, opts|
113 114
        hash[association.to_s] = if records.respond_to?(:to_ary)
          records.to_ary.map { |a| a.serializable_hash(opts) }
115 116 117 118
        else
          records.serializable_hash(opts)
        end
      end
119

120
      hash
121
    end
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149

    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:
150
        return unless includes = options[:include]
151

152 153
        unless includes.is_a?(Hash)
          includes = Hash[Array(includes).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
154 155
        end

156
        includes.each do |association, opts|
157 158 159 160 161
          if records = send(association)
            yield association, records, opts
          end
        end
      end
162
  end
163
end