Class: AttrJson::Type::PolymorphicModel
- Inherits:
-
ActiveModel::Type::Value
- Object
- ActiveModel::Type::Value
- AttrJson::Type::PolymorphicModel
- Defined in:
- lib/attr_json/type/polymorphic_model.rb
Overview
AttrJson::Type::PolymorphicModel can be used to create attr_json attributes that can hold any of various specified AttrJson::Model models. It is a somewhat experimental feature.
"polymorphic" may not be quite the right word, but we use it out of analogy with ActiveRecord polymorphic assocications, which it resembles, as well as ActiveRecord Single-Table Inheritance.
Similar to these AR features, a PolymorphicModel-typed attribute will serialize the
model name of a given value in a type json hash key, so it can deserialize
to the same correct model class.
It can be used for single-model attributes, or arrays (which can be hetereogenous),
in either AttrJson::Record or nested AttrJson::Models. If CD, Book, Person,
and Corporation are all AttrJson::Model classes:
attr_json :favorite, AttrJson::Type::PolymorphicAttribute.new(CD, Book)
attr_json :authors, AttrJson::Type::PolymorphicAttribute.new(Person, Corporation), array: true
Currently, you need a specific enumerated list of allowed types, and they all need to be AttrJson::Model classes. You can't at the moment have an "open" polymorphic type that can accept any AttrJson::Model.
You can change the json key that the "type" (class name) for a value is stored to, when creating the type:
attr_json, :author, AttrJson::Type::PolymorphicAttribute.new(Person, Corporation, type_key: "__type__")
But if you already have existing data in the db, that's gonna be problematic to change on the fly.
You can set attributes with a hash, but it needs to have an appropriate type key
(or other as set by type_key arg). If it does not, or you try to set a non-hash
value, you will get a AttrJson::Type::PolymorphicModel::TypeError. (maybe a validation
error would be better? but it's not what it does now.)
Note this also applies to loading non-compliant data from the database. If you have non-compliant data in the db, the only way to look at it will be as a serialized json string in top-level #json_attributes_before_cast (or other relevant container attribute.)
There is no built-in form support for PolymorphicModels, you'll have to work it out.
jsonb_contains support
There is basic jsonb_contains support, but no sophisticated type-casting like normal, beyond the polymorphic attribute. But you can do:
MyRecord.jsonb_contains(author: { name: "foo"})
MyRecord.jsonb_contains(author: { name: "foo", type: "Corporation"})
MyRecord.jsonb_contains(author: Corporation.new(name: "foo"))
Additionally, there is not_jsonb_contains, which creates the same query terms like jsonb_contains, but negated.
Defined Under Namespace
Classes: TypeError
Instance Attribute Summary collapse
-
#model_type_lookup ⇒ Object
readonly
Returns the value of attribute model_type_lookup.
-
#type_key ⇒ Object
readonly
Returns the value of attribute type_key.
-
#unrecognized_type ⇒ Object
readonly
Returns the value of attribute unrecognized_type.
Instance Method Summary collapse
- #cast(v) ⇒ Object
- #deserialize(v) ⇒ Object
-
#initialize(*args) ⇒ PolymorphicModel
constructor
A new instance of PolymorphicModel.
- #model_names ⇒ Object
- #model_types ⇒ Object
- #serialize(v) ⇒ Object
-
#type ⇒ Object
ActiveModel method, symbol type label.
- #type_for_model_name(model_name) ⇒ Object
-
#value_for_contains_query(key_path_arr, value) ⇒ Object
This is used only by our own keypath-chaining query stuff.
Constructor Details
#initialize(*args) ⇒ PolymorphicModel
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/attr_json/type/polymorphic_model.rb', line 62 def initialize(*args) = { type_key: "type", unrecognized_type: :raise}.merge( args..assert_valid_keys(:type_key, :unrecognized_type) ) @type_key = [:type_key] @unrecognized_type = [:unrecognized_type] model_types = args model_types.collect! do |m| if m.respond_to?(:ancestors) && m.ancestors.include?(AttrJson::Model) m.to_type else m end end if bad_arg = model_types.find { |m| !m.is_a? AttrJson::Type::Model } raise ArgumentError, "#{self.class.name} only works with AttrJson::Model / AttrJson::Type::Model, not '#{bad_arg.inspect}'" end if type_key_conflict = model_types.find { |m| m.model.attr_json_registry.has_attribute?(@type_key) } raise ArgumentError, "conflict between type_key '#{@type_key}' and an existing attr_json in #{type_key_conflict.model}" end @model_type_lookup = model_types.collect do |type| [type.model.name, type] end.to_h end |
Instance Attribute Details
#model_type_lookup ⇒ Object (readonly)
Returns the value of attribute model_type_lookup.
61 62 63 |
# File 'lib/attr_json/type/polymorphic_model.rb', line 61 def model_type_lookup @model_type_lookup end |
#type_key ⇒ Object (readonly)
Returns the value of attribute type_key.
61 62 63 |
# File 'lib/attr_json/type/polymorphic_model.rb', line 61 def type_key @type_key end |
#unrecognized_type ⇒ Object (readonly)
Returns the value of attribute unrecognized_type.
61 62 63 |
# File 'lib/attr_json/type/polymorphic_model.rb', line 61 def unrecognized_type @unrecognized_type end |
Instance Method Details
#cast(v) ⇒ Object
104 105 106 |
# File 'lib/attr_json/type/polymorphic_model.rb', line 104 def cast(v) cast_or_deserialize(v, :cast) end |
#deserialize(v) ⇒ Object
108 109 110 |
# File 'lib/attr_json/type/polymorphic_model.rb', line 108 def deserialize(v) cast_or_deserialize(v, :deserialize) end |
#model_names ⇒ Object
91 92 93 |
# File 'lib/attr_json/type/polymorphic_model.rb', line 91 def model_names model_type_lookup.keys end |
#model_types ⇒ Object
95 96 97 |
# File 'lib/attr_json/type/polymorphic_model.rb', line 95 def model_types model_type_lookup.values end |
#serialize(v) ⇒ Object
112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/attr_json/type/polymorphic_model.rb', line 112 def serialize(v) return nil if v.nil? # if it's not already a model cast it to a model if possible (eg it's a hash) v = cast(v) model_name = v.class.name type = type_for_model_name(model_name) raise_bad_model_name(model_name, v) if type.nil? type.serialize(v).merge(type_key => model_name) end |
#type ⇒ Object
ActiveModel method, symbol type label
100 101 102 |
# File 'lib/attr_json/type/polymorphic_model.rb', line 100 def type @type ||= "any_of_#{model_types.collect(&:type).collect(&:to_s).join('_')}".to_sym end |
#type_for_model_name(model_name) ⇒ Object
126 127 128 |
# File 'lib/attr_json/type/polymorphic_model.rb', line 126 def type_for_model_name(model_name) model_type_lookup[model_name] end |
#value_for_contains_query(key_path_arr, value) ⇒ Object
This is used only by our own keypath-chaining query stuff. For PolymorphicModel type, it does no type casting, just sticks whatever you gave it in, which needs to be json-compat values.
134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/attr_json/type/polymorphic_model.rb', line 134 def value_for_contains_query(key_path_arr, value) hash_arg = {} key_path_arr.each.with_index.inject(hash_arg) do |hash, (n, i)| if i == key_path_arr.length - 1 hash[n] = value else hash[n] = {} end end hash_arg end |