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 +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
|
+
[![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.
|
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
|