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: 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

Constructor Details

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

Returns a new instance of DoubleModel.



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# 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

Class Attribute Details

.keyObject

Returns the value of attribute key.



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

def key
  @key
end

Instance Attribute Details

#_keyObject (readonly)

Returns the value of attribute _key.



244
245
246
# File 'lib/ecoportal/api/common/content/double_model.rb', line 244

def _key
  @_key
end

#_parentObject (readonly)

Returns the value of attribute _parent.



244
245
246
# File 'lib/ecoportal/api/common/content/double_model.rb', line 244

def _parent
  @_parent
end

Class Method Details

.embeds_many(method, key: method, order_matters: false, order_key: nil, klass: nil, enum_class: nil) ⇒ 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



199
200
201
202
203
204
205
206
207
208
209
210
211
212
# 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

.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



187
188
189
# 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

.enforce!(doc) ⇒ Object

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



115
116
117
118
119
120
121
122
# 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

.key?Boolean

Returns:

  • (Boolean)


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

def key?
  !!key
end

.new_uuid(length: 12) ⇒ Object



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

def new_uuid(length: 12)
  SecureRandom.hex(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.



42
43
44
45
46
47
48
49
50
51
52
53
# 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

.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.



59
60
61
62
63
64
65
66
67
68
69
# 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

.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



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# 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

.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?



151
152
153
154
155
156
157
# 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

.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


139
140
141
142
143
144
145
# 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

.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



108
109
110
111
# 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

.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.



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# 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

.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?



128
129
130
131
132
# 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

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



284
285
286
287
288
289
290
291
292
# 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

#as_jsonObject



315
316
317
# File 'lib/ecoportal/api/common/content/double_model.rb', line 315

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



325
326
327
328
# 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

#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



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

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



331
332
333
# File 'lib/ecoportal/api/common/content/double_model.rb', line 331

def dirty?
  as_update != {}
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:



295
296
297
298
299
300
301
302
# 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

#keyString

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

Returns:

  • (String)

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



270
271
272
273
# 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

#key=(value) ⇒ Object

Parameters:

  • the (String)

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



276
277
278
279
280
# 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

#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:



306
307
308
309
310
311
312
313
# 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


359
360
361
362
# File 'lib/ecoportal/api/common/content/double_model.rb', line 359

def print_pretty
  puts JSON.pretty_generate(as_json)
  self
end

#replace_doc(new_doc) ⇒ Object

Raises:



364
365
366
367
368
369
370
371
372
373
# 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

#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



348
349
350
351
352
353
354
355
356
357
# 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

#rootObject



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

def root
  return self if is_root?
  _parent.root
end

#to_json(*args) ⇒ Object



319
320
321
# File 'lib/ecoportal/api/common/content/double_model.rb', line 319

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