class Ecoportal::API::Common::Content::DoubleModel
Basic model class, to **build get / set `methods`** for a given property which differs of `attr_*` ruby native class methods because `pass*` completelly links the methods **to a subjacent `Hash` model**
Constants
- NOT_USED
Attributes
Public Class Methods
@note
- if you have a dedicated `Enumerable` class to manage `many`, you should use `:enum_class` - otherwise, just indicate the child class in `:klass` and it will auto generate the class
@param method [Symbol] the method that exposes the embeded object @param key [Symbol] the `key` that embeds it to the underlying `Hash` model @param order_matters [Boolean] to state if the order will matter @param klass [Class, String] the class of the individual elements it embeds @param enum_class [Class, String] the class of the collection that will hold the individual elements
# File lib/ecoportal/api/common/content/double_model.rb, line 199 def embeds_many(method, key: method, order_matters: false, order_key: nil, klass: nil, enum_class: nil) if enum_class eclass = enum_class elsif klass eclass = new_class(method, inherits: Common::Content::CollectionModel) do |dim_class| dim_class.klass = klass dim_class.order_matters = order_matters dim_class.order_key = order_key end else raise "You should either specify the 'klass' of the elements or the 'enum_class'" end embed(method, key: key, multiple: true, klass: eclass) end
Helper to embed one nested object under one property @param method [Symbol] the method that exposes the embeded object @param key [Symbol] the `key` that embeds it to the underlying `Hash` model @nullable [Boolean] to specify if this object can be `nil` @param klass [Class, String] the class of the embedded object
# File lib/ecoportal/api/common/content/double_model.rb, line 187 def embeds_one(method, key: method, nullable: false, klass:) embed(method, key: key, nullable: nullable, multiple: false, klass: klass) end
Ensures `doc` has the `model_forced_keys`. If it doesn't, it adds those missing
with the defined `default` values
# File lib/ecoportal/api/common/content/double_model.rb, line 115 def enforce!(doc) return unless doc && doc.is_a?(Hash) return if model_forced_keys.empty? model_forced_keys.each do |key, default| doc[key] = default unless doc.key?(key) end doc end
# File lib/ecoportal/api/common/content/double_model.rb, line 30 def key=(value) @key = value.to_s.freeze end
# File lib/ecoportal/api/common/content/double_model.rb, line 26 def key? !!key end
# File lib/ecoportal/api/common/content/double_model.rb, line 246 def initialize(doc = {}, parent: self, key: nil) @_dim_vars = [] @_parent = parent || self @_key = key || self self.class.enforce!(doc) if _parent == self @doc = doc @original_doc = JSON.parse(@doc.to_json) end if key_method? && doc && doc.is_a?(Hash) self.key = doc[key_method] #puts "\n$(#{self.key}<=>#{self.class})" end end
# File lib/ecoportal/api/common/content/double_model.rb, line 34 def new_uuid(length: 12) SecureRandom.hex(length) end
Same as `attr_reader` but links to a subjacent `Hash` model property @note it does not create an _instance variable_ @param methods [Array<Symbol>] the method that exposes the value
as well as its `key` in the underlying `Hash` model.
# File lib/ecoportal/api/common/content/double_model.rb, line 42 def pass_reader(*methods) methods.each do |method| method = method.to_s.freeze define_method method do value = send(:doc)[method] value = yield(value) if block_given? value end end self end
Same as `attr_writer` but links to a subjacent `Hash` model property @note it does not create an _instance variable_ @param methods [Array<Symbol>] the method that exposes the value
as well as its `key` in the underlying `Hash` model.
# File lib/ecoportal/api/common/content/double_model.rb, line 59 def pass_writer(*methods) methods.each do |method| method = method.to_s.freeze define_method "#{method}=" do |value| value = yield(value) if block_given? send(:doc)[method] = value end end self end
To link as plain `Array` to a subjacent `Hash` model property @param methods [Array<Symbol>] the method that exposes the value
as well as its `key` in the underlying `Hash` model.
@param order_matters [Boolean] does the order matter @param uniq [Boolean] should it contain unique elements
# File lib/ecoportal/api/common/content/double_model.rb, line 164 def passarray(*methods, order_matters: true, uniq: true) methods.each do |method| method = method.to_s.freeze var = instance_variable_name(method) dim_class = new_class(method, inherits: Common::Content::ArrayModel) do |klass| klass.order_matters = order_matters klass.uniq = uniq end define_method method do return instance_variable_get(var) if instance_variable_defined?(var) new_obj = dim_class.new(parent: self, key: method) variable_set(var, new_obj) end end end
To link as a `Boolean` to a subjacent `Hash` model property @param methods [Array<Symbol>] the method that exposes the value
as well as its `key` in the underlying `Hash` model.
@param read_only [Boolean] should it only define the reader?
# File lib/ecoportal/api/common/content/double_model.rb, line 151 def passboolean(*methods, read_only: false) pass_reader(*methods) {|value| value} unless read_only pass_writer(*methods) {|value| !!value} end self end
To link as a `Time` date to a subjacent `Hash` model property @see Ecoportal::API::Common::Content::DoubleModel#passthrough @param methods [Array<Symbol>] the method that exposes the value
as well as its `key` in the underlying `Hash` model.
@param read_only [Boolean] should it only define the reader?
# File lib/ecoportal/api/common/content/double_model.rb, line 139 def passdate(*methods, read_only: false) pass_reader(*methods) {|value| to_time(value)} unless read_only pass_writer(*methods) {|value| to_time(value)&.iso8601} end self end
These are methods that should always be present in patch update @note
- `DoubleModel` can be used with objects that do not use `patch_ver` - This ensures that does that do, will get the correct patch update model
@param method [Symbol] the method that exposes the value
as well as its `key` in the underlying `Hash` model.
@param default [Value] the default value that
this `key` will be written in the model when it doesn't exixt
# File lib/ecoportal/api/common/content/double_model.rb, line 108 def passforced(method, default: , read_only: false) model_forced_keys[method.to_s.freeze] = default passthrough(method, read_only: read_only) end
This method is essential to give stability to the model @note `Content::CollectionModel` needs to find elements in the doc `Array`.
The only way to do it is via the access key (i.e. `id`). However, there is no chance you can avoid invinite loop for `get_key` without setting an instance variable key at the moment of the object creation, when the `doc` is firstly received
@param method [Symbol] the method that exposes the value
as well as its `key` in the underlying `Hash` model.
# File lib/ecoportal/api/common/content/double_model.rb, line 79 def passkey(method) method = method.to_s.freeze var = instance_variable_name(method) self.key = method define_method method do return instance_variable_get(var) if instance_variable_defined?(var) value = send(:doc)[method] value = yield(value) if block_given? value end define_method "#{method}=" do |value| variable_set(var, value) value = yield(value) if block_given? send(:doc)[method] = value end self end
Same as `attr_accessor` but links to a subjacent `Hash` model property @param methods [Array<Symbol>] the method that exposes the value
as well as its `key` in the underlying `Hash` model.
@param read_only [Boolean] should it only define the reader?
# File lib/ecoportal/api/common/content/double_model.rb, line 128 def passthrough(*methods, read_only: false) pass_reader *methods pass_writer *methods unless read_only self end
Private Class Methods
# File lib/ecoportal/api/common/content/double_model.rb, line 216 def embed(method, key: method, nullable: false, multiple: false, klass:) method = method.to_s.freeze var = instance_variable_name(method).freeze k = key.to_s.freeze # retrieving method (getter) define_method(method) do return instance_variable_get(var) if instance_variable_defined?(var) unless nullable doc[k] ||= multiple ? [] : {} end return variable_set(var, nil) unless doc[k] self.class.resolve_class(klass).new( doc[k], parent: self, key: k ).tap {|obj| variable_set(var, obj)} end end
The list of keys that will be forced in the model
# File lib/ecoportal/api/common/content/double_model.rb, line 236 def model_forced_keys @forced_model_keys ||= {} end
Public Instance Methods
Offers a method for child classes to transform the key, provided that the child's `doc` can be accessed
# File lib/ecoportal/api/common/content/double_model.rb, line 284 def _doc_key(value) if value.is_a?(Content::DoubleModel) && !value.is_root? #print "?(#{value.class}<=#{value._parent.class})" value._parent._doc_key(value) else #print "!(#{value}<=#{self.class})" value end end
# File lib/ecoportal/api/common/content/double_model.rb, line 315 def as_json doc end
@return [nil, Hash] the patch `Hash` model including only the changes between
`original_doc` and `doc`
# File lib/ecoportal/api/common/content/double_model.rb, line 325 def as_update new_doc = as_json Common::Content::HashDiffPatch.patch_diff(new_doc, original_doc) end
It makes `original_doc` to be like `doc` @note
- after executing it, there will be no pending changes - you should technically run this command, after a successful update request to the server
# File lib/ecoportal/api/common/content/double_model.rb, line 339 def consolidate! replace_original_doc(JSON.parse(doc.to_json)) end
@return [Boolean] stating if there are changes
# File lib/ecoportal/api/common/content/double_model.rb, line 331 def dirty? as_update != {} end
@return [nil, Hash] the underlying `Hash` model as is (carrying current changes)
# File lib/ecoportal/api/common/content/double_model.rb, line 295 def doc raise UnlinkedModel.new(from: "#{self.class}#doc", key: _key) unless linked? if is_root? @doc else _parent.doc.dig(*[_doc_key(_key)].flatten) end end
@return [String] the `value` of the `key` method (i.e. `id` value)
# File lib/ecoportal/api/common/content/double_model.rb, line 270 def key raise "No key_method defined for #{self.class}" unless key_method? self.method(key_method).call end
@param [String] the `value` of the `key` method (i.e. `id` value)
# File lib/ecoportal/api/common/content/double_model.rb, line 276 def key=(value) raise "No key_method defined for #{self.class}" unless key_method? method = "#{key_method}=" self.method(method).call(value) end
The `original_doc` holds the model as is now on server-side. @return [nil, Hash] the underlying `Hash` model as after last `consolidate!` changes
# File lib/ecoportal/api/common/content/double_model.rb, line 306 def original_doc raise UnlinkedModel.new(from: "#{self.class}#original_doc", key: _key) unless linked? if is_root? @original_doc else _parent.original_doc.dig(*[_doc_key(_key)].flatten) end end
# File lib/ecoportal/api/common/content/double_model.rb, line 359 def print_pretty puts JSON.pretty_generate(as_json) self end
# File lib/ecoportal/api/common/content/double_model.rb, line 364 def replace_doc(new_doc) raise UnlinkedModel.new(from: "#{self.class}#replace_doc", key: _key) unless linked? if is_root? @doc = new_doc else dig_set(_parent.doc, [_doc_key(_key)].flatten, new_doc) _parent.variable_remove!(_key) unless new_doc #variables_remove! end end
It makes `doc` to be like `original` @note
- after executing it, changes in `doc` will be lost - you should technically run this command only if you want to remove certain changes
@key [Symbol] the specific part of the model you want to `reset`
# File lib/ecoportal/api/common/content/double_model.rb, line 348 def reset!(key = nil) if key keys = [key].flatten.compact odoc = original_doc.dig(*keys) odoc = odoc && JSON.parse(odoc.to_json) dig_set(doc, keys, odoc) else replace_doc(JSON.parse(original_doc.to_json)) end end
# File lib/ecoportal/api/common/content/double_model.rb, line 264 def root return self if is_root? _parent.root end
# File lib/ecoportal/api/common/content/double_model.rb, line 319 def to_json(*args) doc.to_json(*args) end
Protected Instance Methods
# File lib/ecoportal/api/common/content/double_model.rb, line 377 def is_root? _parent == self && !!defined?(@doc) end
# File lib/ecoportal/api/common/content/double_model.rb, line 381 def linked? is_root? || !!_parent.doc end
# File lib/ecoportal/api/common/content/double_model.rb, line 385 def replace_original_doc(new_doc) raise UnlinkedModel.new(from: "#{self.class}#replace_original_doc", key: _key) unless linked? if is_root? @original_doc = new_doc else dig_set(_parent.original_doc, [_doc_key(_key)].flatten, new_doc) end end
Helper to remove tracked down instance variables
# File lib/ecoportal/api/common/content/double_model.rb, line 402 def variable_remove!(key) var = instance_variable_name(key) unless !@_dim_vars.include?(var) @_dim_vars.delete(var) remove_instance_variable(var) end end
Helper to track down persistent variables
# File lib/ecoportal/api/common/content/double_model.rb, line 395 def variable_set(key, value) var = instance_variable_name(key) @_dim_vars.push(var).uniq! instance_variable_set(var, value) end
Removes all the persistent variables
# File lib/ecoportal/api/common/content/double_model.rb, line 411 def variables_remove! #puts "going to remove vars: #{@_dim_vars} on #{self.class} (parent: #{identify_parent(self._parent)})" @_dim_vars.dup.map {|k| variable_remove!(k)} end
Private Instance Methods
# File lib/ecoportal/api/common/content/double_model.rb, line 431 def dig_set(obj, keys, value) if keys.length == 1 obj[keys.first] = value else dig_set(obj[keys.first], keys.slice(1..-1), value) end end
# File lib/ecoportal/api/common/content/double_model.rb, line 418 def identify_parent(object) case object when Ecoportal::API::V2::Page::Stage "stage #{object.name}" when Ecoportal::API::V2::Page::Section "section #{object.heading}" end end
# File lib/ecoportal/api/common/content/double_model.rb, line 427 def instance_variable_name(key) self.class.instance_variable_name(key) end
# File lib/ecoportal/api/common/content/double_model.rb, line 447 def key_method self.class.key end
# File lib/ecoportal/api/common/content/double_model.rb, line 443 def key_method? self.class.key? end
# File lib/ecoportal/api/common/content/double_model.rb, line 439 def used_param?(val) self.class.used_param?(val) end