ecoportal-api 0.8.5 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +20 -20
  3. data/.rspec +3 -3
  4. data/.rubocop.yml +55 -55
  5. data/.travis.yml +5 -5
  6. data/.yardopts +10 -10
  7. data/CHANGELOG.md +257 -236
  8. data/Gemfile +6 -6
  9. data/LICENSE +21 -21
  10. data/README.md +34 -34
  11. data/Rakefile +27 -27
  12. data/bin/console +14 -14
  13. data/bin/setup +8 -8
  14. data/ecoportal-api.gemspec +36 -36
  15. data/lib/ecoportal/api/common/base_class.rb +33 -29
  16. data/lib/ecoportal/api/common/base_model.rb +195 -177
  17. data/lib/ecoportal/api/common/batch_operation.rb +119 -119
  18. data/lib/ecoportal/api/common/batch_response.rb +34 -34
  19. data/lib/ecoportal/api/common/client.rb +198 -196
  20. data/lib/ecoportal/api/common/doc_helpers.rb +29 -29
  21. data/lib/ecoportal/api/common/elastic_apm_integration.rb +112 -112
  22. data/lib/ecoportal/api/common/hash_diff.rb +41 -41
  23. data/lib/ecoportal/api/common/logging.rb +12 -12
  24. data/lib/ecoportal/api/common/response.rb +31 -31
  25. data/lib/ecoportal/api/common/wrapped_response.rb +54 -54
  26. data/lib/ecoportal/api/common.rb +18 -18
  27. data/lib/ecoportal/api/errors/base.rb +8 -8
  28. data/lib/ecoportal/api/errors/time_out.rb +8 -8
  29. data/lib/ecoportal/api/errors.rb +9 -9
  30. data/lib/ecoportal/api/internal/account.rb +99 -100
  31. data/lib/ecoportal/api/internal/login_provider.rb +9 -9
  32. data/lib/ecoportal/api/internal/login_providers.rb +33 -33
  33. data/lib/ecoportal/api/internal/people.rb +14 -14
  34. data/lib/ecoportal/api/internal/permissions.rb +14 -13
  35. data/lib/ecoportal/api/internal/person.rb +101 -53
  36. data/lib/ecoportal/api/internal/person_details.rb +9 -9
  37. data/lib/ecoportal/api/internal/person_schema.rb +10 -10
  38. data/lib/ecoportal/api/internal/person_schemas.rb +11 -11
  39. data/lib/ecoportal/api/internal/policy_group.rb +9 -9
  40. data/lib/ecoportal/api/internal/policy_groups.rb +32 -32
  41. data/lib/ecoportal/api/internal/preferences.rb +31 -31
  42. data/lib/ecoportal/api/internal/schema_field.rb +8 -8
  43. data/lib/ecoportal/api/internal/schema_field_value.rb +8 -8
  44. data/lib/ecoportal/api/internal.rb +31 -31
  45. data/lib/ecoportal/api/logger.rb +62 -62
  46. data/lib/ecoportal/api/v1/people.rb +218 -218
  47. data/lib/ecoportal/api/v1/person.rb +138 -135
  48. data/lib/ecoportal/api/v1/person_details.rb +94 -82
  49. data/lib/ecoportal/api/v1/person_schema.rb +53 -53
  50. data/lib/ecoportal/api/v1/person_schemas.rb +48 -48
  51. data/lib/ecoportal/api/v1/schema_field.rb +34 -34
  52. data/lib/ecoportal/api/v1/schema_field_value.rb +65 -65
  53. data/lib/ecoportal/api/v1.rb +49 -49
  54. data/lib/ecoportal/api/version.rb +5 -5
  55. data/lib/ecoportal/api.rb +16 -16
  56. metadata +3 -3
