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

key[R]
_key[R]
_parent[R]

Public Class Methods

embeds_many(method, key: method, order_matters: false, order_key: nil, klass: nil, enum_class: nil) click to toggle source

@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

# File lib/ecoportal/api/common/content/double_model.rb, line 142
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
embeds_one(method, key: method, nullable: false, klass:) click to toggle source

Helper to embed one nested object under one property

# File lib/ecoportal/api/common/content/double_model.rb, line 134
def embeds_one(method, key: method, nullable: false, klass:)
  embed(method, key: key, nullable: nullable, multiple: false, klass: klass)
end
key=(value) click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 29
def key=(value)
  @key = value.to_s.freeze
end
key?() click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 25
def key?
  !!key
end
new(doc = {}, parent: self, key: nil) click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 182
def initialize(doc = {}, parent: self, key: nil)
  @_dim_vars = []
  @_parent   = parent || self
  @_key      = key    || self

  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
new_uuid(length: 12) click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 33
def new_uuid(length: 12)
  SecureRandom.hex(length)
end
pass_reader(*methods) { |value| ... } click to toggle source

Same as `attr_reader` but links to a subjacent `Hash` model property @note it does not create an _instance variable_

# File lib/ecoportal/api/common/content/double_model.rb, line 39
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
pass_writer(*methods) { |value| ... } click to toggle source

Same as `attr_writer` but links to a subjacent `Hash` model property @note it does not create an _instance variable_

# File lib/ecoportal/api/common/content/double_model.rb, line 54
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
passarray(*methods, order_matters: true, uniq: true) click to toggle source

To link as plain `Array` to a subjacent `Hash` model property @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 115
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
passdate(*methods, read_only: false) click to toggle source

To link as a `Time` date to a subjacent `Hash` model property @see Ecoportal::API::Common::Content::DoubleModel#passthrough @param read_only [Boolean] should it only define the reader?

# File lib/ecoportal/api/common/content/double_model.rb, line 104
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
passkey(method) { |value| ... } click to toggle source

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
# File lib/ecoportal/api/common/content/double_model.rb, line 72
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
passthrough(*methods, read_only: false) click to toggle source

Same as `attr_accessor` but links to a subjacent `Hash` model property @param read_only [Boolean] should it only define the reader?

# File lib/ecoportal/api/common/content/double_model.rb, line 95
def passthrough(*methods, read_only: false)
  pass_reader *methods
  pass_writer *methods unless read_only
  self
end

Private Class Methods

embed(method, key: method, nullable: false, multiple: false, klass:) click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 159
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

Public Instance Methods

_doc_key(value) click to toggle source

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 216
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
as_json() click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 238
def as_json
  doc
end
as_update() click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 246
def as_update
  new_doc = as_json
  Common::Content::HashDiffPatch.patch_diff(new_doc, original_doc)
end
consolidate!() click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 255
def consolidate!
  replace_original_doc(JSON.parse(doc.to_json))
end
dirty?() click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 251
def dirty?
  as_update != {}
end
doc() click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 226
def doc
  raise UnlinkedModel.new(from: "#{self.class}#doc", key: _key) unless linked?
  return @doc if is_root?
  _parent.doc.dig(*[_doc_key(_key)].flatten)
end
key() click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 203
def key
  raise "No key_method defined for #{self.class}" unless key_method?
  self.method(key_method).call
end
key=(value) click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 208
def key=(value)
  raise "No key_method defined for #{self.class}" unless key_method?
  method = "#{key_method}="
  self.method(method).call(value)
end
original_doc() click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 232
def original_doc
  raise UnlinkedModel.new(from: "#{self.class}#original_doc", key: _key) unless linked?
  return @original_doc if is_root?
  _parent.original_doc.dig(*[_doc_key(_key)].flatten)
end
print_pretty() click to toggle source
replace_doc(new_doc) click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 275
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)
    variables_remove!
  end
end
reset!(key = nil) click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 259
def reset!(key = nil)
  if key
    keys    = [].push(key).compact
    odoc    = original_doc.dig(*keys)
    dig_set(doc, key, odoc && JSON.parse(odoc.to_json))

  else
    replace_doc(JSON.parse(original_doc.to_json))
  end
end
root() click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 198
def root
  return self if is_root?
  _parent.root
end
to_json(*args) click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 242
def to_json(*args)
  doc.to_json(*args)
end

Protected Instance Methods

is_root?() click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 288
def is_root?
  _parent == self && !!defined?(@doc)
end
linked?() click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 292
def linked?
  is_root? || !!_parent.doc
end
replace_original_doc(new_doc) click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 296
def replace_original_doc(new_doc)
  raise UnlinkedModel.new(from: "#{self.class}#replace_original_doc", key: _key) unless linked?
  if is_root?
    @orginal_doc = new_doc
  else
    dig_set(_parent.orginal_doc, [_doc_key(_key)].flatten, new_doc)
  end
end
variable_remove!(key) click to toggle source

Helper to remove tracked down instance variables

# File lib/ecoportal/api/common/content/double_model.rb, line 313
def variable_remove!(key)
  var = instance_variable_name(key)
  unless !@_dim_vars.include?(var)
    @_dim_vars.delete(var)
    remove_instance_variable(var)
  end
end
variable_set(key, value) click to toggle source

Helper to track down persistent variables

# File lib/ecoportal/api/common/content/double_model.rb, line 306
def variable_set(key, value)
  var = instance_variable_name(key)
  @_dim_vars.push(var).uniq!
  instance_variable_set(var, value)
end
variables_remove!() click to toggle source

Removes all the persistent variables

# File lib/ecoportal/api/common/content/double_model.rb, line 322
def variables_remove!
  @_dim_vars.dup.map {|k| variable_remove!(k)}
end

Private Instance Methods

dig_set(obj, keys, value) click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 332
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
instance_variable_name(key) click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 328
def instance_variable_name(key)
  self.class.instance_variable_name(key)
end
key_method() click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 348
def key_method
  self.class.key
end
key_method?() click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 344
def key_method?
  self.class.key?
end
used_param?(val) click to toggle source
# File lib/ecoportal/api/common/content/double_model.rb, line 340
def used_param?(val)
  self.class.used_param?(val)
end