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::Arrayconcerns. -
#active_json_model_array_serialization_tuple ⇒ Object
OpenStruct storing the configuration of of this ActiveJsonModel::Array.
-
#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_viathat 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
290 291 292 |
# File 'lib/active_json_model/array.rb', line 290 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
283 284 285 |
# File 'lib/active_json_model/array.rb', line 283 def active_json_model_array_serialization_tuple @__active_json_model_array_serialization_tuple 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.
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 |
# File 'lib/active_json_model/array.rb', line 590 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.
263 264 265 |
# File 'lib/active_json_model/array.rb', line 263 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
271 272 273 |
# File 'lib/active_json_model/array.rb', line 271 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
297 298 299 |
# File 'lib/active_json_model/array.rb', line 297 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.
304 305 306 |
# File 'lib/active_json_model/array.rb', line 304 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 |
# File 'lib/active_json_model/array.rb', line 240 def attribute_type @attribute_type ||= ActiveRecordType.new(self) end |
#dump(obj) ⇒ Object
Dump the specified object to JSON
693 694 695 696 |
# File 'lib/active_json_model/array.rb', line 693 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.
255 256 257 |
# File 'lib/active_json_model/array.rb', line 255 def encrypted_attribute_type @encrypted_attribute_type ||= ActiveRecordEncryptedType.new(self) 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
648 649 650 651 652 653 654 655 656 657 658 |
# File 'lib/active_json_model/array.rb', line 648 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
511 512 513 514 515 516 517 518 519 520 521 522 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 |
# File 'lib/active_json_model/array.rb', line 511 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
335 336 337 338 339 340 341 342 343 344 345 346 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 |
# File 'lib/active_json_model/array.rb', line 335 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
450 451 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 |
# File 'lib/active_json_model/array.rb', line 450 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
640 641 642 |
# File 'lib/active_json_model/array.rb', line 640 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
664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 |
# File 'lib/active_json_model/array.rb', line 664 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.
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 |
# File 'lib/active_json_model/array.rb', line 565 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 |