data/Rakefile CHANGED
@@ -1,27 +1,27 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
- require "yard"
4
- require "redcarpet"
5
-
6
- desc "run the specs"
7
- RSpec::Core::RakeTask.new(:spec)
8
-
9
- desc "run rspec showing backtrace"
10
- RSpec::Core::RakeTask.new(:spec_trace) do |task|
11
- task.rspec_opts = ['--backtrace']
12
- end
13
-
14
- desc "run rspec stopping on first fail, and show backtrace"
15
- RSpec::Core::RakeTask.new(:spec_fast) do |task|
16
- task.rspec_opts = ['--fail-fast', '--backtrace']
17
- end
18
-
19
- # default task name is yard
20
- desc "Yard: generate all the documentation"
21
- YARD::Rake::YardocTask.new(:doc) do |t|
22
- #t.files = ['lib/**/*.rb']
23
- end
24
-
25
- task :default => [:spec]
26
- task :rspec_trace => :spec_trace
27
- task :rspec_fast => :spec_fast
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "yard"
4
+ require "redcarpet"
5
+
6
+ desc "run the specs"
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ desc "run rspec showing backtrace"
10
+ RSpec::Core::RakeTask.new(:spec_trace) do |task|
11
+ task.rspec_opts = ['--backtrace']
12
+ end
13
+
14
+ desc "run rspec stopping on first fail, and show backtrace"
15
+ RSpec::Core::RakeTask.new(:spec_fast) do |task|
16
+ task.rspec_opts = ['--fail-fast', '--backtrace']
17
+ end
18
+
19
+ # default task name is yard
20
+ desc "Yard: generate all the documentation"
21
+ YARD::Rake::YardocTask.new(:doc) do |t|
22
+ #t.files = ['lib/**/*.rb']
23
+ end
24
+
25
+ task :default => [:spec]
26
+ task :rspec_trace => :spec_trace
27
+ task :rspec_fast => :spec_fast
data/bin/console CHANGED
@@ -1,14 +1,14 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "ecoportal/api"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "pry"
14
- Pry.start(__FILE__)
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ecoportal/api"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "pry"
14
+ Pry.start(__FILE__)
data/bin/setup CHANGED
@@ -1,8 +1,8 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -1,36 +1,36 @@
1
-
2
- lib = File.expand_path("../lib", __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "ecoportal/api/version"
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "ecoportal-api"
8
- spec.version = Ecoportal::API::VERSION
9
- spec.authors = ["Tapio Saarinen"]
10
- spec.email = ["tapio@ecoportal.co.nz", "rien@ecoportal.co.nz", "oscar@ecoportal.co.nz", "bozydar@ecoportal.co.nz"]
11
-
12
- spec.summary = %q{A collection of helpers for interacting with the ecoPortal MS's various APIs}
13
- spec.homepage = "https://www.ecoportal.com"
14
- spec.licenses = %w[MIT]
15
-
16
- spec.required_ruby_version = '>= 2.4.4'
17
-
18
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
- f.match(%r{^(test|spec|features)/})
20
- end
21
- spec.bindir = "exe"
22
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
- spec.require_paths = ["lib"]
24
-
25
- spec.add_development_dependency "bundler", ">= 2.2.17", "< 2.3"
26
- spec.add_development_dependency "rspec", ">= 3.10.0", "< 3.11"
27
- spec.add_development_dependency "rake", ">= 13.0.3", "< 13.1"
28
- spec.add_development_dependency "yard", ">= 0.9.26", "< 0.10"
29
- spec.add_development_dependency "redcarpet", ">= 3.5.1", "< 3.6"
30
- spec.add_development_dependency "pry" , "~> 0.14"
31
-
32
- spec.add_dependency 'http', '~> 4.4.1', "< 5"
33
- spec.add_dependency 'dotenv', '>= 2.7.6', "< 2.8"
34
- spec.add_dependency 'elastic-apm', '>= 4.0.0', "< 4.1"
35
- spec.add_dependency 'hash-polyfill', '~> 0'
36
- end
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "ecoportal/api/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ecoportal-api"
8
+ spec.version = Ecoportal::API::VERSION
9
+ spec.authors = ["Tapio Saarinen"]
10
+ spec.email = ["tapio@ecoportal.co.nz", "rien@ecoportal.co.nz", "oscar@ecoportal.co.nz", "bozydar@ecoportal.co.nz"]
11
+
12
+ spec.summary = %q{A collection of helpers for interacting with the ecoPortal MS's various APIs}
13
+ spec.homepage = "https://www.ecoportal.com"
14
+ spec.licenses = %w[MIT]
15
+
16
+ spec.required_ruby_version = '>= 2.4.4'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency "bundler", ">= 2.2.17", "< 2.3"
26
+ spec.add_development_dependency "rspec", ">= 3.10.0", "< 3.11"
27
+ spec.add_development_dependency "rake", ">= 13.0.3", "< 13.1"
28
+ spec.add_development_dependency "yard", ">= 0.9.26", "< 0.10"
29
+ spec.add_development_dependency "redcarpet", ">= 3.5.1", "< 3.6"
30
+ spec.add_development_dependency "pry" , "~> 0.14"
31
+
32
+ spec.add_dependency 'http', '~> 4.4.1', "< 5"
33
+ spec.add_dependency 'dotenv', '>= 2.7.6', "< 2.8"
34
+ spec.add_dependency 'elastic-apm', '>= 4.0.0', "< 4.1"
35
+ spec.add_dependency 'hash-polyfill', '~> 0'
36
+ end
@@ -1,29 +1,33 @@
1
- module Ecoportal
2
- module API
3
- module Common
4
- module BaseClass
5
-
6
- def class_resolver(name, klass)
7
- define_singleton_method(name) { resolve_class(klass) }
8
- define_method(name) { self.class.resolve_class(klass) }
9
- end
10
-
11
- def resolve_class(klass)
12
- @resolved ||= {}
13
- @resolved[klass] ||=
14
- case klass
15
- when Class
16
- klass
17
- when String
18
- Kernel.const_get(klass)
19
- when Symbol
20
- resolve_class(self.send(klass))
21
- else
22
- raise "Unknown class: #{klass}"
23
- end
24
- end
25
-
26
- end
27
- end
28
- end
29
- end
1
+ module Ecoportal
2
+ module API
3
+ module Common
4
+ module BaseClass
5
+ def redef_without_warning(const, value)
6
+ self.class.send(:remove_const, const) if self.class.const_defined?(const)
7
+ self.class.const_set(const, value)
8
+ end
9
+
10
+ def class_resolver(name, klass)
11
+ define_singleton_method(name) { resolve_class(klass) }
12
+ define_method(name) { self.class.resolve_class(klass) }
13
+ end
14
+
15
+ def resolve_class(klass)
16
+ @resolved ||= {}
17
+ @resolved[klass] ||=
18
+ case klass
19
+ when Class
20
+ klass
21
+ when String
22
+ Kernel.const_get(klass)
23
+ when Symbol
24
+ resolve_class(self.send(klass))
25
+ else
26
+ raise "Unknown class: #{klass}"
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,177 +1,195 @@
1
- module Ecoportal
2
- module API
3
- module Common
4
- class BaseModel
5
- class UnlinkedModel < Exception
6
- def initialize (msg = "Something went wrong when linking the document.", from: nil, key: nil)
7
- msg += " From: #{from}." if from
8
- msg += " key: #{key}." if key
9
- super(msg)
10
- end
11
- end
12
-
13
- extend BaseClass
14
-
15
- class << self
16
- def passthrough(*methods, to: :doc)
17
- methods.each do |method|
18
- method = method.to_s
19
- define_method method do
20
- send(to)[method]
21
- end
22
- define_method "#{method}=" do |value|
23
- send(to)[method] = value
24
- end
25
- end
26
- end
27
-
28
- def embeds_one(method, key: method, nullable: false, klass:)
29
- method = method.to_s.freeze
30
- var = "@#{method}".freeze
31
- key = key.to_s.freeze
32
- define_method(method) do
33
- if instance_variable_defined?(var)
34
- value = instance_variable_get(var)
35
- return value unless nullable
36
- return value if (value && doc[key]) || (!value && !doc[key])
37
- remove_instance_variable(var)
38
- end
39
- doc[key] ||= {} unless nullable
40
- return instance_variable_set(var, nil) unless doc[key]
41
-
42
- self.class.resolve_class(klass).new(
43
- doc[key], parent: self, key: key
44
- ).tap {|obj| instance_variable_set(var, obj)}
45
- end
46
- end
47
-
48
- end
49
-
50
- attr_reader :_parent, :_key
51
-
52
- def initialize(doc = {}, parent: self, key: nil)
53
- @_parent = parent
54
- @_key = key
55
- if !_parent || !_key
56
- @doc = doc
57
- @original_doc = JSON.parse(@doc.to_json)
58
- @initial_doc = JSON.parse(@doc.to_json)
59
- end
60
- end
61
-
62
- def doc
63
- raise UnlinkedModel.new(from: "#{self.class}#doc", key: _key) unless linked?
64
- return @doc if is_root?
65
- _parent.doc.dig(*[_key].flatten)
66
- end
67
-
68
- def original_doc
69
- raise UnlinkedModel.new(from: "#{self.class}#original_doc", key: _key) unless linked?
70
- return @original_doc if is_root?
71
- _parent.original_doc&.dig(*[_key].flatten)
72
- end
73
-
74
- def initial_doc
75
- raise UnlinkedModel.new(from: "#{self.class}#initial_doc", key: _key) unless linked?
76
- return @initial_doc if is_root?
77
- _parent.initial_doc&.dig(*[_key].flatten)
78
- end
79
-
80
- def as_json
81
- doc
82
- end
83
-
84
- def to_json(*args)
85
- doc.to_json(*args)
86
- end
87
-
88
- def as_update(ref = :last, ignore: [])
89
- new_doc = as_json
90
- ref_doc = ref == :total ? initial_doc : original_doc
91
- Common::HashDiff.diff(new_doc, ref_doc, ignore: ignore)
92
- end
93
-
94
- def dirty?
95
- as_update != {}
96
- end
97
-
98
- # It consolidates all the changes carried by `doc` by setting it as `original_doc`.
99
- def consolidate!
100
- raise UnlinkedModel.new(from: "#{self.class}#consolidate!", key: _key) unless linked?
101
- new_doc = JSON.parse(doc.to_json)
102
- if is_root?
103
- @original_doc = new_doc
104
- else
105
- dig_set(_parent.original_doc, [_key].flatten, new_doc)
106
- end
107
- end
108
-
109
- # It removes all the changes carried by `doc` by restoring `original_doc` into `doc`.
110
- # @note
111
- # 1. When there are nullable properties, it may be required to apply `reset!` from the parent
112
- # i.e. `parent.reset!("child")` # when parent.child is `nil`
113
- # 2. In such a case, only immediate childs are allowed to be reset
114
- # @param key [String, Array<String>, nil] if given, it only resets the specified property
115
- def reset!(key = nil)
116
- raise "'key' should be a String. Given #{key}" unless !key || key.is_a?(String)
117
- raise UnlinkedModel.new(from: "#{self.class}#reset!", key: _key) unless linked?
118
-
119
- if key
120
- if self.respond_to?(key) && child = self.send(key) && child.is_a?(Ecoportal::API::Common::BaseModel)
121
- child.reset!
122
- else
123
- new_doc = original_doc && original_doc[key]
124
- dig_set(doc, [key], new_doc && JSON.parse(new_doc.to_json))
125
- # regenerate object if new_doc is null
126
- self.send(key) if !new_doc && self.respond_to?(key)
127
- end
128
- else
129
- new_doc = JSON.parse(original_doc.to_json)
130
- if is_root?
131
- @doc = new_doc
132
- else
133
- dig_set(_parent.doc, [_key].flatten, new_doc)
134
- end
135
- end
136
- end
137
-
138
- def print_pretty
139
- puts JSON.pretty_generate(as_json)
140
- self
141
- end
142
-
143
- protected
144
-
145
- def is_root?
146
- _parent == self && !!defined?(@doc)
147
- end
148
-
149
- def linked?
150
- is_root? || !!_parent.doc.dig(*[_key].flatten)
151
- end
152
-
153
- private
154
-
155
- def dig_set(obj, keys, value)
156
- if keys.length == 1
157
- obj[keys.first] = value
158
- else
159
- dig_set(obj[keys.first], keys.slice(1..-1), value)
160
- end
161
- end
162
-
163
- def set_uniq_array_keep_order(key, value)
164
- unless value.is_a?(Array)
165
- raise "#{key}= needs to be passed an Array, got #{value.class}"
166
- end
167
- ini_vals = (original_doc && original_doc[key]) || []
168
-
169
- value = value.uniq
170
- # preserve original order to avoid false updates
171
- doc[key] = ((ini_vals & value) + (value - ini_vals)).compact
172
- end
173
-
174
- end
175
- end
176
- end
177
- end
1
+ module Ecoportal
2
+ module API
3
+ module Common
4
+ class BaseModel
5
+ class UnlinkedModel < Exception
6
+ def initialize (msg = "Something went wrong when linking the document.", from: nil, key: nil)
7
+ msg += " From: #{from}." if from
8
+ msg += " key: #{key}." if key
9
+ super(msg)
10
+ end
11
+ end
12
+
13
+ extend BaseClass
14
+
15
+ class << self
16
+ def passthrough(*methods, to: :doc)
17
+ methods.each do |method|
18
+ method = method.to_s
19
+ define_method method do
20
+ send(to)[method]
21
+ end
22
+ define_method "#{method}=" do |value|
23
+ send(to)[method] = value
24
+ end
25
+ end
26
+ end
27
+
28
+ def embeds_one(method, key: method, nullable: false, klass:)
29
+ method = method.to_s.freeze
30
+ var = "@#{method}".freeze
31
+ key = key.to_s.freeze
32
+ define_method(method) do
33
+ if instance_variable_defined?(var)
34
+ value = instance_variable_get(var)
35
+ return value unless nullable
36
+ return value if (value && doc[key]) || (!value && !doc[key])
37
+ remove_instance_variable(var)
38
+ end
39
+ doc[key] ||= {} unless nullable
40
+ return instance_variable_set(var, nil) unless doc[key]
41
+
42
+ self.class.resolve_class(klass).new(
43
+ doc[key], parent: self, key: key
44
+ ).tap {|obj| instance_variable_set(var, obj)}
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ attr_reader :_parent, :_key
51
+
52
+ def initialize(doc = {}, parent: self, key: nil)
53
+ @_parent = parent
54
+ @_key = key
55
+ if !_parent || !_key
56
+ @doc = doc
57
+ @original_doc = JSON.parse(@doc.to_json)
58
+ @initial_doc = JSON.parse(@doc.to_json)
59
+ end
60
+ end
61
+
62
+ def doc
63
+ raise UnlinkedModel.new(from: "#{self.class}#doc", key: _key) unless linked?
64
+ return @doc if is_root?
65
+ _parent.doc.dig(*[_key].flatten)
66
+ end
67
+
68
+ def original_doc
69
+ raise UnlinkedModel.new(from: "#{self.class}#original_doc", key: _key) unless linked?
70
+ return @original_doc if is_root?
71
+ _parent.original_doc&.dig(*[_key].flatten)
72
+ end
73
+
74
+ def initial_doc
75
+ raise UnlinkedModel.new(from: "#{self.class}#initial_doc", key: _key) unless linked?
76
+ return @initial_doc if is_root?
77
+ _parent.initial_doc&.dig(*[_key].flatten)
78
+ end
79
+
80
+ # It replaces `doc` by `new_doc`
81
+ # @return [Hash] `doc` before change
82
+ def replace_doc!(new_doc)
83
+ raise UnlinkedModel.new(from: "#{self.class}#replace_doc", key: _key) unless linked?
84
+ @doc.tap do
85
+ @doc = new_doc
86
+ end
87
+ end
88
+
89
+ # It replaces `original_doc` by `new_doc`
90
+ # @return [Hash] `original_doc` before change
91
+ def replace_original_doc!(new_doc)
92
+ raise UnlinkedModel.new(from: "#{self.class}#replace_original_doc", key: _key) unless linked?
93
+ @original_doc.tap do
94
+ @original_doc = new_doc
95
+ end
96
+ end
97
+
98
+ def as_json
99
+ doc
100
+ end
101
+
102
+ def to_json(*args)
103
+ doc.to_json(*args)
104
+ end
105
+
106
+ def as_update(ref = :last, ignore: [])
107
+ new_doc = as_json
108
+ ref_doc = ref == :total ? initial_doc : original_doc
109
+ Common::HashDiff.diff(new_doc, ref_doc, ignore: ignore)
110
+ end
111
+
112
+ def dirty?
113
+ as_update != {}
114
+ end
115
+
116
+ # It consolidates all the changes carried by `doc` by setting it as `original_doc`.
117
+ def consolidate!
118
+ raise UnlinkedModel.new(from: "#{self.class}#consolidate!", key: _key) unless linked?
119
+ new_doc = JSON.parse(doc.to_json)
120
+ if is_root?
121
+ @original_doc = new_doc
122
+ else
123
+ dig_set(_parent.original_doc, [_key].flatten, new_doc)
124
+ end
125
+ end
126
+
127
+ # It removes all the changes carried by `doc` by restoring `original_doc` into `doc`.
128
+ # @note
129
+ # 1. When there are nullable properties, it may be required to apply `reset!` from the parent
130
+ # i.e. `parent.reset!("child")` # when parent.child is `nil`
131
+ # 2. In such a case, only immediate childs are allowed to be reset
132
+ # @param key [String, Array<String>, nil] if given, it only resets the specified property
133
+ def reset!(key = nil)
134
+ raise "'key' should be a String. Given #{key}" unless !key || key.is_a?(String)
135
+ raise UnlinkedModel.new(from: "#{self.class}#reset!", key: _key) unless linked?
136
+
137
+ if key
138
+ if self.respond_to?(key) && child = self.send(key) && child.is_a?(Ecoportal::API::Common::BaseModel)
139
+ child.reset!
140
+ else
141
+ new_doc = original_doc && original_doc[key]
142
+ dig_set(doc, [key], new_doc && JSON.parse(new_doc.to_json))
143
+ # regenerate object if new_doc is null
144
+ self.send(key) if !new_doc && self.respond_to?(key)
145
+ end
146
+ else
147
+ new_doc = JSON.parse(original_doc.to_json)
148
+ if is_root?
149
+ @doc = new_doc
150
+ else
151
+ dig_set(_parent.doc, [_key].flatten, new_doc)
152
+ end
153
+ end
154
+ end
155
+
156
+ def print_pretty
157
+ puts JSON.pretty_generate(as_json)
158
+ self
159
+ end
160
+
161
+ protected
162
+
163
+ def is_root?
164
+ _parent == self && !!defined?(@doc)
165
+ end
166
+
167
+ def linked?
168
+ is_root? || !!_parent.doc.dig(*[_key].flatten)
169
+ end
170
+
171
+ private
172
+
173
+ def dig_set(obj, keys, value)
174
+ if keys.length == 1
175
+ obj[keys.first] = value
176
+ else
177
+ dig_set(obj[keys.first], keys.slice(1..-1), value)
178
+ end
179
+ end
180
+
181
+ def set_uniq_array_keep_order(key, value)
182
+ unless value.is_a?(Array)
183
+ raise "#{key}= needs to be passed an Array, got #{value.class}"
184
+ end
185
+ ini_vals = (original_doc && original_doc[key]) || []
186
+
187
+ value = value.uniq
188
+ # preserve original order to avoid false updates
189
+ doc[key] = ((ini_vals & value) + (value - ini_vals)).compact
190
+ end
191
+
192
+ end
193
+ end
194
+ end
195
+ end