ecoportal-api-v2 1.1.6 → 1.1.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6294103b68ebdbf099d1137865856b10baec21683bd648727976f245cf197ad
4
- data.tar.gz: e0500e2c784a46c7a82ca3aa29e44f96d116bd6898e5b35f9174162457208165
3
+ metadata.gz: 65194be30f026aa431874543d6905b5704ea081c6a3c9ef0bf9741142c4c4253
4
+ data.tar.gz: 398eaeb83ea38386670715c0fa83144004abdbd8c7edfd8581ecb7b157c8d205
5
5
  SHA512:
6
- metadata.gz: eb18fb2a167afeaea5eac0db4e13ee58d64fc348ff1ebd33942ad1bb7120cbb828ad2652ed2d6b165d7a1b53eea097338032690e26ee97f5765fd87ba2f97862
7
- data.tar.gz: 7a61a35335247d39a0e97574f5b2816a92238d93a9f5b23c62b909a92ae03817057752ffc576ae1414c5e6744a6381b473be4cd2cfc2a0fd4e7582cffd97bdd4
6
+ metadata.gz: 0761f2079d2493f6fef928f846daf4b6b0413838c4f1252c8cf31742284fc5bcfc0e8d6dbd635f2659b37a34a6b90c09ed42f7db781c868a635188354dbe4bbd
7
+ data.tar.gz: c979ae5a583898d47c1b8501d19ea651f31013a78461008c4ed6eff69c6b1a271c2c812dbbc2a52bc82d90b7caf67e9a286e8d488639eb8bd5a8fd6678c91659
data/CHANGELOG.md CHANGED
@@ -1,12 +1,24 @@
1
1
  # Change Log
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
- ## [1.1.7] - 2024-02-xx
4
+ ## [1.1.8] - 2024-03-xx
5
5
 
6
6
  ### Added
7
7
  ### Changed
8
8
  ### Fixed
9
9
 
10
+ ## [1.1.7] - 2024-03-xx
11
+
12
+ ### Added
13
+ - `Ecoportal::API::Common::Concerns::Benchmarkable`
14
+ - `Ecoportal::API::Common::Conent::CollectionModel`
15
+ - added ability to define **default** value for `read_only`
16
+
17
+ ### Fixed
18
+ - `Ecoportal::API::Common::Content::CollectionModel` tidied up item `klass` scoping
19
+ - **added** some benchmarking too
20
+ - memoize `@_items`
21
+
10
22
  ## [1.1.6] - 2024-02-29
11
23
 
12
24
  ### Added
