azeroth 0.6.2 → 0.6.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75366fe54c6684c1e58e55ced58c72570a211ba6e463c46d06504bc8b803a667
4
- data.tar.gz: 45548e777bf55688478af05716b200421c7629b6542222a4abab559bb24dd966
3
+ metadata.gz: ff267cb06b99dea008313ebf30569e33565cbbe210602b3008bd859016c28755
4
+ data.tar.gz: 494a34050515c0dcd7e05ecdce0151e26f089e0ae39d3c1652a38d210ec1f99d
5
5
  SHA512:
6
- metadata.gz: 19602b6e52d2f20ef53c1c8f555bf9d7b55de7cd898fe201b91fdd6afd5714ad9c9ec1ffaa4136219180e6631f94440bfaea3bc449ea9031e79691f281caae49
7
- data.tar.gz: 9f02bc1df351a23f01bd964ffda1d51169dd84e25b4835847b4a709e6efab9ce08612407de771371d90280d6c029a0a2f49613d5dad2d1be40ced130f1d649f3
6
+ metadata.gz: 1e89465dc060b9ab4a0b9a6c95e6b2cb054ab08efd32ebf312bbe94f751f5d505f20230632e8435d0de57898f9bbd9875215fd23c59f51a1de6516a2bfb0e273
7
+ data.tar.gz: fe6c480bc37d5e03ea61eb22df4c66154537fca6917d671e19fac1ce7bdf5de5051dfd736de0c7529ffa240512f615bd5a1c1a2e5ed285a3326956bd59758dd4
data/README.md CHANGED
@@ -11,4 +11,4 @@ Azeroth
11
11
 
12
12
  Yard Documentation
13
13
  -------------------
