aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model/serializable.rb
blob: 769e934dbe8ff243b7db8f25b6b0328e0b411c1b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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
150
151
152
153
154
155
156
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/array/wrap'

module ActiveModel
  # == Active Model Serializable
  #
  # Provides a basic serialization to a serializable_hash for your object.
  #
  # A minimal implementation could be:
  #
  #   class Person
  #
  #     include ActiveModel::Serializable
  #
  #     attr_accessor :name
  #
  #     def attributes
  #       {'name' => name}
  #     end
  #
  #   end
  #
  # Which would provide you with:
  #
  #   person = Person.new
  #   person.serializable_hash   # => {"name"=>nil}
  #   person.name = "Bob"
  #   person.serializable_hash   # => {"name"=>"Bob"}
  #
  # You need to declare some sort of attributes hash which contains the attributes
  # you want to serialize and their current value.
  #
  # Most of the time though, you will want to include the JSON or XML
  # serializations. Both of these modules automatically include the
  # ActiveModel::Serialization module, so there is no need to explicitly
  # include it.
  #
  # So a minimal implementation including XML and JSON would be:
  #
  #   class Person
  #
  #     include ActiveModel::Serializable::JSON
  #     include ActiveModel::Serializable::XML
  #
  #     attr_accessor :name
  #
  #     def attributes
  #       {'name' => name}
  #     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...
  #
  # Valid options are <tt>:only</tt>, <tt>:except</tt> and <tt>:methods</tt> .
  module Serializable
    extend ActiveSupport::Concern

    autoload :JSON, "active_model/serializable/json"
    autoload :XML,  "active_model/serializable/xml"

    include ActiveModel::Serializer::Scope

    module ClassMethods #:nodoc:
      def _model_serializer
        @_model_serializer ||= ActiveModel::Serializer::Finder.find(self, self)
      end
    end

    def serializable_hash(options = nil)
      options ||= {}

      attribute_names = attributes.keys.sort
      if only = options[:only]
        attribute_names &= Array.wrap(only).map(&:to_s)
      elsif except = options[:except]
        attribute_names -= Array.wrap(except).map(&:to_s)
      end

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

      method_names = Array.wrap(options[:methods]).select { |n| respond_to?(n) }
      method_names.each { |n| hash[n] = send(n) }

      serializable_add_includes(options) do |association, records, opts|
        hash[association] = if records.is_a?(Enumerable)
          records.map { |a| a.serializable_hash(opts) }
        else
          records.serializable_hash(opts)
        end
      end

      hash
    end

    # Returns a model serializer for this object considering its namespace.
    def model_serializer
      self.class._model_serializer
    end

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

      unless include.is_a?(Hash)
        include = Hash[Array.wrap(include).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
      end

      include.each do |association, opts|
        if records = send(association)
          yield association, records, opts
        end
      end
    end
  end
end