Class: Ecoportal::API::Common::Content::DoubleModel

Inherits:
Common::BaseModel
  • Object
show all
Extended by:
ClassHelpers
Includes:
ModelHelpers
Defined in:
lib/ecoportal/api/common/content/double_model.rb

Overview

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

Defined Under Namespace

Classes: NoKeyMethod, UnlinkedModel

Constant Summary collapse

NOT_USED =
Common::Content::ClassHelpers::NOT_USED

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ClassHelpers

inheritable_attrs, inheritable_class_vars, inherited, new_class, resolve_class, to_constant, to_time, uid

Constructor Details

#initialize(doc = {}, parent: self, key: nil, read_only: false) ⇒ DoubleModel

Returns a new instance of DoubleModel.



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/ecoportal/api/common/content/double_model.rb', line 268

def initialize(doc = {}, parent: self, key: nil, read_only: false)
  @_dim_vars  = []
  @_parent    = parent || self
  @_key       = key    || self
  @_read_only = read_only

  self.class.enforce!(doc)

  if (_parent == self) || read_only
    @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

Class Attribute Details

.keyObject

Returns the value of attribute key.



25
26
27
# File 'lib/ecoportal/api/common/content/double_model.rb', line 25

def key
  @key
end

Instance Attribute Details

#_keyObject (readonly)

Returns the value of attribute _key.



266
267
268
# File 'lib/ecoportal/api/common/content/double_model.rb', line 266

def _key
  @_key
end

#_parentObject (readonly)

Returns the value of attribute _parent.



266
267
268
# File 'lib/ecoportal/api/common/content/double_model.rb', line 266

def _parent
  @_parent
end

#_read_onlyObject (readonly)

Returns the value of attribute _read_only.



266
267
268
# File 'lib/ecoportal/api/common/content/double_model.rb', line 266

def _read_only
  @_read_only
end

Class Method Details

.embeds_many(method, key: method, klass: nil, enum_class: nil, order_matters: false, order_key: nil, read_only: false) ⇒ Object

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

Parameters:

  • method (Symbol)

    the method that exposes the embeded object

  • key (Symbol) (defaults to: method)

    the key that embeds it to the underlying Hash model

  • order_matters (Boolean) (defaults to: false)

    to state if the order will matter

  • klass (Class, String) (defaults to: nil)

    the class of the individual elements it embeds

  • enum_class (Class, String) (defaults to: nil)

    the class of the collection that will hold the individual elements

  • read_only (Boolean) (defaults to: false)

    whether or not should try to work around items klass missing a key

    • If set to true this is meant only for read purposes (won't be able to successufully insert)


202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/ecoportal/api/common/content/double_model.rb', line 202

def embeds_many(method, key: method, klass: nil, enum_class: nil,
  order_matters: false, order_key: nil, read_only: false)
  if enum_class
    eclass = enum_class
  elsif klass
    eclass = new_class("#{method}::#{klass}", 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, read_only: read_only) do |instance_with_called_method|
    # keep reference to the original class to resolve the `klass` dependency
    # See stackoverflow: https://stackoverflow.com/a/73709529/4352306
    referrer_class = instance_with_called_method.class
    eclass.klass = {referrer_class => klass} if klass
  end
end

.embeds_one(method, key: method, nullable: false, klass:) ⇒ Object

Helper to embed one nested object under one property

Parameters:

  • method (Symbol)

    the method that exposes the embeded object

  • key (Symbol) (defaults to: method)

    the key that embeds it to the underlying Hash model

  • klass (Class, String)

    the class of the embedded object



188
189
190
# File 'lib/ecoportal/api/common/content/double_model.rb', line 188

def embeds_one(method, key: method, nullable: false, klass:)
  embed(method, key: key, nullable: nullable, multiple: false, klass: klass)
end

.enforce!(doc) ⇒ Object

Ensures doc has the model_forced_keys. If it doesn't, it adds those missing with the defined default values



116
117
118
119
120
121
122
123
# File 'lib/ecoportal/api/common/content/double_model.rb', line 116

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

.key?Boolean

Returns:

  • (Boolean)


27
28
29
# File 'lib/ecoportal/api/common/content/double_model.rb', line 27

def key?
  !!key
end

.new_uuid(length: 24) ⇒ Object



35
36
37
# File 'lib/ecoportal/api/common/content/double_model.rb', line 35

def new_uuid(length: 24)
  uid(length)
end

.pass_reader(*methods) ⇒ Object

Note:

it does not create an instance variable

Same as attr_reader but links to a subjacent Hash model property

Parameters:

  • methods (Array<Symbol>)

    the method that exposes the value as well as its key in the underlying Hash model.



43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/ecoportal/api/common/content/double_model.rb', line 43

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) ⇒ Object

