azeroth 0.1.0 → 0.2.0
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 +4 -4
- data/README.md +3 -1
- data/config/yardstick.yml +1 -0
- data/lib/azeroth.rb +4 -3
- data/lib/azeroth/decorator.rb +50 -15
- data/lib/azeroth/decorator/hash_builder.rb +99 -0
- data/lib/azeroth/version.rb +1 -1
- data/spec/dummy/app/models/document/decorator.rb +5 -0
- data/spec/dummy/app/models/dummy_model.rb +8 -0
- data/spec/dummy/app/models/dummy_model/decorator.rb +7 -1
- data/spec/lib/azeroth/decorator/hash_builder_spec.rb +74 -0
- data/spec/lib/azeroth/decorator_spec.rb +76 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4969648cf9b578079bc4d7baca012aa82768fd37edc9279ed2be606ef11b78b4
|
4
|
+
data.tar.gz: e426759bcab101a0ef02b0b9cc68d56d2cabc43fdea9a30027974dbaebf7a4e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8848cd9189e14cb2ac4ce22b401b2d5c4d5a39af868a38c1746a9e05136018bbf42b5e071f4feff680b823ab70fc9f3320c60fb0b4c982dfee23a2621781bc0a
|
7
|
+
data.tar.gz: ed36e6cf227e30e6f37dbe8ca109ccfecda6f2747349a1c73e97cf7319af929785aeaa0ccb8ab08265d0524d58a39fbe6a3101f5e95cb1b39998879866f783d2
|
data/README.md
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
Azeroth
|
2
2
|
========
|
3
|
+
[](https://circleci.com/gh/darthjee/azeroth)
|
3
4
|
[](https://codeclimate.com/github/darthjee/azeroth)
|
4
5
|
[](https://codeclimate.com/github/darthjee/azeroth/coverage)
|
5
6
|
[](https://codeclimate.com/github/darthjee/azeroth)
|
6
7
|
[](https://badge.fury.io/rb/azeroth)
|
8
|
+
[](http://inch-ci.org/github/darthjee/azeroth)
|
7
9
|
|
8
10
|

|
9
11
|
|
10
12
|
Yard Documentation
|
11
13
|
-------------------
|
12
|
-
https://www.rubydoc.info/gems/azeroth/0.
|
14
|
+
[https://www.rubydoc.info/gems/azeroth/0.2.0](https://www.rubydoc.info/gems/azeroth/0.2.0)
|
data/config/yardstick.yml
CHANGED
data/lib/azeroth.rb
CHANGED
@@ -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'
|
data/lib/azeroth/decorator.rb
CHANGED
@@ -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
|
50
|
-
@
|
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
|
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,
|
90
|
+
def expose(attribute, **options)
|
86
91
|
builder = Sinclair.new(self)
|
87
|
-
builder.add_method(
|
92
|
+
builder.add_method(attribute, "@object.#{attribute}")
|
88
93
|
builder.build
|
89
94
|
|
90
|
-
|
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
|
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
|
-
|
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
|
data/lib/azeroth/version.rb
CHANGED
@@ -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.
|
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-
|
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
|