14
- [https://www.rubydoc.info/gems/azeroth/0.6.2](https://www.rubydoc.info/gems/azeroth/0.6.2)
14
+ [https://www.rubydoc.info/gems/azeroth/0.6.3](https://www.rubydoc.info/gems/azeroth/0.6.3)
data/config/yardstick.yml CHANGED
@@ -25,6 +25,7 @@ rules:
25
25
  exclude:
26
26
  - Azeroth::Decorator#initialize
27
27
  - Azeroth::Decorator::HashBuilder#initialize
28
+ - Azeroth::Decorator::KeyValueExtractor#initialize
28
29
  - Azeroth::DummyDecorator#initialize
29
30
  - Azeroth::Exception::InvalidOptions#initialize
30
31
  - Azeroth::Model#initialize
@@ -40,8 +40,9 @@ module Azeroth
40
40
  # # 'pokemon' => 'Arcanine'
41
41
  # # }
42
42
  class Decorator
43
- autoload :HashBuilder, 'azeroth/decorator/hash_builder'
44
- autoload :Options, 'azeroth/decorator/options'
43
+ autoload :HashBuilder, 'azeroth/decorator/hash_builder'
44
+ autoload :KeyValueExtractor, 'azeroth/decorator/key_value_extractor'
45
+ autoload :Options, 'azeroth/decorator/options'
45
46
 
46
47
  class << self
47
48
  # @api private
@@ -76,10 +77,11 @@ module Azeroth
76
77
  # Expose attributes on json decorated
77
78
  #
78
79
  # @param attribute [Symbol,String] attribute to be exposed
79
- # @param options [Hash] exposing options
80
- # @option options as [Symbol,String] custom key
80
+ # @param options_hash [Hash] exposing options
81
+ # @option options_hash as [Symbol,String] custom key
81
82
  # to expose
82
- # @option options if [Symbol,Proc] method/block to be called
83
+ # @option options_hash if [Symbol,Proc] method/block
84
+ # to be called
83
85
  # checking if an attribute should or should not
84
86
  # be exposed
85
87
  #
@@ -24,10 +24,10 @@ module Azeroth
24
24
  #
25
25
  # @return [Hash]
26
26
  def as_json
27
- attributes_map.each do |method, options|
28
- add_attribute(method, options)
27
+ attributes_map.inject({}) do |hash, (method, options)|
28
+ new_hash = KeyValueExtractor.new(decorator, method, options).as_json
29
+ hash.merge!(new_hash)
29
30
  end
30
- hash
31
31
  end
32
32
 
33
33
  private
@@ -41,57 +41,6 @@ module Azeroth
41
41
  #
42
42
  # @return [Decorator]
43
43
 
44
- # Adds an attribute to the exposed hash
45
- #
46
- # @param method [Symbol,String] method to be called
47
- # from the decorator
48
- # @param options [Hash] exposing options
49
- # @option options as [Symbol,String] custom key
50
- # to expose
51
- # @option options if [Symbol,Proc] method/block to be called
52
- # checking if an attribute should or should not
53
- # be exposed
54
- #
55
- # @return [Object] result of method call from decorator
56
- def add_attribute(method, options)
57
- return unless add_attribute?(options)
58
-
59
- key = options.as || method
60
-
61
- hash[key.to_s] = json_for(method)
62
- end
63
-
64
- def json_for(method)
65
- value = decorator.public_send(method)
66
-
67
- decorator_class_for(value).new(value).as_json
68
- end
69
-
70
- def decorator_class_for(value)
71
- return value.class::Decorator unless value.is_a?(Enumerable)
72
-
73
- decorator_class_for(value.first)
74
- rescue NameError
75
- Azeroth::DummyDecorator
76
- end
77
-
78
- # Check if an attribute should be added to the hash
79
- #
80
- # @param options [Hash] exposing options
81
- # @option options if [Symbol,Proc] method/block to be called
82
- # checking if an attribute should or should not
83
- # be exposed
84
- #
85
- # @return [Object] result of method call from decorator
86
- def add_attribute?(options)
87
- conditional = options.if
88
- return true unless conditional.present?
89
-
90
- block = proc(&conditional)
91
-
92
- block.call(decorator)
93
- end
94
-
95
44
  # attributes to be built to hash
96
45
  #
97
46
  # The keys are the methods to be called and
@@ -101,13 +50,6 @@ module Azeroth
101
50
  def attributes_map
102
51
  decorator.class.attributes_map
103
52
  end
104
-
105
- # Hash being built
106
- #
107
- # @return [Hash]
108
- def hash
109
- @hash ||= {}
110
- end
111
53
  end
112
54
  end
113
55
  end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Azeroth
4
+ class Decorator
5
+ # @api private
6
+ #
7
+ # Class responsible to extract the value for an attribute
8
+ #
9
+ # The value is extracted by sending a method
10
+ # call to the decorator
11
+ #
12
+ # A decorator is infered for the value / attribute
13
+ # or used from the options given
14
+ class KeyValueExtractor
15
+ # @param decorator [Decorator] decorator object
16
+ # @param attribute [Symbol] attribute to be used on output hash
17
+ # @param options [Decorator::Options] decoration options
18
+ # @option options if [Proc,Symbol] conditional to be
19
+ # checked when exposing field
20
+ # (see {Decorator::Options#if})
21
+ def initialize(decorator, attribute, options)
22
+ @decorator = decorator
23
+ @attribute = attribute
24
+ @options = options
25
+ end
26
+
27
+ # Return hash for attribute
28
+ #
29
+ # @return [Hash] hash in the format
30
+ # +{ attribute => decorated_value }+
31
+ def as_json
32
+ return {} unless add_attribute?
33
+
34
+ key = options.as || attribute
35
+
36
+ {
37
+ key.to_s => json_value
38
+ }
39
+ end
40
+
41
+ # private
42
+
43
+ attr_reader :decorator, :attribute, :options
44
+ # @method decorator
45
+ # @api private
46
+ # @private
47
+ #
48
+ # decorator with object to be decorated
49
+ #
50
+ # @return [Decorator]
51
+
52
+ # @method attribute
53
+ # @api private
54
+ # @private
55
+ #
56
+ # attribute to be exposed
57
+ #
58
+ # @return [Symbol]
59
+
60
+ # @method options
61
+ # @api private
62
+ # @private
63
+ #
64
+ # exposing options
65
+ #
66
+ # @return [Decorator::Options]
67
+
68
+ # @private
69
+ #
70
+ # returns the value ready to be transformed into json
71
+ #
72
+ # @return [Object]
73
+ def json_value
74
+ decorator_class(value).new(value).as_json
75
+ end
76
+
77
+ # @private
78
+ # Retruns the value extracted from decorator
79
+ #
80
+ # @return [Object]
81
+ def value
82
+ @value ||= decorator.public_send(attribute)
83
+ end
84
+
85
+ # @private
86
+ #
87
+ # Finds the correct decorator class for a value
88
+ #
89
+ # @return [Class<Decorator>]
90
+ def decorator_class(object)
91
+ decorator_from_options || decorator_class_for(object)
92
+ end
93
+
94
+ # @private
95
+ #
96
+ # returns decorator defined in options
97
+ #
98
+ # @return [Class<Decorator>, NilClass]
99
+ def decorator_from_options
100
+ return options.decorator if options.decorator
101
+
102
+ Azeroth::DummyDecorator unless options.any_decorator?
103
+ end
104
+
105
+ # @private
106
+ #
107
+ # Finds the correct decorator class for a value
108
+ #
109
+ # @return [Class<Decorator>]
110
+ def decorator_class_for(object)
111
+ return object.class::Decorator unless object.is_a?(Enumerable)
112
+
113
+ decorator_class_for(object.first)
114
+ rescue NameError
115
+ Azeroth::DummyDecorator
116
+ end
117
+
118
+ # @private
119
+ #
120
+ # Check if an attribute should be added to the hash
121
+ #
122
+ # @return [Object] result of method call from decorator
123
+ def add_attribute?
124
+ conditional = options.if
125
+ return true unless conditional.present?
126
+
127
+ block = proc(&conditional)
128
+
129
+ block.call(decorator)
130
+ end
131
+ end
132
+ end
133
+ end
@@ -12,14 +12,14 @@ module Azeroth
12
12
  class Options < Sinclair::Options
13
13
  DEFAULT_OPTIONS = {
14
14
  as: nil,
15
- if: nil
15
+ if: nil,
16
+ decorator: nil
16
17
  }.freeze
17
18
 
18
19
  with_options DEFAULT_OPTIONS
19
20
 
20
21
  # @method as
21
22
  # @api private
22
- # @public
23
23
  #
24
24
  # key to use when exposing the field
25
25
  #
@@ -29,17 +29,42 @@ module Azeroth
29
29
 
30
30
  # @method if
31
31
  # @api private
32
- # @public
33
32
  #
34
33
  # conditional to be checked when exposing field
35
34
  #
36
35
  # when conditional returns false, the
37
36
  # field will not be exposed
38
37
  #
39
- # when if is a {Proc} the proc will be used,
40
- # when it is a {Symbol} or {String} this will be
38
+ # when if is a Proc the proc will be used,
39
+ # when it is a Symbol or String this will be
41
40
  # the name of the method called in the decorator
42
41
  # to evaluate the condition
42
+ #
43
+ # @return [Proc,Symbol]
44
+
45
+ # @method decorator
46
+ # @api private
47
+ #
48
+ # Decorator class to decorate the value
49
+ #
50
+ # when false {DummyDecorator} will be used
51
+ #
52
+ # when nil, a decorator will be infered from
53
+ # value::Decorator
54
+ #
55
+ # @return [NilClass,Decorator]
56
+
57
+ # @api private
58
+ #
59
+ # Returns true when attribute must can be decorated
60
+ #
61
+ # when false, decoration happens through
62
+ # #as_json call on value
63
+ #
64
+ # @return [TrueClass,FalseClass]
65
+ def any_decorator?
66
+ decorator != false
67
+ end
43
68
  end
44
69
  end
45
70
  end
@@ -10,7 +10,6 @@ module Azeroth
10
10
  class DummyDecorator
11
11
  delegate :as_json, to: :@object
12
12
  # @method as_json
13
- # @public
14
13
  # @api public
15
14
  #
16
15
  # Returns object.as_json
@@ -27,7 +27,6 @@ module Azeroth
27
27
 
28
28
  # @method only
29
29
  # @api private
30
- # @public
31
30
  #
32
31
  # filter of only actions to be built
33
32
  #
@@ -35,7 +34,6 @@ module Azeroth
35
34
 
36
35
  # @method except
37
36
  # @api private
38
- # @public
39
37
  #
40
38
  # actions to be ignored
41
39
  #
@@ -43,7 +41,6 @@ module Azeroth
43
41
 
44
42
  # @method decorator
45
43
  # @api private
46
- # @public
47
44
  #
48
45
  # decorator class to be used
49
46
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Azeroth
4
- VERSION = '0.6.2'
4
+ VERSION = '0.6.3'
5
5
  end
@@ -3,7 +3,7 @@
3
3
  class Factory
4
4
  class DecoratorWithProduct < Azeroth::Decorator
5
5
  expose :name
6
- expose :main_product
6
+ expose :main_product, decorator: Product::DecoratorWithFactory
7
7
  expose :products
8
8
  end
9
9
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class IdDecorator < Azeroth::Decorator
4
+ expose :id
5
+ end
@@ -0,0 +1,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Azeroth::Decorator::KeyValueExtractor do
6
+ subject(:extractor) do
7
+ described_class.new(decorator, attribute, options)
8
+ end
9
+
10
+ let(:decorator_class) { Class.new(Azeroth::Decorator) }
11
+ let(:decorator) { decorator_class.new(object) }
12
+ let(:object) { create(:document) }
13
+ let(:attribute) { :name }
14
+ let(:options_hash) { {} }
15
+ let(:options) do
16
+ Azeroth::Decorator::Options.new(options_hash)
17
+ end
18
+
19
+ describe '#as_json' do
20
+ context 'with default options' do
21
+ it 'returns attribute and value as hash' do
22
+ expect(extractor.as_json)
23
+ .to eq({ 'name' => object.name })
24
+ end
25
+ end
26
+
27
+ context 'when value is a collection' do
28
+ let(:options_hash) { { decorator: false } }
29
+ let(:object) { create(:factory) }
30
+ let(:attribute) { :products }
31
+
32
+ let!(:products) do
33
+ create_list(:product, 3, factory: object)
34
+ end
35
+
36
+ let(:expected) do
37
+ {
38
+ 'products' => products.as_json
39
+ }
40
+ end
41
+
42
+ it 'returns value as array' do
43
+ expect(extractor.as_json)
44
+ .to eq(expected)
45
+ end
46
+
47
+ context 'with custom decorator option' do
48
+ let(:options_hash) { { decorator: IdDecorator } }
49
+ let(:expected) do
50
+ {
51
+ 'products' => [
52
+ { 'id' => products[0].id },
53
+ { 'id' => products[1].id },
54
+ { 'id' => products[2].id }
55
+ ]
56
+ }
57
+ end
58
+
59
+ it 'returns value as array' do
60
+ expect(extractor.as_json)
61
+ .to eq(expected)
62
+ end
63
+ end
64
+ end
65
+
66
+ context "with 'as' option" do
67
+ let(:options_hash) { { as: :the_name } }
68
+
69
+ it 'uses new given key' do
70
+ expect(extractor.as_json)
71
+ .to eq({ 'the_name' => object.name })
72
+ end
73
+ end
74
+
75
+ context "with 'if' option" do
76
+ context 'with symbol for method returns true' do
77
+ let(:options_hash) { { if: :magic? } }
78
+ let(:decorator_class) { Document::Decorator }
79
+ let(:object) do
80
+ create(:document, reference: 'X-MAGIC-1')
81
+ end
82
+
83
+ it 'returns attribute and value as hash' do
84
+ expect(extractor.as_json)
85
+ .to eq({ 'name' => object.name })
86
+ end
87
+ end
88
+
89
+ context 'with symbol for method returns false' do
90
+ let(:options_hash) { { if: :magic? } }
91
+ let(:decorator_class) { Document::Decorator }
92
+
93
+ it do
94
+ expect(extractor.as_json)
95
+ .to be_a(Hash)
96
+ end
97
+
98
+ it do
99
+ expect(extractor.as_json)
100
+ .to be_empty
101
+ end
102
+ end
103
+
104
+ context 'with Proc resolving to true' do
105
+ let(:options_hash) do
106
+ {
107
+ if: proc do |decorator|
108
+ decorator.name == object.name
109
+ end
110
+ }
111
+ end
112
+
113
+ it do
114
+ expect(extractor.as_json)
115
+ .to eq({ 'name' => object.name })
116
+ end
117
+ end
118
+
119
+ context 'with Proc resolving to false' do
120
+ let(:options_hash) do
121
+ {
122
+ if: proc do |decorator|
123
+ decorator.name != object.name
124
+ end
125
+ }
126
+ end
127
+
128
+ it do
129
+ expect(extractor.as_json)
130
+ .to be_a(Hash)
131
+ end
132
+
133
+ it do
134
+ expect(extractor.as_json)
135
+ .to be_empty
136
+ end
137
+ end
138
+ end
139
+
140
+ context 'when value is an object that can be decorated' do
141
+ context 'when it has decorator' do
142
+ let(:object) { create(:factory) }
143
+ let!(:product) { create(:product, factory: object) }
144
+ let(:attribute) { :main_product }
145
+ let(:expected) do
146
+ {
147
+ 'main_product' => {
148
+ 'name' => product.name
149
+ }
150
+ }
151
+ end
152
+
153
+ it 'exposes value with default decorator' do
154
+ expect(extractor.as_json)
155
+ .to eq(expected)
156
+ end
157
+ end
158
+
159
+ context 'when it has decorator and false decorator is given' do
160
+ let(:object) { create(:factory) }
161
+ let!(:product) { create(:product, factory: object) }
162
+ let(:attribute) { :main_product }
163
+ let(:options_hash) { { decorator: false } }
164
+
165
+ let(:expected) do
166
+ {
167
+ 'main_product' => product.as_json
168
+ }
169
+ end
170
+
171
+ it 'expose decorated value' do
172
+ expect(extractor.as_json)
173
+ .to eq(expected)
174
+ end
175
+ end
176
+
177
+ context 'when it has decorator and cusom decorator is given' do
178
+ let(:object) { create(:factory) }
179
+ let!(:product) { create(:product, factory: object) }
180
+ let(:attribute) { :main_product }
181
+ let(:custom_decorator) { IdDecorator }
182
+ let(:options_hash) { { decorator: custom_decorator } }
183
+
184
+ let(:expected) do
185
+ {
186
+ 'main_product' => {
187
+ 'id' => product.id
188
+ }
189
+ }
190
+ end
191
+
192
+ it 'expose decorated value' do
193
+ expect(extractor.as_json)
194
+ .to eq(expected)
195
+ end
196
+ end
197
+
198
+ context 'when it has no decorator' do
199
+ let(:object) { create(:product) }
200
+ let(:factory) { object.factory }
201
+ let(:attribute) { :factory }
202
+
203
+ it 'exposes value as json' do
204
+ expect(extractor.as_json)
205
+ .to eq({ 'factory' => factory.as_json })
206
+ end
207
+ end
208
+
209
+ context 'when it has no decorator and cusom decorator is given' do
210
+ let(:object) { create(:product) }
211
+ let(:factory) { object.factory }
212
+ let(:attribute) { :factory }
213
+ let(:custom_decorator) { IdDecorator }
214
+ let(:options_hash) { { decorator: custom_decorator } }
215
+
216
+ let(:expected) do
217
+ {
218
+ 'factory' => {
219
+ 'id' => factory.id
220
+ }
221
+ }
222
+ end
223
+
224
+ it 'expose decorated value' do
225
+ expect(extractor.as_json)
226
+ .to eq(expected)
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
@@ -224,7 +224,7 @@ describe Azeroth::Decorator do
224
224
  end
225
225
  end
226
226
 
227
- context 'when relation object has decorator' do
227
+ context 'when relation object has decorator and defined decorator' do
228
228
  subject(:decorator) do
229
229
  Factory::DecoratorWithProduct.new(factory)
230
230
  end
@@ -240,7 +240,8 @@ describe Azeroth::Decorator do
240
240
  {
241
241
  name: factory.name,
242
242
  main_product: {
243
- name: main_product.name
243
+ name: main_product.name,
244
+ factory: factory.as_json
244
245
  },
245
246
  products: [{
246
247
  name: main_product.name
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: azeroth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Darthjee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-03 00:00:00.000000000 Z
11
+ date: 2020-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -416,6 +416,7 @@ files:
416
416
  - lib/azeroth.rb
417
417
  - lib/azeroth/decorator.rb
418
418
  - lib/azeroth/decorator/hash_builder.rb
419
+ - lib/azeroth/decorator/key_value_extractor.rb
419
420
  - lib/azeroth/decorator/options.rb
420
421
  - lib/azeroth/dummy_decorator.rb
421
422
  - lib/azeroth/model.rb
@@ -464,6 +465,7 @@ files:
464
465
  - spec/dummy/app/models/dummy_model/decorator.rb
465
466
  - spec/dummy/app/models/factory.rb
466
467
  - spec/dummy/app/models/factory/decorator_with_product.rb
468
+ - spec/dummy/app/models/id_decorator.rb
467
469
  - spec/dummy/app/models/product.rb
468
470
  - spec/dummy/app/models/product/decorator.rb
469
471
  - spec/dummy/app/models/product/decorator_with_factory.rb
@@ -519,6 +521,7 @@ files:
519
521
  - spec/dummy/tmp/storage/.keep
520
522
  - spec/integration/yard/azeroth/decorator_spec.rb
521
523
  - spec/lib/azeroth/decorator/hash_builder_spec.rb
524
+ - spec/lib/azeroth/decorator/key_value_extractor_spec.rb
522
525
  - spec/lib/azeroth/decorator_spec.rb
523
526
  - spec/lib/azeroth/dummy_decorator_spec.rb
524
527
  - spec/lib/azeroth/model_spec.rb
@@ -601,6 +604,7 @@ test_files:
601
604
  - spec/dummy/app/models/dummy_model/decorator.rb
602
605
  - spec/dummy/app/models/factory.rb
603
606
  - spec/dummy/app/models/factory/decorator_with_product.rb
607
+ - spec/dummy/app/models/id_decorator.rb
604
608
  - spec/dummy/app/models/product.rb
605
609
  - spec/dummy/app/models/product/decorator.rb
606
610
  - spec/dummy/app/models/product/decorator_with_factory.rb
@@ -656,6 +660,7 @@ test_files:
656
660
  - spec/dummy/tmp/storage/.keep
657
661
  - spec/integration/yard/azeroth/decorator_spec.rb
658
662
  - spec/lib/azeroth/decorator/hash_builder_spec.rb
663
+ - spec/lib/azeroth/decorator/key_value_extractor_spec.rb
659
664
  - spec/lib/azeroth/decorator_spec.rb
660
665
  - spec/lib/azeroth/dummy_decorator_spec.rb
661
666
  - spec/lib/azeroth/model_spec.rb