@@ -0,0 +1,160 @@
1
+ module Ecoportal
2
+ module API
3
+ module Common
4
+ module Concerns
5
+ # @note While benchmarking with multi-thread gives correct results
6
+ # for top unique block, will calculate the real time of each individual
7
+ # thread, which is way higher than a single thread. However, as they
8
+ # run in parallel, the total time would be the valid reference.
9
+ module Benchmarkable
10
+ private
11
+
12
+ def benchmark_enabled?
13
+ @benchmark_enabled = true if @benchmark_enabled.nil?
14
+ @benchmark_enabled
15
+ end
16
+
17
+ def benchmarking(ref = nil, print: false)
18
+ return yield unless benchmark_enabled?
19
+ benchmark_mem(ref, print: false) do
20
+ benchmark_time(ref, print: false) do
21
+ yield
22
+ end
23
+ end.tap do
24
+ puts "\n#{bench_summary_ref(ref)}" if print
25
+ end
26
+ end
27
+
28
+ def benchmark_mem(ref = nil, print: false)
29
+ return yield unless benchmark_enabled?
30
+ memory_before = memory_usage
31
+ yield.tap do
32
+ memory_after = memory_usage
33
+ mb_footprint = ((memory_after - memory_before) / 1024.0).round(2)
34
+ bench_add_mem(ref, mb_footprint)
35
+ puts bench_str_mem(mb, ref: ref, reffix: true) if print
36
+ end
37
+ end
38
+
39
+ def benchmark_time(ref = nil, print: false)
40
+ return yield unless benchmark_enabled?
41
+ result = nil
42
+ time = benchmark.realtime do
43
+ result = yield
44
+ end.round(2)
45
+ result.tap do
46
+ bench_add_time(ref, time)
47
+ puts bench_str_time(time, ref: ref, reffix: true) if print
48
+ end
49
+ end
50
+
51
+ def benchmark_summary(ref = nil)
52
+ return '' unless benchmark_enabled?
53
+ return bench_summary_ref(ref) unless ref == :all
54
+
55
+ bench.keys.each_with_object([]) do |ref, out|
56
+ out << bench_summary_ref(ref)
57
+ end.join("\n")
58
+ end
59
+
60
+ def bench_summary_ref(ref = nil)
61
+ ref_lines = bench_summary_ref_lines(ref)
62
+ "- #{ref}\n" + " • " + ref_lines.join("\n • ")
63
+ end
64
+
65
+ def bench_summary_ref_lines(ref = nil)
66
+ ref_bench = bench_get(ref)
67
+ [
68
+ bench_str_time(*ref_bench[:time].values_at(:avg, :cnt), ref: ref),
69
+ bench_str_mem(*ref_bench[:mem].values_at(:avg, :cnt), ref: ref)
70
+ ]
71
+ end
72
+
73
+ def benchmark
74
+ require 'benchmark'
75
+ Benchmark
76
+ end
77
+
78
+ def bench
79
+ @bench ||= {}
80
+ end
81
+
82
+ def bench_add_mem(ref, mem)
83
+ bench_data_push(bench_get(ref)[:mem], mem)
84
+ end
85
+
86
+ def bench_add_time(ref, time)
87
+ bench_data_push(bench_get(ref)[:time], time)
88
+ end
89
+
90
+ def bench_get(ref)
91
+ bench[ref] ||= {
92
+ time: bench_data,
93
+ mem: bench_data
94
+ }
95
+ end
96
+
97
+ def bench_data
98
+ {
99
+ avg: nil,
100
+ cnt: 0
101
+ }
102
+ end
103
+
104
+ def bench_data_push(data, value)
105
+ total = value + ( (data[:avg] || 0) * data[:cnt])
106
+ data[:cnt] += 1
107
+ data[:avg] = (total / data[:cnt]).round(3)
108
+ data
109
+ end
110
+
111
+ def bench_str_mem(mem, count = nil, ref: nil, reffix: false)
112
+ ref = reffix ? ref : nil
113
+ msg = [ref, 'Memory'].compact.join(' -- ')
114
+ cnt = count ? " (cnt: #{count})" : ''
115
+ "#{msg}: #{mem} MB#{cnt}"
116
+ end
117
+
118
+ def active_support_duration?
119
+ return false unless Kernel.const_defined?(:ActiveSupport)
120
+ ActiveSupport.const_defined?(:Duration, false)
121
+ end
122
+
123
+ def bench_str_time(time, count = nil, ref: nil, reffix: false)
124
+ ref = reffix ? ref : nil
125
+ msg = [ref, 'Time'].compact.join(' -- ')
126
+ total = (time * count).round(2)
127
+ str_desc = ''
128
+ if active_support_duration? && total >= 60
129
+ duration = ActiveSupport::Duration.build(total.round)
130
+ str_desc = ": #{duration_to_s(duration)}"
131
+ end
132
+ cnt = count ? " (cnt: #{count}; sum: #{total} s#{str_desc})" : ''
133
+ "#{msg}: #{time} s#{cnt}"
134
+ end
135
+
136
+ def duration_to_s(value)
137
+ return "" unless active_support_duration?
138
+ return "" if value.nil?
139
+
140
+ raise ArgumentError, "Expecint ActiveSupport::Duration. Given: #{value.class}" unless value.is_a?(ActiveSupport::Duration)
141
+ parts = value.parts.map {|pair| pair.reverse.join(" ")}
142
+ return parts.first if parts.length == 1
143
+ [parts[..-2].join(", "), parts.last].join(" and ")
144
+ end
145
+
146
+ # @return [Integer] total memory used by this process in KB
147
+ def memory_usage
148
+ if Gem.win_platform?
149
+ wmem = `wmic process where processid=#{Process.pid} get WorkingSetSize | findstr "[0-9]"`
150
+ return 0 unless wmem
151
+ wmem.lines.first.chop.strip.to_i / 1024.0
152
+ else
153
+ `ps -o rss= -p #{Process.pid}`.to_i
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,10 @@
1
+ module Ecoportal
2
+ module API
3
+ module Common
4
+ module Concerns
5
+ end
6
+ end
7
+ end
8
+ end
9
+
10
+ require 'ecoportal/api/common/concerns/benchmarkable'
@@ -34,7 +34,7 @@ module Ecoportal
34
34
 
