Module: ActiveJsonModel::Model::ClassMethods
- Defined in:
- lib/active_json_model/model.rb
Instance Method Summary collapse
-
#active_json_model_ancestors ⇒ Array<Class>
Filter the ancestor hierarchy to those built with
ActiveJsonModel::Model
concerns. -
#active_json_model_attributes ⇒ Array<JsonAttribute>
Attributes that have been defined for this class using
json_attribute
. -
#active_json_model_cast(val) ⇒ Object
Convert a value that might already be an instance of this class from underlying data.
-
#active_json_model_concrete_class_from_ancestry_polymorphic(data) ⇒ Class
Computes the concrete class that should be used to load the data based on the ancestry tree’s
json_polymorphic_via
. -
#active_json_model_fixed_attributes ⇒ Hash
Get the hash of key-value pairs that are fixed for this class.
-
#active_json_model_load_callbacks ⇒ Array<Proc>
A list of procs that will be executed after data has been loaded.
-
#active_json_model_polymorphic_factory ⇒ Object
A factory defined via
json_polymorphic_via
that allows the class to choose different concrete classes based on the data in the JSON. -
#ancestry_active_json_model_attributes ⇒ Array<JsonAttribute>
Get all active json model attributes for all the class hierarchy tree.
-
#ancestry_active_json_model_fixed_attributes ⇒ Hash
Get the hash of key-value pairs that are fixed for this class hierarchy.
-
#ancestry_active_json_model_load_callbacks ⇒ Array<AfterLoadCallback>
Get all active json model after load callbacks for all the class hierarchy tree.
-
#ancestry_active_json_model_polymorphic_factory ⇒ Array<Proc>
Get all polymorphic factories in the ancestry chain.
-
#attribute_type ⇒ Object
Allow this model to be used as ActiveRecord attribute type in Rails 5+.
-
#dump(obj) ⇒ Object
Dump the specified object to JSON.
-
#encrypted_attribute_type ⇒ Object
Allow this model to be used as ActiveRecord attribute type in Rails 5+.
-
#json_after_load(method_name = nil, &block) ⇒ Object
Register a new after load callback which is invoked after the instance is loaded from JSON.
-
#json_attribute(name, clazz = nil, default: nil, render_default: true, validation: nil, serialize_with: nil, deserialize_with: nil, &load_proc) ⇒ Object
Define a new attribute for the model that will be backed by a JSON attribute.
-
#json_fixed_attribute(name, value:) ⇒ Object
Set a fixed attribute for the current class.
-
#json_polymorphic_via(&block) ⇒ Object
Define a polymorphic factory to choose the concrete class for the model.
-
#load(json_data) ⇒ Object
Load an instance of the class from JSON.
Instance Method Details
#active_json_model_ancestors ⇒ Array<Class>
Filter the ancestor hierarchy to those built with ActiveJsonModel::Model
concerns
334 335 336 |
# File 'lib/active_json_model/model.rb', line 334 def active_json_model_ancestors self.ancestors.filter{|o| o.respond_to?(:active_json_model_attributes)}.reverse end |
#active_json_model_attributes ⇒ Array<JsonAttribute>
Attributes that have been defined for this class using json_attribute
.
312 313 314 |
# File 'lib/active_json_model/model.rb', line 312 def active_json_model_attributes @__active_json_model_attributes ||= [] end |
#active_json_model_cast(val) ⇒ Object
Convert a value that might already be an instance of this class from underlying data. Used to delegate potential loading from ActiveRecord attributes
566 567 568 569 570 571 572 |
# File 'lib/active_json_model/model.rb', line 566 def active_json_model_cast(val) if val.is_a?(self) val elsif val.is_a?(::Hash) || val.is_a?(::HashWithIndifferentAccess) self.load(val) end end |
#active_json_model_concrete_class_from_ancestry_polymorphic(data) ⇒ Class
Computes the concrete class that should be used to load the data based on the ancestry tree’s json_polymorphic_via
. Also handles potential recursion at the leaf nodes of the tree.
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 |
# File 'lib/active_json_model/model.rb', line 544 def active_json_model_concrete_class_from_ancestry_polymorphic(data) clazz = nil ancestry_active_json_model_polymorphic_factory.each do |proc| clazz = proc.call(data) break if clazz end if clazz if clazz != self && clazz.respond_to?(:active_json_model_concrete_class_from_ancestry_polymorphic) clazz.active_json_model_concrete_class_from_ancestry_polymorphic(data) || clazz else clazz end else self end end |
#active_json_model_fixed_attributes ⇒ Hash
Get the hash of key-value pairs that are fixed for this class. Fixed attributes render to the JSON payload but cannot be set directly.
363 364 365 |
# File 'lib/active_json_model/model.rb', line 363 def active_json_model_fixed_attributes @__active_json_fixed_attributes ||= {} end |
#active_json_model_load_callbacks ⇒ Array<Proc>
A list of procs that will be executed after data has been loaded.
319 320 321 |
# File 'lib/active_json_model/model.rb', line 319 def active_json_model_load_callbacks @__active_json_model_load_callbacks ||= [] end |
#active_json_model_polymorphic_factory ⇒ Object
A factory defined via json_polymorphic_via
that allows the class to choose different concrete classes based on the data in the JSON. Property is for only this class, not the entire class hierarchy.
@ return [Proc, nil] proc used to select the concrete base class for the model class
327 328 329 |
# File 'lib/active_json_model/model.rb', line 327 def active_json_model_polymorphic_factory @__active_json_model_polymorphic_factory end |
#ancestry_active_json_model_attributes ⇒ Array<JsonAttribute>
Get all active json model attributes for all the class hierarchy tree
341 342 343 |
# File 'lib/active_json_model/model.rb', line 341 def ancestry_active_json_model_attributes self.active_json_model_ancestors.flat_map(&:active_json_model_attributes) end |
#ancestry_active_json_model_fixed_attributes ⇒ Hash
Get the hash of key-value pairs that are fixed for this class hierarchy. Fixed attributes render to the JSON payload but cannot be set directly.
371 372 373 374 375 376 |
# File 'lib/active_json_model/model.rb', line 371 def ancestry_active_json_model_fixed_attributes self .active_json_model_ancestors .map{|a| a.active_json_model_fixed_attributes} .reduce({}, :merge) end |
#ancestry_active_json_model_load_callbacks ⇒ Array<AfterLoadCallback>
Get all active json model after load callbacks for all the class hierarchy tree
348 349 350 |
# File 'lib/active_json_model/model.rb', line 348 def ancestry_active_json_model_load_callbacks self.active_json_model_ancestors.flat_map(&:active_json_model_load_callbacks) end |
#ancestry_active_json_model_polymorphic_factory ⇒ Array<Proc>
Get all polymorphic factories in the ancestry chain.
355 356 357 |
# File 'lib/active_json_model/model.rb', line 355 def ancestry_active_json_model_polymorphic_factory self.active_json_model_ancestors.map(&:active_json_model_polymorphic_factory).filter(&:present?) end |
#attribute_type ⇒ Object
Allow this model to be used as ActiveRecord attribute type in Rails 5+.
E.g.
class Credentials < ::ActiveJsonModel; end;
class Integration < ActiveRecord::Base
attribute :credentials, Credentials.attribute_type
end
Note that this data would be stored as jsonb in the database
277 278 279 280 281 282 283 |
# File 'lib/active_json_model/model.rb', line 277 def attribute_type if Gem.find_files("active_record").any? @attribute_type ||= ::ActiveJsonModel::ActiveRecordType.new(self) else raise RuntimeError.new('ActiveRecord must be installed to use attribute_type') end end |
#dump(obj) ⇒ Object
Dump the specified object to JSON
616 617 618 619 |
# File 'lib/active_json_model/model.rb', line 616 def dump(obj) raise ArgumentError.new("Expected #{self} got #{obj.class} to dump to JSON") unless obj.is_a?(self) obj.dump_to_json end |
#encrypted_attribute_type ⇒ Object
Allow this model to be used as ActiveRecord attribute type in Rails 5+.
E.g.
class SecureCredentials < ::ActiveJsonModel; end;
class Integration < ActiveRecord::Base
attribute :secure_credentials, SecureCredentials.encrypted_attribute_type
end
Note that this data would be stored as a string in the database, encrypted using a symmetric key at the application level.
296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/active_json_model/model.rb', line 296 def encrypted_attribute_type if Gem.find_files("active_record").any? if Gem.find_files("symmetric-encryption").any? @encrypted_attribute_type ||= ::ActiveJsonModel::ActiveRecordEncryptedType.new(self) else raise RuntimeError.new('symmetric-encryption must be installed to use attribute_type') end else raise RuntimeError.new('active_record must be installed to use attribute_type') end end |
#json_after_load(method_name = nil, &block) ⇒ Object
Register a new after load callback which is invoked after the instance is loaded from JSON
578 579 580 581 582 583 584 585 586 587 588 |
# File 'lib/active_json_model/model.rb', line 578 def json_after_load(method_name=nil, &block) raise ArgumentError.new("Must specify method or block for ActiveJsonModel after load") unless method_name || block raise ArgumentError.new("Can only specify method or block for ActiveJsonModel after load") if method_name && block active_json_model_load_callbacks.push( AfterLoadCallback.new( method_name: method_name, block: block ) ) end |
#json_attribute(name, clazz = nil, default: nil, render_default: true, validation: nil, serialize_with: nil, deserialize_with: nil, &load_proc) ⇒ Object
Define a new attribute for the model that will be backed by a JSON attribute
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 |
# File 'lib/active_json_model/model.rb', line 452 def json_attribute(name, clazz = nil, default: nil, render_default: true, validation: nil, serialize_with: nil, deserialize_with: nil, &load_proc) if deserialize_with && load_proc raise ArgumentError.new("Cannot specify both deserialize_with and block to json_attribute") end name = name.to_sym # Add the attribute to the collection of json attributes defined for this class active_json_model_attributes.push( JsonAttribute.new( name: name, clazz: clazz, default: default, render_default: render_default, validation: validation, dump_proc: serialize_with, load_proc: load_proc || deserialize_with ) ) # Define ActiveModel attribute methods (https://api.rubyonrails.org/classes/ActiveModel/AttributeMethods.html) # for this class. E.g. reset_<name> # # Used for dirty tracking for the model. # # @see https://api.rubyonrails.org/classes/ActiveModel/AttributeMethods/ClassMethods.html#method-i-define_attribute_methods define_attribute_methods name # Define the getter for this attribute attr_reader name # Define the setter for this attribute with proper change tracking # # @param value [...] the value to set the attribute to define_method "#{name}=" do |value| # Trigger ActiveModle's change tracking system if the value is actually changing # @see https://stackoverflow.com/questions/23958170/understanding-attribute-will-change-method send("#{name}_will_change!") unless value == instance_variable_get("@#{name}") # Set the value as a direct instance variable instance_variable_set("@#{name}", value) # Record that the value is not a default instance_variable_set("@#{name}_is_default", false) end # Check if the attribute is set to the default value. This implies this value has never been set. # @return [Boolean] true if the value has been explicitly set or loaded, false otherwise define_method "#{name}_is_default?" do !!instance_variable_get("@#{name}_is_default") end if validation validates name, validation end end |
#json_fixed_attribute(name, value:) ⇒ Object
Set a fixed attribute for the current class. A fixed attribute is a constant value that is set at the class level that still renders to the underlying JSON structure. This is useful when you have a hierarchy of classes which may have certain properties set that differentiate them in the rendered json. E.g. a type
attribute.
Example:
class BaseWorkflow
include ::ActiveJsonModel::Model
json_attribute :name
end
class EmailWorkflow < BaseWorkflow
include ::ActiveJsonModel::Model
json_fixed_attribute :type, 'email'
end
class WebhookWorkflow < BaseWorkflow
include ::ActiveJsonModel::Model
json_fixed_attribute :type, 'webhook'
end
workflows = [EmailWorkflow.new(name: 'wf1'), WebhookWorkflow.new(name: 'wf2')].map(&:dump_to_json)
# [{"name": "wf1", "type": "email"}, {"name": "wf2", "type": "webhook"}]
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 |
# File 'lib/active_json_model/model.rb', line 405 def json_fixed_attribute(name, value:) active_json_model_fixed_attributes[name.to_sym] = value # We could handle fixed attributes as just a get method, but this approach keeps them consistent with the # other attributes for things like changed tracking. instance_variable_set("@#{name}", value) # Define ActiveModel attribute methods (https://api.rubyonrails.org/classes/ActiveModel/AttributeMethods.html) # for this class. E.g. reset_<name> # # Used for dirty tracking for the model. # # @see https://api.rubyonrails.org/classes/ActiveModel/AttributeMethods/ClassMethods.html#method-i-define_attribute_methods define_attribute_methods name # Define the getter for this attribute attr_reader name # Define the setter method to prevent the value from being changed. define_method "#{name}=" do |v| unless value == v raise RuntimeError.new("#{self.class}.#{name} is an Active JSON Model fixed attribute with a value of '#{value}'. It's value cannot be set to '#{v}''.") end end end |
#json_polymorphic_via(&block) ⇒ Object
Define a polymorphic factory to choose the concrete class for the model.
Example:
class BaseWorkflow
include ::ActiveJsonModel::Model
json_polymorphic_via do |data|
if data[:type] == 'email'
EmailWorkflow
else
WebhookWorkflow
end
end
end
class EmailWorkflow < BaseWorkflow
include ::ActiveJsonModel::Model
json_fixed_attribute :type, 'email'
end
class WebhookWorkflow < BaseWorkflow
include ::ActiveJsonModel::Model
json_fixed_attribute :type, 'webhook'
end
535 536 537 |
# File 'lib/active_json_model/model.rb', line 535 def json_polymorphic_via(&block) @__active_json_model_polymorphic_factory = block end |
#load(json_data) ⇒ Object
Load an instance of the class from JSON
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 |
# File 'lib/active_json_model/model.rb', line 594 def load(json_data) if json_data.nil? || (json_data.is_a?(String) && json_data.blank?) return nil end # Get the data to a hash, regardless of the starting data type data = json_data.is_a?(String) ? ::JSON.parse(json_data) : json_data # Recursively make the value have indifferent access data = ::ActiveJsonModel::Utils.recursively_make_indifferent(data) # Get the concrete class from the ancestry tree's potential polymorphic behavior. Note this needs to be done # for each sub property as well. This just covers the outermost case. clazz = active_json_model_concrete_class_from_ancestry_polymorphic(data) clazz.new.tap do |instance| instance.load_from_json(data) end end |