Module: ActiveJsonModel::Model

Defined in:
lib/active_json_model/model.rb

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base_class) ⇒ Object



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
# File 'lib/active_json_model/model.rb', line 15

def self.included(base_class)
  # Add all the class methods to the included class
  base_class.extend(ClassMethods)

  # Add additional settings into the class
  base_class.class_eval do
    # Make sure the objects will be ActiveModels
    include ::ActiveModel::Model unless include?(::ActiveModel::Model)

    # Make sure that it has dirty tracking
    include ::ActiveModel::Dirty unless include?(::ActiveModel::Dirty)

    # Has this model changed? Override's <code>ActiveModel::Dirty</code>'s base behavior to properly handle
    # recursive changes.
    #
    # @return [Boolean] true if any attribute has changed, false otherwise
    def changed?
      # Note: this method is implemented here versus in the module overall because if it is implemented in the
      # module overall, it doesn't properly override the implementation for <code>ActiveModel::Dirty</code> that
      # gets dynamically pulled in using the <code>included</code> hook.
      super || self.class.ancestry_active_json_model_attributes.any? do |attr|
        val = send(attr.name)
        val&.respond_to?(:changed?) && val.changed?
      end
    end

    # For new/loaded tracking
    @_active_json_model_dumped = false
    @_active_json_model_loaded = false

    # Register model validation to handle recursive validation into the model tree
    validate :active_json_model_validate

    def initialize(**kwargs)
      # Apply default values values that weren't specified
      self.class.active_json_model_attributes.filter{|attr| !attr.default.nil?}.each do |attr|
        unless kwargs.key?(attr.name)
          # Set as an instance variable to avoid being recorded as a true set value
          instance_variable_set("@#{attr.name}", attr.get_default_value)

          # Record that the value is a default
          instance_variable_set("@#{attr.name}_is_default", true)
        end
      end

      # You cannot set the fixed JSON attributes by a setter method. Instead, initialize the member variable
      # directly
      self.class.active_json_model_fixed_attributes.each do |k, v|
        instance_variable_set("@#{k}", v)
      end

      # Invoke the superclass constructor to let active model do the work of setting the attributes
      super(**kwargs).tap do |_|
        # Clear out any recorded changes as this object is starting fresh
        clear_changes_information
      end
    end
  end
end

Instance Method Details

#active_json_model_validateObject

Validate method that handles recursive validation into json_attributes. Individual validations on attributes for this model will be handled by the standard mechanism.



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/active_json_model/model.rb', line 249

def active_json_model_validate
  self.class.active_json_model_attributes.each do |attr|
    val = send(attr.name)

    # Check if attribute value is an ActiveJsonModel
    if val && val.respond_to?(:valid?)
      # This call to <code>valid?</code> is important because it will actually trigger recursive validations
      unless val.valid?
        val.errors.each do |error|
          errors.add("#{attr.name}.#{error.attribute}".to_sym, error.message)
        end
      end
    end
  end
end

#dump_to_jsonObject



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/active_json_model/model.rb', line 194

def dump_to_json
  # Record that the data has been dumped
  @_active_json_model_dumped = true

  # Get the attributes that are constants in the JSON rendering
  fixed_attributes = self.class.ancestry_active_json_model_fixed_attributes

  key_values = []

  self.class.ancestry_active_json_model_attributes.each do |attr|
    # Skip on the off chance of a name collision between normal and fixed attributes
    next if fixed_attributes.key?(attr.name)

    # Don't render the value if it is a default and configured not to
    next unless attr.render_default || !send("#{attr.name}_is_default?")

    # Get the value from the underlying attribute from the instance
    value = send(attr.name)

    # Recurse if the value is itself an ActiveJsonModel
    if value&.respond_to?(:dump_to_json)
      value = value.dump_to_json
    end

    if attr.dump_proc
      # Invoke the proc to do the translation
      value = if attr.dump_proc.arity == 2
                attr.dump_proc.call(value, self)
              else
                attr.dump_proc.call(value)
              end
    end

    key_values.push([attr.name, value])
  end

  # Iterate over all the allowed attributes (fixed and regular)
  fixed_attributes.each do |key, value|
    # Recurse if the value is itself an ActiveJsonModel (unlikely)
    if value&.respond_to?(:dump_to_json)
      value = value.dump_to_json
    end

    key_values.push([key, value])
  end

  # Render the array of key-value pairs to a hash
  key_values.to_h.tap do |_|
    # All changes are cleared after dump
    clear_changes_information
  end