Note:

it does not create an instance variable

Same as attr_writer but links to a subjacent Hash model property

Parameters:

  • methods (Array<Symbol>)

    the method that exposes the value as well as its key in the underlying Hash model.



60
61
62
63
64
65
66
67
68
69
70
# File 'lib/ecoportal/api/common/content/double_model.rb', line 60

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) ⇒ Object

To link as plain Array to a subjacent Hash model property

Parameters:

  • methods (Array<Symbol>)

    the method that exposes the value as well as its key in the underlying Hash model.

  • order_matters (Boolean) (defaults to: true)

    does the order matter

  • uniq (Boolean) (defaults to: true)

    should it contain unique elements



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/ecoportal/api/common/content/double_model.rb', line 165

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, read_only: self._read_only)
      variable_set(var, new_obj)
    end
  end
end

.passboolean(*methods, read_only: false) ⇒ Object

To link as a Boolean to a subjacent Hash model property

Parameters:

  • methods (Array<Symbol>)

    the method that exposes the value as well as its key in the underlying Hash model.

  • read_only (Boolean) (defaults to: false)

    should it only define the reader?



152
153
154
155
156
157
158
# File 'lib/ecoportal/api/common/content/double_model.rb', line 152

def passboolean(*methods, read_only: false)
  pass_reader(*methods) {|value| value}
  unless read_only
    pass_writer(*methods) {|value| !!value}
  end
  self
end

.passdate(*methods, read_only: false) ⇒ Object

To link as a Time date to a subjacent Hash model property

Parameters:

  • methods (Array<Symbol>)

    the method that exposes the value as well as its key in the underlying Hash model.

  • read_only (Boolean) (defaults to: false)

    should it only define the reader?

See Also:

  • #passthrough


140
141
142
143
144
145
146
# File 'lib/ecoportal/api/common/content/double_model.rb', line 140

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

.passforced(method, default:, read_only: false) ⇒ Object

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

These are methods that should always be present in patch update

Parameters:

  • method (Symbol)

    the method that exposes the value as well as its key in the underlying Hash model.

  • default (Value)

    the default value that this key will be written in the model when it doesn't exixt



109
110
111
112
# File 'lib/ecoportal/api/common/content/double_model.rb', line 109

def passforced(method, default: , read_only: false)
  model_forced_keys[method.to_s.freeze] = default
  passthrough(method, read_only: read_only)
end

.passkey(method) ⇒ Object

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

This method is essential to give stability to the model

Parameters:

  • method (Symbol)

    the method that exposes the value as well as its key in the underlying Hash model.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/ecoportal/api/common/content/double_model.rb', line 80

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) ⇒ Object

Same as attr_accessor but links to a subjacent Hash model property

Parameters:

  • methods (Array<Symbol>)

    the method that exposes the value as well as its key in the underlying Hash model.

  • read_only (Boolean) (defaults to: false)

    should it only define the reader?



129
130
131
132
133
# File 'lib/ecoportal/api/common/content/double_model.rb', line 129

