azeroth 0.1.0 → 0.2.0

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: afd378d756eaf524cab794de457c654237f287bee671be9e6409702528695247
4
- data.tar.gz: 6dabd7110587bb3df0dcf7239a179ea1ceaee017692262f2366a2ac8b73fe0fc
3
+ metadata.gz: 4969648cf9b578079bc4d7baca012aa82768fd37edc9279ed2be606ef11b78b4
4
+ data.tar.gz: e426759bcab101a0ef02b0b9cc68d56d2cabc43fdea9a30027974dbaebf7a4e5
5
5
  SHA512:
6
- metadata.gz: b434a935b247bce958b72abefc6dc82063df57c9ad331e3b087c246e95cebea9dc2a619964081c54c65fe5c2775b62f5fac1b58ed4122a3776850922b24096b7
7
- data.tar.gz: d12697602efd61c4932b8fddb3e6b83b273dc51c441b4f9c5a1cf07a5325933e6b3c0629f594b06921d06a8cd4f37998f75ccedbf39c3a4950f7d9353fc3bfaf
6
+ metadata.gz: 8848cd9189e14cb2ac4ce22b401b2d5c4d5a39af868a38c1746a9e05136018bbf42b5e071f4feff680b823ab70fc9f3320c60fb0b4c982dfee23a2621781bc0a
7
+ data.tar.gz: ed36e6cf227e30e6f37dbe8ca109ccfecda6f2747349a1c73e97cf7319af929785aeaa0ccb8ab08265d0524d58a39fbe6a3101f5e95cb1b39998879866f783d2
data/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  Azeroth
2
2
  ========
