ecoportal-api-v2 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +55 -0
  5. data/.travis.yml +5 -0
  6. data/.yardopts +10 -0
  7. data/CHANGELOG.md +171 -0
  8. data/Gemfile +6 -0
  9. data/LICENSE +21 -0
  10. data/README.md +22 -0
  11. data/Rakefile +27 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/ecoportal-api-v2.gemspec +34 -0
  15. data/lib/ecoportal/api-v2.rb +10 -0
  16. data/lib/ecoportal/api/common.rb +18 -0
  17. data/lib/ecoportal/api/common/content.rb +18 -0
  18. data/lib/ecoportal/api/common/content/array_model.rb +286 -0
  19. data/lib/ecoportal/api/common/content/class_helpers.rb +146 -0
  20. data/lib/ecoportal/api/common/content/client.rb +40 -0
  21. data/lib/ecoportal/api/common/content/collection_model.rb +279 -0
  22. data/lib/ecoportal/api/common/content/doc_helpers.rb +67 -0
  23. data/lib/ecoportal/api/common/content/double_model.rb +356 -0
  24. data/lib/ecoportal/api/common/content/hash_diff_patch.rb +183 -0
  25. data/lib/ecoportal/api/common/content/string_digest.rb +27 -0
  26. data/lib/ecoportal/api/common/content/wrapped_response.rb +42 -0
  27. data/lib/ecoportal/api/v2.rb +82 -0
  28. data/lib/ecoportal/api/v2/page.rb +42 -0
  29. data/lib/ecoportal/api/v2/page/component.rb +133 -0
  30. data/lib/ecoportal/api/v2/page/component/action.rb +28 -0
  31. data/lib/ecoportal/api/v2/page/component/action_field.rb +54 -0
  32. data/lib/ecoportal/api/v2/page/component/chart_field.rb +54 -0
  33. data/lib/ecoportal/api/v2/page/component/chart_field/frequency.rb +29 -0
  34. data/lib/ecoportal/api/v2/page/component/chart_field/heatmap.rb +27 -0
  35. data/lib/ecoportal/api/v2/page/component/chart_field/indicator.rb +26 -0
  36. data/lib/ecoportal/api/v2/page/component/chart_field/multiseries.rb +31 -0
  37. data/lib/ecoportal/api/v2/page/component/chart_field/sankey.rb +27 -0
  38. data/lib/ecoportal/api/v2/page/component/chart_field/serie.rb +26 -0
  39. data/lib/ecoportal/api/v2/page/component/chart_field/series_config.rb +23 -0
  40. data/lib/ecoportal/api/v2/page/component/chart_fr_field.rb +32 -0
  41. data/lib/ecoportal/api/v2/page/component/checklist_field.rb +49 -0
  42. data/lib/ecoportal/api/v2/page/component/checklist_item.rb +25 -0
  43. data/lib/ecoportal/api/v2/page/component/date_field.rb +34 -0
  44. data/lib/ecoportal/api/v2/page/component/file.rb +16 -0
  45. data/lib/ecoportal/api/v2/page/component/files_field.rb +13 -0
  46. data/lib/ecoportal/api/v2/page/component/gauge_field.rb +36 -0
  47. data/lib/ecoportal/api/v2/page/component/gauge_stop.rb +88 -0
  48. data/lib/ecoportal/api/v2/page/component/geo_field.rb +13 -0
  49. data/lib/ecoportal/api/v2/page/component/image.rb +16 -0
  50. data/lib/ecoportal/api/v2/page/component/images_field.rb +23 -0
  51. data/lib/ecoportal/api/v2/page/component/law_field.rb +12 -0
  52. data/lib/ecoportal/api/v2/page/component/number_field.rb +12 -0
  53. data/lib/ecoportal/api/v2/page/component/people_field.rb +25 -0
  54. data/lib/ecoportal/api/v2/page/component/plain_text_field.rb +15 -0
  55. data/lib/ecoportal/api/v2/page/component/reference_field.rb +16 -0
  56. data/lib/ecoportal/api/v2/page/component/rich_text_field.rb +13 -0
  57. data/lib/ecoportal/api/v2/page/component/selection_field.rb +78 -0
  58. data/lib/ecoportal/api/v2/page/component/selection_option.rb +25 -0
  59. data/lib/ecoportal/api/v2/page/component/signature_field.rb +25 -0
  60. data/lib/ecoportal/api/v2/page/component/tag_field.rb +14 -0
  61. data/lib/ecoportal/api/v2/page/components.rb +42 -0
  62. data/lib/ecoportal/api/v2/page/section.rb +59 -0
  63. data/lib/ecoportal/api/v2/page/sections.rb +47 -0
  64. data/lib/ecoportal/api/v2/page/stage.rb +29 -0
  65. data/lib/ecoportal/api/v2/page/stages.rb +26 -0
  66. data/lib/ecoportal/api/v2/pages.rb +92 -0
  67. data/lib/ecoportal/api/v2/pages/page_stage.rb +16 -0
  68. data/lib/ecoportal/api/v2/pages/stages.rb +55 -0
  69. data/lib/ecoportal/api/v2/people.rb +31 -0
  70. data/lib/ecoportal/api/v2/registers.rb +89 -0
  71. data/lib/ecoportal/api/v2/registers/page_result.rb +21 -0
  72. data/lib/ecoportal/api/v2/registers/register.rb +37 -0
  73. data/lib/ecoportal/api/v2/registers/stage_result.rb +14 -0
  74. data/lib/ecoportal/api/v2/registers/stages_result.rb +13 -0
  75. data/lib/ecoportal/api/v2/registers/template.rb +12 -0
  76. data/lib/ecoportal/api/v2/version.rb +7 -0
  77. metadata +254 -0
