Module: ActiveJsonModel::Array::ClassMethods
- Defined in:
- lib/active_json_model/array.rb
Instance Method Summary collapse
-
#active_json_model_ancestors ⇒ Array<Class>
Filter the ancestor hierarchy to those built with
ActiveJsonModel::Array
concerns. -
#active_json_model_array_serialization_tuple ⇒ Object
OpenStruct storing the configuration of of this ActiveJsonModel::Array.
-
#active_json_model_cast(vals) ⇒ Object
Convert a value that might already be an instance of this class from underlying data.
-
#active_json_model_concrete_class_from_ancestry_polymorphic(array_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_load_callbacks ⇒ Array<Proc>
A list of procs that will be executed after array_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 array_data in the JSON. -
#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_array(serialize:, deserialize:, validate: nil, keep_nils: false, errors_go_to_nil: true, nil_data_to_empty_array: false) ⇒ Object
A JSON array that uses arbitrary serialization/deserialization.
-
#json_array_of(clazz, validate: nil, keep_nils: false, errors_go_to_nil: true, nil_data_to_empty_array: false) ⇒ Object
Configure this list class to have elements of a specific ActiveJsonModel Model type.
-
#json_polymorphic_array_by(validate: nil, keep_nils: false, errors_go_to_nil: false, nil_data_to_empty_array: false, &factory) ⇒ Object
The factory for generating instances of the array when hydrating from JSON.
-
#json_polymorphic_via(&block) ⇒ Object
Define a polymorphic factory to choose the concrete class for the list model.
-
#load(json_array_data) ⇒ Object
Load an instance of the class from JSON.
-
#validator_for_item_type(clazz, recursive_validator = nil) ⇒ Proc
Crate a validator that can be used to check that items of the array of a specified type.
Instance Method Details
#active_json_model_ancestors ⇒ Array<Class>
Filter the ancestor hierarchy to those built with ActiveJsonModel::Array
concerns
302 303 304 |
# File 'lib/active_json_model/array.rb', line 302 def active_json_model_ancestors self.ancestors.filter{|o| o.respond_to?(:active_json_model_array_serialization_tuple)}.reverse end |
#active_json_model_array_serialization_tuple ⇒ Object
OpenStruct storing the configuration of of this ActiveJsonModel::Array. Properties include:
serialize_proc - proc used to translate from objects -> json
serialize_method - symbol of method name to call to translate from objects -> json
deserialize_proc - proc used to translate from json -> objects
deserialize_method - symbol of method name to call to translate from json -> objects
keep_nils - boolean flag indicating if nils should be kept in the array after de/serialization
errors_go_to_nil - boolean flag if errors should be capture from the de/serialization methods and translated
to nil
295 296 297 |
# File 'lib/active_json_model/array.rb', line 295 def active_json_model_array_serialization_tuple @__active_json_model_array_serialization_tuple end |
#active_json_model_cast(vals) ⇒ Object
Convert a value that might already be an instance of this class from underlying data. Used to delegate potential loading from ActiveRecord attributes
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 |
# File 'lib/active_json_model/array.rb', line 624 def active_json_model_cast(vals) if vals.is_a?(self) vals elsif vals.is_a?(::Array) if vals.length == 0 self.new(values: vals) elsif vals[0].respond_to?(:dump_to_json) self.new(values: vals) else self.load(vals) end elsif vals.nil? self.new(values: []) end end |
#active_json_model_concrete_class_from_ancestry_polymorphic(array_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.
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 |
# File 'lib/active_json_model/array.rb', line 602 def active_json_model_concrete_class_from_ancestry_polymorphic(array_data) clazz = nil ancestry_active_json_model_polymorphic_factory.each do |proc| clazz = proc.call(array_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(array_data) || clazz else clazz end else self end end |
#active_json_model_load_callbacks ⇒ Array<Proc>
A list of procs that will be executed after array_data has been loaded.
275 276 277 |
# File 'lib/active_json_model/array.rb', line 275 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 array_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 list model class
283 284 285 |
# File 'lib/active_json_model/array.rb', line 283 def active_json_model_polymorphic_factory @__active_json_model_polymorphic_factory end |
#ancestry_active_json_model_load_callbacks ⇒ Array<AfterLoadCallback>
Get all active json model after load callbacks for all the class hierarchy tree
309 310 311 |
# File 'lib/active_json_model/array.rb', line 309 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.
316 317 318 |
# File 'lib/active_json_model/array.rb', line 316 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 array_data would be stored as jsonb in the database
240 241 242 243 244 245 246 |
# File 'lib/active_json_model/array.rb', line 240 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
725 726 727 728 |
# File 'lib/active_json_model/array.rb', line 725 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 array_data would be stored as a string in the database, encrypted using a symmetric key at the application level.
259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/active_json_model/array.rb', line 259 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
680 681 682 683 684 685 686 687 688 689 690 |
# File 'lib/active_json_model/array.rb', line 680 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_array(serialize:, deserialize:, validate: nil, keep_nils: false, errors_go_to_nil: true, nil_data_to_empty_array: false) ⇒ Object
A JSON array that uses arbitrary serialization/deserialization.
Example:
class DateTimeArray
include ::ActiveJsonModel::Array
json_array serialize: ->(dt){ dt.iso8601 }
deserialize: ->(s){ DateTime.iso8601(s) }
end
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 |
# File 'lib/active_json_model/array.rb', line 523 def json_array(serialize:, deserialize:, validate: nil, keep_nils: false, errors_go_to_nil: true, nil_data_to_empty_array: false) unless serialize && (serialize.is_a?(Proc) || serialize.is_a?(Symbol)) raise ArgumentError.new("Must specify serialize to json_array and it must be either a proc or a symbol to refer to a method in the class") end if serialize.is_a?(Proc) && serialize.arity != 1 raise ArgumentError.new("Serialize proc must take exactly one argument.") end unless deserialize && (deserialize.is_a?(Proc) || deserialize.is_a?(Symbol)) raise ArgumentError.new("Must specify deserialize to json_array and it must be either a proc or a symbol to refer to a method in the class") end if deserialize.is_a?(Proc) && deserialize.arity != 1 raise ArgumentError.new("Deserialize proc must take exactly one argument.") end if @__active_json_model_array_serialization_tuple raise ArgumentError.new("json_array_of, json_polymorphic_array_by, and json_array are exclusive. Exactly one of them must be specified.") end @__active_json_model_array_serialization_tuple = OpenStruct.new.tap do |t| if serialize.is_a?(Proc) t.serialize_proc = serialize else t.serialize_method = serialize end if deserialize.is_a?(Proc) t.deserialize_proc = deserialize else t.deserialize_method = deserialize end if validate if validate.is_a?(Proc) t.validate_proc = validate else t.validate_method = validate end end t.keep_nils = keep_nils t.errors_go_to_nil = errors_go_to_nil t.nil_data_to_empty_array = nil_data_to_empty_array end end |
#json_array_of(clazz, validate: nil, keep_nils: false, errors_go_to_nil: true, nil_data_to_empty_array: false) ⇒ Object
Configure this list class to have elements of a specific ActiveJsonModel Model type.
Example:
class PhoneNumber
include ::ActiveJsonModel::Model
json_attribute :number, String
json_attribute :label, String
end
class PhoneNumberArray
include ::ActiveJsonModel::Array
json_array_of PhoneNumber
end
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/active_json_model/array.rb', line 347 def json_array_of(clazz, validate: nil, keep_nils: false, errors_go_to_nil: true, nil_data_to_empty_array: false) unless clazz && clazz.is_a?(Class) raise ArgumentError.new("json_array_of must be passed a class to use as the type for elements of the array. Received '#{clazz}'") end unless [Integer, Float, String, Symbol, DateTime, Date].any?{|c| c == clazz} || clazz.include?(::ActiveJsonModel::Model) raise ArgumentError.new("Class used with json_array_of must include ActiveJsonModel::Model or be of type Integer, Float, String, Symbol, DateTime, or Date") end if @__active_json_model_array_serialization_tuple raise ArgumentError.new("json_array_of, json_polymorphic_array_by, and json_array are exclusive. Exactly one of them must be specified.") end # Delegate the real work to a serialize/deserialize approach. if clazz == Integer json_array(serialize: ->(o){ o }, deserialize: ->(d){ d&.to_i }, validate: validator_for_item_type(Integer, validate), keep_nils: keep_nils, errors_go_to_nil: errors_go_to_nil, nil_data_to_empty_array: nil_data_to_empty_array) elsif clazz == Float json_array(serialize: ->(o){ o }, deserialize: ->(d){ d&.to_f }, validate: validator_for_item_type(Float, validate), keep_nils: keep_nils, errors_go_to_nil: errors_go_to_nil, nil_data_to_empty_array: nil_data_to_empty_array) elsif clazz == String json_array(serialize: ->(o){ o }, deserialize: ->(d){ d&.to_s }, validate: validator_for_item_type(String, validate), keep_nils: keep_nils, errors_go_to_nil: errors_go_to_nil, nil_data_to_empty_array: nil_data_to_empty_array) elsif clazz == Symbol json_array(serialize: ->(o){ o&.to_s }, deserialize: ->(d){ d&.to_sym }, validate: validator_for_item_type(Symbol, validate), keep_nils: keep_nils, errors_go_to_nil: errors_go_to_nil, nil_data_to_empty_array: nil_data_to_empty_array) elsif clazz == DateTime json_array(serialize: ->(o){ o&.iso8601 }, deserialize: ->(d){ DateTime.iso8601(d) }, validate: validator_for_item_type(DateTime, validate), keep_nils: keep_nils, errors_go_to_nil: errors_go_to_nil, nil_data_to_empty_array: nil_data_to_empty_array) elsif clazz == Date json_array(serialize: ->(o){ o&.iso8601 }, deserialize: ->(d){ Date.iso8601(d) }, validate: validator_for_item_type(Date, validate), keep_nils: keep_nils, errors_go_to_nil: errors_go_to_nil, nil_data_to_empty_array: nil_data_to_empty_array) else # This is the case where this is a Active JSON Model json_array( serialize: ->(o) { if o && o.respond_to?(:dump_to_json) o.dump_to_json else o end }, deserialize: ->(d) { c = if clazz&.respond_to?(:active_json_model_concrete_class_from_ancestry_polymorphic) clazz.active_json_model_concrete_class_from_ancestry_polymorphic(d) || clazz else clazz end if c c.new.tap do |m| m.load_from_json(d) end else nil end }, validate: validator_for_item_type(clazz, validate), keep_nils: keep_nils, errors_go_to_nil: errors_go_to_nil, nil_data_to_empty_array: nil_data_to_empty_array) end end |
#json_polymorphic_array_by(validate: nil, keep_nils: false, errors_go_to_nil: false, nil_data_to_empty_array: false, &factory) ⇒ Object
The factory for generating instances of the array when hydrating from JSON. The factory must return the ActiveJsonModel::Model implementing class chosen.
Example:
class PhoneNumber
include ::ActiveJsonModel::Model
json_attribute :number, String
json_attribute :label, String
end
class Email
include ::ActiveJsonModel::Model
json_attribute :address, String
json_attribute :label, String
end
class ContactInfoArray
include ::ActiveJsonModel::Array
json_polymorphic_array_by do |item_data|
if item_data.key?(:address)
Email
else
PhoneNumber
end
end
end
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 |
# File 'lib/active_json_model/array.rb', line 462 def json_polymorphic_array_by(validate: nil, keep_nils: false, errors_go_to_nil: false, nil_data_to_empty_array: false, &factory) unless factory && factory.arity == 1 raise ArgumentError.new("Must pass block taking one argument to json_polymorphic_array_by") end if @__active_json_model_array_serialization_tuple raise ArgumentError.new("json_array_of, json_polymorphic_array_by, and json_array are exclusive. Exactly one of them must be specified.") end # Delegate the real work to a serialize/deserialize approach. json_array( serialize: ->(o) { if o && o.respond_to?(:dump_to_json) o.dump_to_json else o end }, deserialize: ->(d) { clazz = factory.call(d) if clazz&.respond_to?(:active_json_model_concrete_class_from_ancestry_polymorphic) clazz = clazz.active_json_model_concrete_class_from_ancestry_polymorphic(d) || clazz end if clazz clazz.new.tap do |m| m.load_from_json(d) end else nil end }, validate: validate, keep_nils: keep_nils, errors_go_to_nil: errors_go_to_nil, nil_data_to_empty_array: nil_data_to_empty_array) end |
#json_polymorphic_via(&block) ⇒ Object
Define a polymorphic factory to choose the concrete class for the list model. Note that because the array_data passed to the block is an array of models, you must account for what the behavior is if there are no elements.
Example:
class BaseWorkflowArray
include ::ActiveJsonModel::List
json_polymorphic_via do |array_data|
if array_data[0]
if array_data[0][:type] == 'email'
EmailWorkflow
else
WebhookWorkflow
end
else
BaseWorkflowArray
end
end
end
class EmailWorkflow < BaseWorkflow
def home_emails
filter{|e| e.label == 'home'}
end
end
class WebhookWorkflow < BaseWorkflow
def secure_webhooks
filter{|wh| wh.secure }
end
end
672 673 674 |
# File 'lib/active_json_model/array.rb', line 672 def json_polymorphic_via(&block) @__active_json_model_polymorphic_factory = block end |
#load(json_array_data) ⇒ Object
Load an instance of the class from JSON
696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 |
# File 'lib/active_json_model/array.rb', line 696 def load(json_array_data) if json_array_data.nil? || (json_array_data.is_a?(String) && json_array_data.blank?) clazz = active_json_model_concrete_class_from_ancestry_polymorphic([]) if clazz&.active_json_model_array_serialization_tuple&.nil_data_to_empty_array return clazz.new.tap do |instance| instance.load_from_json(nil) end else return nil end end # Get the array_data to a hash, regardless of the starting array_data type array_data = json_array_data.is_a?(String) ? JSON.parse(json_array_data) : json_array_data # Recursively make the value have indifferent access array_data = ::ActiveJsonModel::Utils.recursively_make_indifferent(array_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(array_data) clazz.new.tap do |instance| instance.load_from_json(array_data) end end |
#validator_for_item_type(clazz, recursive_validator = nil) ⇒ Proc
Crate a validator that can be used to check that items of the array of a specified type.
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 |
# File 'lib/active_json_model/array.rb', line 577 def validator_for_item_type(clazz, recursive_validator=nil) ->(val, i, errors, me) do unless val&.is_a?(clazz) errors.add(:values, "Element #{i} must be of type #{clazz} but is of type #{val&.class}") end if recursive_validator if recursive_validator.is_a?(Proc) if recursive_validator.arity == 4 recursive_validator.call(val, i, errors, me) else recursive_validator.call(val, i, errors) end else me.send(recursive_validator, val, i) end end end end |