def passthrough(*methods, read_only: false)
  pass_reader *methods
  pass_writer *methods unless read_only
  self
end

Instance Method Details

#_doc_key(value) ⇒ Object

Offers a method for child classes to transform the key, provided that the child's doc can be accessed



307
308
309
310
311
312
313
314
315
# File 'lib/ecoportal/api/common/content/double_model.rb', line 307

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_jsonObject



339
340
341
# File 'lib/ecoportal/api/common/content/double_model.rb', line 339

def as_json
  doc
end

#as_updatenil, Hash

Returns the patch Hash model including only the changes between original_doc and doc.

Returns:

  • (nil, Hash)

    the patch Hash model including only the changes between original_doc and doc



349
350
351
352
# File 'lib/ecoportal/api/common/content/double_model.rb', line 349

def as_update
  new_doc = as_json
  Common::Content::HashDiffPatch.patch_diff(new_doc, original_doc)
end

#consolidate!Object

Note:
  • after executing it, there will be no pending changes
  • you should technically run this command, after a successful update request to the server

It makes original_doc to be like doc



364
365
366
# File 'lib/ecoportal/api/common/content/double_model.rb', line 364

def consolidate!
  replace_original_doc(JSON.parse(doc.to_json))
end

#dirty?Boolean

Returns stating if there are changes.

Returns:

  • (Boolean)

    stating if there are changes



355
356
357
358
# File 'lib/ecoportal/api/common/content/double_model.rb', line 355

def dirty?
  au = as_update
  !((au == {}) || (au == nil))
end

#docnil, Hash

Returns the underlying Hash model as is (carrying current changes).

Returns:

  • (nil, Hash)

    the underlying Hash model as is (carrying current changes)

Raises:



318
319
320
321
322
323
324
325
326
# File 'lib/ecoportal/api/common/content/double_model.rb', line 318

def doc
  return @doc if doc_var?
  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

#keyString

Returns the value of the key method (i.e. id value).

Returns:

  • (String)

    the value of the key method (i.e. id value)



293
294
295
296
# File 'lib/ecoportal/api/common/content/double_model.rb', line 293

def key
  raise NoKeyMethod.new "No key_method defined for #{self.class}" unless key_method?
  self.method(key_method).call
end

#key=(value) ⇒ Object

Parameters:

  • the (String)

    value of the key method (i.e. id value)



299
300
301
302
303
# File 'lib/ecoportal/api/common/content/double_model.rb', line 299

def key=(value)
  raise NoKeyMethod.new "No key_method defined for #{self.class}" unless key_method?
  method = "#{key_method}="
  self.method(method).call(value)
end

#original_docnil, Hash

The original_doc holds the model as is now on server-side.

Returns:

  • (nil, Hash)

    the underlying Hash model as after last consolidate! changes

Raises:



330
331
332
333
334
335
336
337
# File 'lib/ecoportal/api/common/content/double_model.rb', line 330

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


384
385
386
387
# File 'lib/ecoportal/api/common/content/double_model.rb', line 384

def print_pretty
  puts JSON.pretty_generate(as_json)
  self
end

#replace_doc(new_doc) ⇒ Object

Raises:



389
390
391
392
393
394
395
396
397
398
# File 'lib/ecoportal/api/common/content/double_model.rb', line 389

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

#reset!(key = nil) ⇒ Object

Note:
  • after executing it, changes in doc will be lost
  • you should technically run this command only if you want to remove certain changes

It makes doc to be like original



373
374
375
376
377
378
379
380
381
382
# File 'lib/ecoportal/api/common/content/double_model.rb', line 373

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

#rootObject



287
288
289
290
# File 'lib/ecoportal/api/common/content/double_model.rb', line 287

def root
  return self if is_root?
  _parent.root
end

#to_json(*args) ⇒ Object



343
344
345
# File 'lib/ecoportal/api/common/content/double_model.rb', line 343

def to_json(*args)
  doc.to_json(*args)
end