35
35
  inheritable_class_vars :order_matteres, :uniq
36
36
 
37
- def initialize(doc = [], parent: self, key: nil, read_only: false)
37
+ def initialize(doc = [], parent: self, key: nil, read_only: self.class.read_only?)
38
38
  super(doc, parent: parent, key: key, read_only: read_only)
39
39
  end
40
40
 
@@ -6,7 +6,6 @@ module Ecoportal
6
6
  # @note to be able to refer to the correct element of the Collection,
7
7
  # it is required that those elements have a unique `key` that allows to identify them
8
8
  class CollectionModel < Content::DoubleModel
9
-
10
9
  class << self
11
10
  attr_writer :klass
12
11
  attr_accessor :order_matters, :order_key
@@ -26,24 +25,24 @@ module Ecoportal
26
25
  # - use block to define `klass` callback
27
26
  # @note When `klass` is resolved, if the items are of type
28
27
  # `DoubleModel`, it sets on the collection class the `items_key`
28
+ # @note when `klass` is directly resolved (not via doc) only once
29
+ # it will set @klass as resolved and will use this class from now on.
30
+ # This is an optimization to cut class lookups
29
31
  # @param value [Hash] base `doc` (raw object) to create the object with
30
32
  # @yield [doc] identifies the target `class` of the raw object
31
33
  # @yieldparam doc [Hash]
32
34
  # @yieldreturn [Klass] the target `class`
33
35
  # @return [Klass] the target `class`
34
36
  def klass(value = NOT_USED, &block)
35
- if block
36
- @klass = block
37
- block.call(value) if value != NOT_USED
37
+ @klass = block if block_given?
38
+
39
+ if @klass && !@class.is_a?(Proc)
40
+ @klass = resolve_class(@klass, exception: false) unless @klass.is_a?(Class)
38
41
  @klass
39
- elsif used_param?(value)
40
- if @klass.is_a?(Proc)
41
- @klass.call(value)
42
- else
43
- resolve_class(@klass, exception: false)
44
- end
42
+ elsif @klass.is_a?(Proc) && used_param?(value)
43
+ @klass.call(value)
45
44
  else
46
- resolve_class(@klass, exception: false)
45
+ @klass
47
46
  end.tap do |result|
48
47
  next unless result.is_a?(Class)
49
48
  next unless result < Ecoportal::API::Common::Content::DoubleModel
@@ -51,6 +50,19 @@ module Ecoportal
51
50
  end
52
51
  end
53
52
 
53
+ # @return [Boolean] are there the factory logics to build item objects defined?
54
+ def klass?
55
+ @klass || @new_item
56
+ end
57
+
58
+ # Optimization
59
+ def new_item_class_based?
60
+ return false if @new_item.is_a?(Proc)
61
+ return false if @klass.is_a?(Proc)
62
+ return true if klass.is_a?(Class)
63
+ false
64
+ end
65
+
54
66
  # Generates a new object of the target class
55
67
  # @note
56
68
  # - use block to define `new_item` callback, which will prevail over `klass`
@@ -60,29 +72,24 @@ module Ecoportal
60
72
  # @yield [doc, parent, key] creates an object instance of the target `klass`
61
73
  # @yieldparam doc [Hash]
