azeroth 0.6.2 → 0.6.3

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: 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