3
+ [![Build Status](https://circleci.com/gh/darthjee/azeroth.svg?style=shield)](https://circleci.com/gh/darthjee/azeroth)
3
4
  [![Code Climate](https://codeclimate.com/github/darthjee/azeroth/badges/gpa.svg)](https://codeclimate.com/github/darthjee/azeroth)
4
5
  [![Test Coverage](https://codeclimate.com/github/darthjee/azeroth/badges/coverage.svg)](https://codeclimate.com/github/darthjee/azeroth/coverage)
5
6
  [![Issue Count](https://codeclimate.com/github/darthjee/azeroth/badges/issue_count.svg)](https://codeclimate.com/github/darthjee/azeroth)
6
7
  [![Gem Version](https://badge.fury.io/rb/azeroth.svg)](https://badge.fury.io/rb/azeroth)
8
+ [![Inline docs](http://inch-ci.org/github/darthjee/azeroth.svg)](http://inch-ci.org/github/darthjee/azeroth)
7
9
 
8
10
  ![azeroth](https://raw.githubusercontent.com/darthjee/azeroth/master/azeroth.jpg)
9
11
 
10
12
  Yard Documentation
11
13
  -------------------
12
- https://www.rubydoc.info/gems/azeroth/0.1.0
14
+ [https://www.rubydoc.info/gems/azeroth/0.2.0](https://www.rubydoc.info/gems/azeroth/0.2.0)
@@ -23,6 +23,7 @@ rules:
23
23
  enabled: true
24
24
  exclude:
25
25
  - Azeroth::Decorator#initialize
26
+ - Azeroth::Decorator::HashBuilder#initialize
26
27
  - Azeroth::Model#initialize
27
28
  - Azeroth::RequestHandler#initialize
28
29
  - Azeroth::ResourceBuilder#initialize
@@ -1,13 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'sinclair'
4
+
3
5
  # @api public
4
6
  #
5
7
  # Lib for easily developing controllers
6
8
  #
9
+ # @example (see Resourceable)
10
+ #
7
11
  # @see Resourceable
8
-
9
- require 'sinclair'
10
-
11
12
  module Azeroth
12
13
  autoload :Decorator, 'azeroth/decorator'
13
14
  autoload :Model, 'azeroth/model'
@@ -40,20 +40,20 @@ module Azeroth
40
40
  # # 'pokemon' => 'Arcanine'
41
41
  # # }
42
42
  class Decorator
43
+ autoload :HashBuilder, 'azeroth/decorator/hash_builder'
44
+
43
45
  class << self
44
46
  # @api private
45
47
  #
46
48
  # All attributes exposed
47
49
  #
48
50
  # @return [Array<Symbol>]
49
- def attributes
50
- @attributes ||= []
51
+ def attributes_map
52
+ @attributes_map ||= {}
51
53
  end
52
54
 
53
55
  private
54
56
 
55
- # rubocop:disable Naming/UncommunicativeMethodParamName
56
-
57
57
  # @visibility public
58
58
  # @api public
59
59
  # @private
@@ -61,7 +61,12 @@ module Azeroth
61
61
  # Expose attributes on json decorated
62
62
  #
63
63
  # @param attribute [Symbol,String] attribute to be exposed
64
- # @param as [Symbol,String] name of the attribute on the json
64
+ # @param options [Hash] exposing options
65
+ # @option options as [Symbol,String] custom key
66
+ # to expose
67
+ # @option options if [Symbol,Proc] method/block to be called
68
+ # checking if an attribute should or should not
69
+ # be exposed
65
70
  #
66
71
  # @return [Array<Symbol>]
67
72
  #
@@ -82,14 +87,13 @@ module Azeroth
82
87
  # end
83
88
  # end
84
89
  # end
85
- def expose(attribute, as: attribute)
90
+ def expose(attribute, **options)
86
91
  builder = Sinclair.new(self)
87
- builder.add_method(as, "@object.#{attribute}")
92
+ builder.add_method(attribute, "@object.#{attribute}")
88
93
  builder.build
89
94
 
90
- attributes << as.to_sym
95
+ attributes_map[attribute] = options
91
96
  end
92
- # rubocop:enable Naming/UncommunicativeMethodParamName
93
97
  end
94
98
 
95
99
  # @api private
@@ -109,17 +113,13 @@ module Azeroth
109
113
  # When object is an iterator, decoration is applied to each
110
114
  # and an array is returned
111
115
  #
112
- # @param *args [Hash] options (to be implemented)
116
+ # @param args [Hash] options (to be implemented)
113
117
  #
114
118
  # @return [Hash]
115
119
  def as_json(*args)
116
120
  return array_as_json(*args) if enum?
117
121
 
118
- {}.tap do |hash|
119
- self.class.attributes.each do |method|
120
- hash[method.to_s] = public_send(method)
121
- end
122
- end
122
+ HashBuilder.new(self).as_json
123
123
  end
124
124
 
125
125
  private
@@ -153,5 +153,40 @@ module Azeroth
153
153
  self.class.new(item).as_json(*args)
154
154
  end
155
155
  end
156
+
157
+ # @api private
158
+ # @private
159
+ # Method called when method is missing
160
+ #
161
+ # This delegates method calls to the given object
162
+ #
163
+ # @param method_name [Symbol] name of the method
164
+ # called
165
+ # @param args [Array<Object>] arguments of the
166
+ # method called
167
+ #
168
+ # @return [Object]
169
+ def method_missing(method_name, *args)
170
+ if object.respond_to?(method_name)
171
+ object.public_send(method_name, *args)
172
+ else
173
+ super
174
+ end
175
+ end
176
+
177
+ # @api private
178
+ # @private
179
+ # Checks if it would respond to a method
180
+ #
181
+ # The decision is delegated to the object
182
+ #
183
+ # @param method_name [Symbol] name of the method checked
184
+ # @param include_private [TrueClass,FalseClass] flag
185
+ # indicating if private methods should be included
186
+ #
187
+ # @return [TrueClass,FalseClass]
188
+ def respond_to_missing?(method_name, include_private)
189
+ object.respond_to?(method_name, include_private)
190
+ end
156
191
  end
157
192
  end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinclair'
4
+
5
+ module Azeroth
6
+ class Decorator
7
+ # @api private
8
+ #
9
+ # Class responsible for building the hash on
10
+ # Decorator#as_json calls
11
+ class HashBuilder
12
+ # @param decorator [Decorator] decorator that
13
+ # will receive all method calls
14
+ def initialize(decorator)
15
+ @decorator = decorator
16
+ end
17
+
18
+ # Build hash as defined by decorator
19
+ #
20
+ # The built of the hash keys is defined
21
+ # by decorator attributes and method calls
22
+ # to the same decorator that will then delgate,
23
+ # when needed, to its object
24
+ #
25
+ # @return [Hash]
26
+ def as_json
27
+ attributes_map.each do |method, options|
28
+ add_attribute(method, options)
29
+ end
30
+ hash
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :decorator
36
+ # @method decorator
37
+ # @api private
38
+ # @private
39
+ #
40
+ # decorator with object to be decorated
41
+ #
42
+ # @return [Decorator]
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] = decorator.public_send(method)
62
+ end
63
+
64
+ # Check if an attribute should be added to the hash
65
+ #
66
+ # @param options [Hash] exposing options
67
+ # @option options if [Symbol,Proc] method/block to be called
68
+ # checking if an attribute should or should not
69
+ # be exposed
70
+ #
71
+ # @return [Object] result of method call from decorator
72
+ def add_attribute?(options)
73
+ conditional = options[:if]
74
+ return true unless conditional.present?
75
+
76
+ block = proc(&conditional)
77
+
78
+ block.call(decorator)
79
+ end
80
+
81
+ # attributes to be built to hash
82
+ #
83
+ # The keys are the methods to be called and
84
+ # the values are the exposing options
85
+ #
86
+ # @return [Hash] hash of pairs method,options
87
+ def attributes_map
88
+ decorator.class.attributes_map
89
+ end
90
+
91
+ # Hash being built
92
+ #
93
+ # @return [Hash]
94
+ def hash
95
+ @hash ||= {}
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Azeroth
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
@@ -3,5 +3,10 @@
3
3
  class Document
4
4
  class Decorator < Azeroth::Decorator
5
5
  expose :name
6
+ expose :reference, if: proc(&:magic?)
7
+
8
+ def magic?
9
+ reference&.match(/^X-MAGIC/)
10
+ end
6
11
  end
7
12
  end
@@ -5,4 +5,12 @@ class DummyModel
5
5
 
6
6
  attr_accessor :id, :first_name, :last_name, :age,
7
7
  :favorite_pokemon
8
+
9
+ validates_presence_of :first_name
10
+
11
+ private
12
+
13
+ def private_name
14
+ "Secret #{last_name}"
15
+ end
8
16
  end
@@ -5,9 +5,15 @@ class DummyModel
5
5
  expose :name
6
6
  expose :age
7
7
  expose :favorite_pokemon, as: :pokemon
8
+ expose :errors, if: :invalid?
8
9
 
9
10
  def name
10
- [object.first_name, object.last_name].join(' ')
11
+ [object.first_name, object.last_name].compact.join(' ')
12
+ end
13
+
14
+ def errors
15
+ object.valid?
16
+ object.errors.messages
11
17
  end
12
18
  end
13
19
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Azeroth::Decorator::HashBuilder do
6
+ subject(:builder) { described_class.new(decorator) }
7
+
8
+ let(:decorator_class) { DummyModel::Decorator }
9
+ let(:decorator) { decorator_class.new(model) }
10
+ let(:model) { build(:dummy_model) }
11
+
12
+ describe '#as_json' do
13
+ let(:expected_json) do
14
+ {
15
+ name: "#{model.first_name} #{model.last_name}",
16
+ age: model.age,
17
+ pokemon: model.favorite_pokemon
18
+ }.stringify_keys
19
+ end
20
+
21
+ it 'returns meta data defined json' do
22
+ expect(decorator.as_json).to eq(expected_json)
23
+ end
24
+
25
+ context 'when conditional attibute is exposed' do
26
+ let(:model) { build(:dummy_model, first_name: nil) }
27
+
28
+ let(:expected_json) do
29
+ {
30
+ name: model.last_name,
31
+ age: model.age,
32
+ pokemon: model.favorite_pokemon,
33
+ errors: {
34
+ first_name: ["can't be blank"]
35
+ }
36
+ }.stringify_keys
37
+ end
38
+
39
+ it 'include the conditional attributes' do
40
+ expect(decorator.as_json).to eq(expected_json)
41
+ end
42
+ end
43
+
44
+ context 'when decorator has an expose block conditional' do
45
+ let(:decorator_class) { Document::Decorator }
46
+ let(:model) { create(:document) }
47
+
48
+ let(:expected_json) do
49
+ {
50
+ name: model.name
51
+ }.stringify_keys
52
+ end
53
+
54
+ it 'returns meta data defined json' do
55
+ expect(decorator.as_json).to eq(expected_json)
56
+ end
57
+
58
+ context 'when conditional returns true' do
59
+ let(:model) { create(:document, reference: 'X-MAGIC-01') }
60
+
61
+ let(:expected_json) do
62
+ {
63
+ name: model.name,
64
+ reference: 'X-MAGIC-01'
65
+ }.stringify_keys
66
+ end
67
+
68
+ it 'returns meta data defined json' do
69
+ expect(decorator.as_json).to eq(expected_json)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'spec_helper'
4
+
3
5
  describe Azeroth::Decorator do
4
6
  subject(:decorator) { DummyModel::Decorator.new(object) }
5
7
 
@@ -19,6 +21,25 @@ describe Azeroth::Decorator do
19
21
  it 'returns meta data defined json' do
20
22
  expect(decorator.as_json).to eq(expected_json)
21
23
  end
24
+
25
+ context 'when conditional attibute is exposed' do
26
+ let(:model) { build(:dummy_model, first_name: nil) }
27
+
28
+ let(:expected_json) do
29
+ {
30
+ name: model.last_name,
31
+ age: model.age,
32
+ pokemon: model.favorite_pokemon,
33
+ errors: {
34
+ first_name: ["can't be blank"]
35
+ }
36
+ }.stringify_keys
37
+ end
38
+
39
+ it 'include the conditional attributes' do
40
+ expect(decorator.as_json).to eq(expected_json)
41
+ end
42
+ end
22
43
  end
23
44
 
24
45
  context 'when object is an array' do
@@ -92,4 +113,59 @@ describe Azeroth::Decorator do
92
113
  end
93
114
  end
94
115
  end
116
+
117
+ describe '#method_missing' do
118
+ subject(:decorator) { decorator_class.new(object) }
119
+
120
+ let(:decorator_class) { Class.new(described_class) }
121
+ let(:model) { build(:dummy_model) }
122
+
123
+ it 'delegates methods to object' do
124
+ expect(decorator.first_name).not_to be_nil
125
+ end
126
+
127
+ context 'when object does not respond to method' do
128
+ it do
129
+ expect { decorator.bad_method }
130
+ .to raise_error(NoMethodError)
131
+ end
132
+ end
133
+ end
134
+
135
+ describe '#respond_to_missing?' do
136
+ subject(:decorator) { decorator_class.new(object) }
137
+
138
+ let(:decorator_class) { Class.new(described_class) }
139
+ let(:model) { build(:dummy_model) }
140
+
141
+ context 'when object responds to it' do
142
+ it do
143
+ expect(decorator)
144
+ .to respond_to(:first_name)
145
+ end
146
+ end
147
+
148
+ context 'when method is private' do
149
+ it do
150
+ expect(decorator)
151
+ .not_to respond_to(:private_name)
152
+ end
153
+ end
154
+
155
+ context 'when method is private and passing include_private' do
156
+ # rubocop:disable RSpec/PredicateMatcher
157
+ it do
158
+ expect(decorator.respond_to?(:private_name, true))
159
+ .to be_truthy
160
+ end
161
+ # rubocop:enable RSpec/PredicateMatcher
162
+ end
163
+
164
+ context 'when object does not respond to it' do
165
+ it do
166
+ expect(decorator)
167
+ .not_to respond_to(:no_name)
168
+ end
169
+ end
170
+ end
95
171
  end
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.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Darthjee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-16 00:00:00.000000000 Z
11
+ date: 2019-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -401,6 +401,7 @@ files:
401
401
  - docker-compose.yml
402
402
  - lib/azeroth.rb
403
403
  - lib/azeroth/decorator.rb
404
+ - lib/azeroth/decorator/hash_builder.rb
404
405
  - lib/azeroth/model.rb
405
406
  - lib/azeroth/options.rb
406
407
  - lib/azeroth/request_handler.rb
@@ -491,6 +492,7 @@ files:
491
492
  - spec/dummy/tmp/.keep
492
493
  - spec/dummy/tmp/storage/.keep
493
494
  - spec/integration/yard/azeroth/decorator_spec.rb
495
+ - spec/lib/azeroth/decorator/hash_builder_spec.rb
494
496
  - spec/lib/azeroth/decorator_spec.rb
495
497
  - spec/lib/azeroth/model_spec.rb
496
498
  - spec/lib/azeroth/request_handler/create_spec.rb
@@ -613,6 +615,7 @@ test_files:
613
615
  - spec/dummy/tmp/.keep
614
616
  - spec/dummy/tmp/storage/.keep
615
617
  - spec/integration/yard/azeroth/decorator_spec.rb
618
+ - spec/lib/azeroth/decorator/hash_builder_spec.rb
616
619
  - spec/lib/azeroth/decorator_spec.rb
617
620
  - spec/lib/azeroth/model_spec.rb
618
621
  - spec/lib/azeroth/request_handler/create_spec.rb