ecoportal-api-v2 1.1.6 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -1
- data/lib/ecoportal/api/common/concerns/benchmarkable.rb +160 -0
- data/lib/ecoportal/api/common/concerns.rb +10 -0
- data/lib/ecoportal/api/common/content/array_model.rb +1 -1
- data/lib/ecoportal/api/common/content/collection_model.rb +44 -32
- data/lib/ecoportal/api/common/content/double_model.rb +26 -6
- data/lib/ecoportal/api/common.v2.rb +1 -0
- data/lib/ecoportal/api/v2_version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65194be30f026aa431874543d6905b5704ea081c6a3c9ef0bf9741142c4c4253
|
4
|
+
data.tar.gz: 398eaeb83ea38386670715c0fa83144004abdbd8c7edfd8581ecb7b157c8d205
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
@@ -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:
|
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
|
36
|
-
|
37
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
84
|
-
|
85
|
-
|
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.
|
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.
|
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:
|
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:
|
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.
|
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:
|
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
|
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.
|
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-
|
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
|