62
74
  # @yieldreturn [Klass] instance object of the target `klass`
75
+ # @parent [CollectionModel] the parent of the new item
76
+ # @key [Symbol, String] the key value to access the item within collection
77
+ # Please observe that items in a CollectionModel are identified via their key attr.
78
+ # Meaning that there is actually no need to define this argument.
63
79
  # @return [Klass] instance object of the target `klass`
64
80
  def new_item(doc = NOT_USED, parent: nil, key: nil, read_only: false, &block)
65
- if block
66
- @new_item = block
67
- elsif used_param?(doc)
68
- raise "You should define either a 'klass' or a 'new_item' callback first" unless klass?
69
- if @new_item
70
- @new_item.call(doc, parent, key)
71
- else
72
- if target_class = self.klass(doc)
73
- doc.is_a?(target_class) ? doc : target_class.new(doc, parent: parent, key: key, read_only: read_only)
74
- else
75
- raise "Could not find a class for: #{doc}"
76
- end
77
- end
78
- else
79
- raise "To define the 'new_item' callback (factory), you need to use a block"
80
- end
81
- end
81
+ return (@new_item = block; nil) if block_given
82
82
 
83
- # @return [Boolean] are there the factory logics to build item objects defined?
84
- def klass?
85
- @klass || @new_item
83
+ msg = "To define the 'new_item' callback (factory), you need to use a block"
84
+ raise msg unless used_param?(doc)
85
+ msg = "You should define either a 'klass' or a 'new_item' callback first"
86
+ raise msg unless klass?
87
+ return @new_item.call(doc, parent, key) if @new_item.is_a?(Proc)
88
+
89
+ raise "Could not find a class for: #{doc}" unless target_class = klass(doc)
90
+ return doc if doc.is_a?(target_class)
91
+
92
+ target_class.new(doc, parent: parent, key: key, read_only: read_only)
86
93
  end
87
94
 
88
95
  def doc_class(name)
@@ -166,6 +173,7 @@ module Ecoportal
166
173
  _doc_items.each do |item_doc|
167
174
  elements << new_item(item_doc)
168
175
  end
176
+ @_items = elements if read_only?
169
177
  end
170
178
  end
171
179
 
@@ -275,7 +283,11 @@ module Ecoportal
275
283
  private
276
284
 
277
285
  def new_item(value)
278
- self.class.new_item(value, parent: self, read_only: self._read_only)
286
+ if self.class.new_item_class_based?
287
+ self.class.klass.new(value, parent: self, read_only: self._read_only)
288
+ else
289
+ self.class.new_item(value, parent: self, read_only: self._read_only)
290
+ end
279
291
  end
280
292
 
281
293
  # Helper to remove tracked down instance variables
@@ -38,6 +38,16 @@ module Ecoportal
38
38
  uid(length)
39
39
  end
40
40
 
41
+ def read_only?
42
+ @read_only = false if @read_only.nil?
43
+ @read_only
44
+ end
45
+
46
+ # Be able to define if a class should be read-only
47
+ def read_only!
48
+ @read_only = true
49
+ end
50
+
41
51
  # Same as `attr_reader` but links to a subjacent `Hash` model property
42
52
  # @note it does **not** create an _instance variable_
43
53
  # @param methods [Array<Symbol>] the method that exposes the value
@@ -176,7 +186,7 @@ module Ecoportal
176
186
 
177
187
  define_method method do
178
188
  return instance_variable_get(var) if instance_variable_defined?(var)
179
- new_obj = dim_class.new(parent: self, key: method, read_only: self._read_only)
189
+ new_obj = dim_class.new(parent: self, key: method, read_only: self.read_only?)
180
190
  variable_set(var, new_obj)
181
191
  end
182
192
  end
@@ -202,7 +212,7 @@ module Ecoportal
202
212
  # @param read_only [Boolean] whether or not should try to **work around** items `klass` missing a `key`
203
213
  # - If set to `true` this is meant only for read purposes (won't be able to successufully insert)