end

#dumped?Boolean

Was this instance dumped to JSON?

Returns:

  • (Boolean)

    true if dumped to JSON, false otherwise



83
84
85
# File 'lib/active_json_model/model.rb', line 83

def dumped?
  @_active_json_model_dumped
end

#load_from_json(json_hash) ⇒ Object

Load data for this instance from a JSON hash

Parameters:

  • json_hash (Hash)

    hash of data to be loaded into a model instance



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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/active_json_model/model.rb', line 96

def load_from_json(json_hash)
  # Record this object was loaded
  @_active_json_model_loaded = true

  # Cache fixed attributes
  fixed_attributes = self.class.ancestry_active_json_model_fixed_attributes

  # Iterate over all the allowed attributes
  self.class.ancestry_active_json_model_attributes.each do |attr|
    begin
      # The value that was set from the hash
      json_value = json_hash[attr.name]
    rescue TypeError
      raise ArgumentError.new("Invalid value specified for json_hash. Expected hash-like object received #{json_hash.class}")
    end


    # Now translate the raw value into how it should interpreted
    if fixed_attributes.key?(attr.name)
      # Doesn't matter what the value was. Must confirm to the fixed value.
      value = fixed_attributes[attr.name]
    elsif !json_hash.key?(attr.name) && attr.default
      # Note that this logic reflects that an explicit nil value is not the same as not set. Only not set
      # generates the default.
      value = attr.get_default_value
    elsif attr.load_proc && json_value
      # Invoke the proc to get a value back. This gives the proc the opportunity to either generate a value
      # concretely or return a class to use.
      value = if attr.load_proc.arity == 2
                attr.load_proc.call(json_value, json_hash)
              else
                attr.load_proc.call(json_value)
              end

      if value
        # If it's a class, new it up assuming it will support loading from JSON.
        if value.is_a?(Class)
          # First check if it supports polymorphic behavior.
          if value.respond_to?(:active_json_model_concrete_class_from_ancestry_polymorphic)
            value = value.active_json_model_concrete_class_from_ancestry_polymorphic(json_value) || value
          end

          # New up an instance of the class for loading
          value = value.new
        end

        # If supported, recursively allow the model to load from JSON
        if value.respond_to?(:load_from_json)
          value.load_from_json(json_value)
        end
      end
    elsif attr.clazz && json_value
      # Special case certain builtin types
      if Integer == attr.clazz
        value = json_value.to_i
      elsif Float == attr.clazz
        value = json_value.to_f
      elsif String == attr.clazz
        value = json_value.to_s
      elsif Symbol == attr.clazz
        value = json_value.to_sym
      elsif DateTime == attr.clazz
        value = DateTime.iso8601(json_value)
      elsif Date == attr.clazz
        value = Date.iso8601(json_value)
      else
        # First check if it supports polymorphic behavior.
        clazz = if attr.clazz.respond_to?(:active_json_model_concrete_class_from_ancestry_polymorphic)
                  value = attr.clazz.active_json_model_concrete_class_from_ancestry_polymorphic(json_value) || attr.clazz
                else
                  attr.clazz
                end

        # New up the instance
        value = clazz.new

        # If supported, recursively allow the model to load from JSON
        if value.respond_to?(:load_from_json)
          value.load_from_json(json_value)
        end
      end
    else
      value = json_value
    end

    # Actually set the value on the instance
    send("#{attr.name}=", value)
  end

  # Now that the load is complete, mark dirty tracking as clean
  clear_changes_information

  # Invoke any on-load callbacks
  self.class.ancestry_active_json_model_load_callbacks.each do |cb|
    cb.invoke(self)
  end
end

#loaded?Boolean

Was this instance loaded from JSON?

Returns:

  • (Boolean)

    true if loaded from JSON, false otherwise



77
78
79
# File 'lib/active_json_model/model.rb', line 77

def loaded?
  @_active_json_model_loaded
end

#new?Boolean

Is this a new instance that was created without loading, and has yet to be dumped?

Returns:

  • (Boolean)

    true if new, false otherwise



89
90
91
# File 'lib/active_json_model/model.rb', line 89

def new?
  !loaded? && !dumped?
end