@@ -0,0 +1,18 @@
1
+ module Ecoportal
2
+ module API
3
+ module Common
4
+ module Content
5
+ end
6
+ end
7
+ end
8
+ end
9
+
10
+ require 'ecoportal/api/common/content/class_helpers'
11
+ require 'ecoportal/api/common/content/string_digest'
12
+ require 'ecoportal/api/common/content/double_model'
13
+ require 'ecoportal/api/common/content/array_model'
14
+ require 'ecoportal/api/common/content/collection_model'
15
+ require 'ecoportal/api/common/content/doc_helpers'
16
+ require 'ecoportal/api/common/content/hash_diff_patch'
17
+ require 'ecoportal/api/common/content/client'
18
+ require 'ecoportal/api/common/content/wrapped_response'
@@ -0,0 +1,286 @@
1
+ module Ecoportal
2
+ module API
3
+ module Common
4
+ module Content
5
+ # Class to handle a plain Array embedded in a Hashed model.
6
+ # @note its purpose is to handle an Array of basic objects (i.e. `Date`, `String`, `Number`)
7
+ class ArrayModel < Content::DoubleModel
8
+ class TypeMismatchedComparison < Exception
9
+ def initialize (this: nil, that: msg = "Trying to compare objects with different behavior.")
10
+ if this
11
+ msg += " From object with 'order_matters: #{this.order_matters?}' and 'uniq: #{this.uniq?}'."
12
+ end
13
+ if that
14
+ msg += " To object where 'order_matters: #{that.order_matters?}' and 'uniq: #{that.uniq?}'."
15
+ end
16
+ super(msg)
17
+ end
18
+ end
19
+
20
+ include Enumerable
21
+
22
+ class << self
23
+ attr_accessor :order_matters, :uniq
24
+
25
+ # @param a [ArrayModel]
26
+ # @param b [ArrayModel]
27
+ # @return [Boolean] `true` if both elements have same behaviour
28
+ def same_type?(a, b)
29
+ raise "To use this comparison both objects should be `ArrayModel`" unless a.is_a?(ArrayModel) && b.is_a?(ArrayModel)
30
+ (a.order_matters? == b.order_matters?) && (a.uniq? == b.uniq?)
31
+ end
32
+
33
+ end
34
+
35
+ def initialize(doc = {}, parent: self, key: nil)
36
+ super(doc, parent: parent, key: key)
37
+ end
38
+
39
+ def order_matters?; self.class.order_matters; end
40
+ def uniq?; self.class.uniq; end
41
+
42
+ def length; count; end
43
+ def empty?; count == 0; end
44
+ def present?; count > 0; end
45
+
46
+ def each(&block)
47
+ return to_enum(:each) unless block
48
+ _items.each(&block)
49
+ end
50
+
51
+ # @return [Array] the array element represented by this object
52
+ def _items
53
+ replace_doc([]) unless doc.is_a?(Array)
54
+ doc.tap {|d| d.uniq! if uniq?}
55
+ end
56
+
57
+ # @see #_items
58
+ # @return [Array] a **copy** of the `Array` elements
59
+ def to_a
60
+ _items.slice(0..-1)
61
+ end
62
+
63
+ # @param value [Object, Array<Object>, ArrayModel] the value(s) of the new object
64
+ # @return [ArrayModel] a new object with the current class
65
+ def new_from(value)
66
+ self.class.new(into_a(value))
67
+ end
68
+
69
+ # @return [ArrayModel] a copy of the current object
70
+ def dup
71
+ new_from(to_a)
72
+ end
73
+
74
+ # @return [Integer] the position of the element in the `Array`
75
+ def index(value)
76
+ _items.index(value)
77
+ end
78
+
79
+ # Retrieves the element of a certain position
80
+ # @param pos [Integer] the position of the element
81
+ # @return [Date, String, Number]
82
+ def [](pos)
83
+ _items[pos]
84
+ end
85
+
86
+ # Sets the element of a certain position
87
+ # @param pos [Integer] the position of the element
88
+ # @param value [String, Date, Number] the element
89
+ # @return [Date, String, Number]
90
+ def []=(post, value)
91
+ _items[pos] = value
92
+ on_change
93
+ self[pos]
94
+ end
95
+
96
+ # Compares with an `Array` or another `ArrayModel`
97
+ # @param a [ArrayModel, Array]
98
+ def ==(a)
99
+ return true if self.equal?(a)
100
+ return false unless (a.class == self.class) || a.is_a?(Array)
101
+ case a
102
+ when Array
103
+ self == new_from(a)
104
+ when ArrayModel
105
+ return true if
106
+ raise TypeMismatchedComparison.new(this: self, that: a) unless self.class.same_type?(self, a)
107
+
108
+ if self.order_matters?
109
+ _items == a.to_a
110
+ else
111
+ (_items - a.to_a).empty? && (a.to_a - _items).empty?
112
+ end
113
+ end
114
+ end
115
+
116
+ # @return [Boolean] `true` if `value` is present, `false` otherwise
117
+ def include?(value)
118
+ _items.include?(value)
119
+ end
120
+
121
+ def include_any?(*value)
122
+ value.any? {|v| _items.include?(v)}
123
+ end
124
+
125
+ def include_all?(*value)
126
+ value.all? {|v| _items.include?(v)}
127
+ end
128
+
129
+ # Adds an element to the subjacent `Array`
130
+ # @note if the class variable `uniq` is `true`, it skips duplicates
131
+ def <<(value)
132
+ _items.concat(into_a(value)).tap do |doc|
133
+ doc.uniq! if uniq?
134
+ end
135
+ on_change
136
+ self
137
+ end
138
+
139
+ # @see #<<
140
+ def push!(value)
141
+ self << value
142
+ end
143
+
144
+ # @see #<<
145
+ # @note same as #push! but for multiple elements
146
+ def concat!(values)
147
+ self << values
148
+ end
149
+
150
+ # Resets the `Array` by keeping its reference and adds the value(s)
151
+ # @param value [Object, Array<Object>, ArrayModel] the value(s) to be added
152
+ # @param values [Array]
153
+ def <(values)
154
+ _items.clear
155
+ self << values
156
+ end
157
+
158
+ # Clears the `Array` keeping its reference
159
+ def clear!
160
+ _items.clear
161
+ on_change
162
+ self
163
+ end
164
+
165
+ # Concat to new
166
+ def +(value)
167
+ new_from(self.to_a + into_a(value))
168
+ end
169
+
170
+ # Join
171
+ # @param value [Object, Array<Object>, ArrayModel] the value(s) to be joined
172
+ # @return [ArrayModel] a new object instance with the intersection done
173
+ def |(value)
174
+ new = new_from(value) - self
175
+ new_from(to_a + new.to_a)
176
+ end
177
+
178
+ # Intersect
179
+ # @param value [Object, Array<Object>, ArrayModel] the value(s) to be deleted
180
+ # @return [ArrayModel] a new object instance with the intersection done
181
+ def &(value)
182
+ self.dup.tap do |out|
183
+ self.dup.tap do |delta|
184
+ delta.delete!(*into_a(value))
185
+ out.delete!(*into_a(delta))
186
+ end
187
+ end
188
+ end
189
+
190
+ # Subtract
191
+ # @param value [Object, Array<Object>, ArrayModel] the value(s) to be deleted
192
+ # @return [ArrayModel] a **copy** of the object with the elements subtracted
193
+ def -(value)
194
+ self.dup.tap do |copy|
195
+ copy.delete!(*into_a(value))
196
+ end
197
+ end
198
+
199
+ # Deletes `values` from the `Array`
200
+ def delete!(*values)
201
+ values.map do |v|
202
+ deletion!(v)
203
+ end.tap do |r|
204
+ on_change
205
+ end
206
+ end
207
+
208
+ # Swaps two values' positions
209
+ # @note this will work with first instances when **not** `uniq?`
210
+ # @param val1 [Object] the first value to swap
211
+ # @param val2 [Object] the second value to swap
212
+ # @return [Integer] the new of `value1`, `nil` if it wasn't moved
213
+ def swap(value1, value2)
214
+ index(value2).tap do |dest|
215
+ if dest && pos = index(value1)
216
+ _items[dest] = value1
217
+ _items[pos] = value2
218
+ end
219
+ end
220
+ end
221
+
222
+ def insert_one(value, pos: NOT_USED, before: NOT_USED, after: NOT_USED)
223
+ i = index(value)
224
+ return i if (i && uniq?)
225
+
226
+ pos = case
227
+ when used_param?(pos)
228
+ pos
229
+ when used_param?(before)
230
+ index(before)
231
+ when used_param?(after)
232
+ if i = index(after)
233
+ i + 1
234
+ end
235
+ else
236
+ length
237
+ end
238
+
239
+ pos.tap do |i|
240
+ unless !i
241
+ _items.insert(pos, value)
242
+ on_change
243
+ end
244
+ end
245
+ end
246
+
247
+ # TODO
248
+ def move(value, pos: NOT_USED, before: NOT_USED, after: NOT_USED)
249
+ if i = index(value)
250
+ unless i == pos
251
+
252
+ on_change
253
+ end
254
+ pos
255
+ end
256
+ end
257
+
258
+ protected
259
+
260
+ def on_change
261
+ # to be overriden by child classes
262
+ end
263
+
264
+ private
265
+
266
+ def into_a(value)
267
+ raise "Can't convert to 'Array' a 'Hash', as is a key_value pair Enumerable" if value.is_a?(Hash)
268
+ return value.to_a.slice(0..-1) if value.is_a?(Enumerable)
269
+ [].push(value).compact
270
+ end
271
+
272
+ def deletion!(value)
273
+ if !uniq?
274
+ if i = _items.index(value)
275
+ _items.slice!(i)
276
+ end
277
+ else
278
+ _items.delete(value)
279
+ end
280
+ end
281
+
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,146 @@
1
+ module Ecoportal
2
+ module API
3
+ module Common
4
+ module Content
5
+ module ClassHelpers
6
+ include Common::BaseClass
7
+ NOT_USED = "no_used!"
8
+
9
+ # Class resolver
10
+ # @note it caches the resolved `klass`es
11
+ # @raise [Exception] when could not resolve if `exception` is `true`
12
+ # @param klass [Class, String, Symbol] the class to resolve
13
+ # @param exception [Boolean] if it should raise exception when could not resolve
14
+ # @return [Class] the `Class` constant
15
+ def resolve_class(klass, exception: true)
16
+ @resolved ||= {}
17
+ @resolved[klass] ||=
18
+ case klass
19
+ when Class
20
+ klass
21
+ when String
22
+ begin
23
+ Kernel.const_get(klass)
24
+ rescue NameError => e
25
+ raise if exception
26
+ end
27
+ when Symbol
28
+ resolve_class(self.send(klass))
29
+ else
30
+ raise "Unknown class: #{klass}" if exception
31
+ end
32
+ end
33
+
34
+ # Helper to normalize `key` into a correct `ruby` **constant name**
35
+ # @param key [String, Symbol] to be normalized
36
+ # @return [String] a correct constant name
37
+ def to_constant(key)
38
+ str_name = key.to_s.strip.split(/[\-\_ ]/i).compact.map do |str|
39
+ str.slice(0).upcase + str.slice(1..-1).downcase
40
+ end.join("")
41
+ end
42
+
43
+ # Helper to create an instance variable `name`
44
+ # @param [String, Symbol] the name of the variable
45
+ # @reutrn [String] the name of the created instance variable
46
+ def instance_variable_name(name)
47
+ str = name.to_s
48
+ str = "@#{str}" unless str.start_with?("@")
49
+ str
50
+ end
51
+
52
+ # If the class for `name` exists, it returns it. Otherwise it generates it.
53
+ # @param name [String, Symbol] the name of the new class
54
+ # @param inherits [Class] the parent class to _inherit_ from
55
+ # @yield [child_class] configure the new class
56
+ # @yieldparam child_class [Class] the new class
57
+ # @return [Class] the new generated class
58
+ def new_class(name, inherits:)
59
+ name = name.to_sym.freeze
60
+ class_name = to_constant(name)
61
+ full_class_name = "#{inherits}::#{class_name}"
62
+
63
+ unless target_class = resolve_class(full_class_name, exception: false)
64
+ target_class = Class.new(inherits)
65
+ self.const_set class_name, target_class
66
+ end
67
+
68
+ target_class.tap do |klass|
69
+ yield(klass) if block_given?
70
+ end
71
+ end
72
+
73
+ # Helper to parse a value into a `Time` object.
74
+ # @raise [Exception] if `exception` is `true` and could not convert
75
+ # @param value [String, Date] the value to convert to `Time`
76
+ # @param exception [Boolean] if should raise `Exception` when could not convert
77
+ # @return
78
+ def to_time(value, exception: true)
79
+ case value
80
+ when NilClass
81
+ value
82
+ when String
83
+ begin
84
+ Time.parse(value)
85
+ rescue ArgumentArgument => e
86
+ raise if exception
87
+ nil
88
+ end
89
+ when Date
90
+ Time.parse(value.to_s)
91
+ when Time
92
+ value
93
+ else
94
+ to_time(value.to_s) if value.respond_to?(:to_s)
95
+ end
96
+ end
97
+
98
+ # Helper to determine if a paramter has been used
99
+ # @note to effectivelly use this helper, you should initialize your target
100
+ # paramters with the constant `NOT_USED`
101
+ # @param val [] the value of the paramter
102
+ # @return [Boolean] `true` if value other than `NOT_USED`, `false` otherwise
103
+ def used_param?(val)
104
+ val != NOT_USED
105
+ end
106
+
107
+ # Keeps track on class instance variables that should be inherited by child classes.
108
+ # @note
109
+ # - subclasses will inherit the value as is at that moment
110
+ # - any change afterwards will be only on the specific class (in line with class instance variables)
111
+ # - adapted from https://stackoverflow.com/a/10729812/4352306
112
+ # TODO: this separates the logic of the method to the instance var. Think if would be possible to join them somehow.
113
+ def inheritable_class_vars(*vars)
114
+ @inheritable_class_vars ||= [:inheritable_class_vars]
115
+ @inheritable_class_vars += vars
116
+ end
117
+
118
+ # Builds the attr_reader and attr_writer of `attrs` and registers the associated instance variable as inheritable.
119
+ def inheritable_attrs(*attrs)
120
+ attrs.each do |attr|
121
+ class_eval %(
122
+ class << self; attr_accessor :#{attr} end
123
+ )
124
+ end
125
+ inheritable_class_vars(*attrs)
126
+ end
127
+
128
+ # This callback method is called whenever a subclass of the current class is created.
129
+ # @note
130
+ # - values of the instance variables are copied as they are (no dups or clones)
131
+ # - the above means: avoid methods that change the state of the mutable object on it
132
+ # - mutating methods would reflect the changes on other classes as well
133
+ # - therefore, `freeze` will be called on the values that are inherited.
134
+ def inherited(subclass)
135
+ inheritable_class_vars.each do |var|
136
+ instance_var = instance_variable_name(var)
137
+ value = instance_variable_get(instance_var)
138
+ subclass.instance_variable_set(instance_var, value.freeze)
139
+ end
140
+ end
141
+
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end