204
214
  def embeds_many(method, key: method, klass: nil, enum_class: nil,
205
- order_matters: false, order_key: nil, read_only: false)
215
+ order_matters: false, order_key: nil, read_only: self.read_only?)
206
216
  if enum_class
207
217
  eclass = enum_class
208
218
  elsif klass
@@ -210,6 +220,7 @@ module Ecoportal
210
220
  dim_class.klass = klass
211
221
  dim_class.order_matters = order_matters
212
222
  dim_class.order_key = order_key
223
+ dim_class.read_only! if read_only
213
224
  end
214
225
  else
215
226
  raise "You should either specify the 'klass' of the elements or the 'enum_class'"
@@ -224,7 +235,7 @@ module Ecoportal
224
235
 
225
236
  private
226
237
 
227
- def embed(method, key: method, nullable: false, multiple: false, klass:, read_only: false, &block)
238
+ def embed(method, key: method, nullable: false, multiple: false, klass:, read_only: self.read_only?, &block)
228
239
  method = method.to_s.freeze
229
240
  var = instance_variable_name(method).freeze
230
241
  k = key.to_s.freeze
@@ -251,7 +262,7 @@ module Ecoportal
251
262
  end
252
263
  end
253
264
 
254
- embedded_class.new(doc[k], parent: self, key: k, read_only: self._read_only || read_only).tap do |obj|
265
+ embedded_class.new(doc[k], parent: self, key: k, read_only: self.read_only? || read_only).tap do |obj|
255
266
  variable_set(var, obj)
256
267
  end
257
268
  end
@@ -263,12 +274,12 @@ module Ecoportal
263
274
  end
264
275
  end
265
276
 
266
- inheritable_class_vars :forced_model_keys, :key
277
+ inheritable_class_vars :forced_model_keys, :key, :read_only
267
278
 
268
279
  # `_key` refers to the parent's property that links to this model
269
280
  attr_reader :_parent, :_key, :_read_only
270
281
 
271
- def initialize(doc = {}, parent: self, key: nil, read_only: false)
282
+ def initialize(doc = {}, parent: self, key: nil, read_only: self.class.read_only?)
272
283
  @_dim_vars = []
273
284
  @_parent = parent || self
274
285
  @_key = key || self
@@ -287,6 +298,12 @@ module Ecoportal
287
298
  end
288
299
  end
289
300
 
301
+ # @note `read_only` allows for some optimizations, such as storing values
302
+ # in instance variables, for optimization purposes
303
+ def read_only?
304
+ @_read_only
305
+ end
306
+
290
307
  def root
291
308
  return self if is_root?
292
309
  _parent.root
@@ -408,6 +425,9 @@ module Ecoportal
408
425
  !!defined?(@doc)
409
426
  end
410
427
 
428
+ # Both requisites
429
+ # @note that for optimization purposes, `@doc` var may be used when
430
+ # the object is `read_only?`
411
431
  def is_root?
412
432
  _parent == self && doc_var?
413
433
  end
@@ -5,4 +5,5 @@ module Ecoportal
5
5
  end
6
6
  end
7
7
 
8
+ require 'ecoportal/api/common/concerns'
8
9
  require 'ecoportal/api/common/content'
@@ -1,5 +1,5 @@
1
1
  module Ecoportal
2
2
  module API
3
- GEM2_VERSION = "1.1.6"
3
+ GEM2_VERSION = "1.1.7"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ecoportal-api-v2
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.6
4
+ version: 1.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oscar Segura
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-29 00:00:00.000000000 Z
11
+ date: 2024-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -167,6 +167,8 @@ files:
167
167
  - ecoportal-api-v2.gemspec
168
168
  - lib/ecoportal/api-v2.rb
169
169
  - lib/ecoportal/api/common.v2.rb
170
+ - lib/ecoportal/api/common/concerns.rb
171
+ - lib/ecoportal/api/common/concerns/benchmarkable.rb
170
172
  - lib/ecoportal/api/common/content.rb
171
173
  - lib/ecoportal/api/common/content/array_model.rb
172
174
  - lib/ecoportal/api/common/content/class_helpers.rb