Module: ActiveJsonModel::Model
- Defined in:
- lib/active_json_model/model.rb
Defined Under Namespace
Modules: ClassMethods
Class Method Summary collapse
Instance Method Summary collapse
-
#active_json_model_validate ⇒ Object
Validate method that handles recursive validation into
json_attribute
s. - #dump_to_json ⇒ Object
-
#dumped? ⇒ Boolean
Was this instance dumped to JSON?.
-
#load_from_json(json_hash) ⇒ Object
Load data for this instance from a JSON hash.
-
#loaded? ⇒ Boolean
Was this instance loaded from JSON?.
-
#new? ⇒ Boolean
Is this a new instance that was created without loading, and has yet to be dumped?.
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_validate ⇒ Object
Validate method that handles recursive validation into json_attribute
s. 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.) end end end end end |
#dump_to_json ⇒ Object
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?
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
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?
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?
89 90 91 |
# File 'lib/active_json_model/model.rb', line 89 def new? !loaded? && !dumped? end |