sinclair 1.1.0 → 1.1.1
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/.gitignore +2 -0
- data/.rubocop_todo.yml +0 -2
- data/README.md +82 -85
- data/lib/sinclair.rb +161 -5
- data/lib/sinclair/matchers.rb +25 -0
- data/lib/sinclair/matchers/add_method.rb +20 -5
- data/lib/sinclair/matchers/add_method_to.rb +57 -14
- data/lib/sinclair/method_definition.rb +41 -24
- data/lib/sinclair/options_parser.rb +31 -5
- data/lib/sinclair/version.rb +1 -1
- data/sinclair.gemspec +1 -0
- data/spec/integration/readme/my_class_spec.rb +2 -2
- data/spec/integration/readme_spec.rb +3 -3
- data/spec/integration/yard/matchers/add_method_to_spec.rb +16 -0
- data/spec/integration/yard/options_parser_spec.rb +14 -0
- data/spec/integration/yard/sinclair_spec.rb +149 -0
- data/spec/lib/sinclair/method_definition_spec.rb +6 -6
- data/spec/lib/sinclair/options_parser_spec.rb +2 -2
- data/spec/support/models/initial_valuer.rb +13 -0
- data/spec/support/models/my_model.rb +2 -0
- data/spec/support/models/person.rb +8 -0
- data/spec/support/models/purchase.rb +6 -0
- data/spec/support/models/validator_builder.rb +2 -2
- metadata +23 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e2734e2007f6c98dc270ea39df18e07aed7f9324
|
4
|
+
data.tar.gz: d570bf309c48f65b080b0db7da0301968fb055c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4cbe6a2654818eedc3aac238d6689e61210d1e2fe9ad1dfc892bc340474b9ccc49a1474a2fa4e69dabf3dd3975ddc003120cc2a7db31c4a32b74ea3b55fc8924
|
7
|
+
data.tar.gz: daf0ff8fbe75d336c0651dc27298b530a12b1c2709e67b3a6b2dd3c64407a5ddbb1ff14d0a92dd018a3df4f1f143508c0812e4075f4317a0f2105b214dc01f49
|
data/.gitignore
CHANGED
data/.rubocop_todo.yml
CHANGED
@@ -111,8 +111,6 @@ Metrics/BlockLength:
|
|
111
111
|
# SupportedStyles: nested, compact
|
112
112
|
Style/ClassAndModuleChildren:
|
113
113
|
Exclude:
|
114
|
-
- 'lib/sinclair/method_definition.rb'
|
115
|
-
- 'lib/sinclair/options_parser.rb'
|
116
114
|
- 'spec/support/models/dummy_builder.rb'
|
117
115
|
- 'spec/support/models/dummy_options_parser.rb'
|
118
116
|
|
data/README.md
CHANGED
@@ -33,34 +33,38 @@ The concern builder can actully be used in two ways, as an stand alone object ca
|
|
33
33
|
adding methods to your class or by extending it for more complex logics
|
34
34
|
|
35
35
|
- Stand Alone usage:
|
36
|
-
```ruby
|
37
|
-
class Clazz
|
38
|
-
end
|
39
36
|
|
40
|
-
|
37
|
+
```ruby
|
41
38
|
|
42
|
-
|
43
|
-
|
44
|
-
builder.build
|
39
|
+
class Clazz
|
40
|
+
end
|
45
41
|
|
46
|
-
|
42
|
+
builder = Sinclair.new(Clazz)
|
47
43
|
|
48
|
-
|
49
|
-
|
50
|
-
|
44
|
+
builder.add_method(:twenty, '10 + 10')
|
45
|
+
builder.add_method(:eighty) { 4 * twenty }
|
46
|
+
builder.build
|
51
47
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
48
|
+
instance = Clazz.new
|
49
|
+
|
50
|
+
puts "Twenty => #{instance.twenty}"
|
51
|
+
puts "Eighty => #{instance.eighty}"
|
52
|
+
```
|
53
|
+
|
54
|
+
```string
|
55
|
+
|
56
|
+
Twenty => 20
|
57
|
+
Eighty => 80
|
58
|
+
```
|
56
59
|
|
57
60
|
- Extending the builder
|
58
61
|
|
59
62
|
```ruby
|
63
|
+
|
60
64
|
class ValidationBuilder < Sinclair
|
61
65
|
delegate :expected, to: :options_object
|
62
66
|
|
63
|
-
def initialize(
|
67
|
+
def initialize(klass, options={})
|
64
68
|
super
|
65
69
|
end
|
66
70
|
|
@@ -69,7 +73,7 @@ adding methods to your class or by extending it for more complex logics
|
|
69
73
|
end
|
70
74
|
|
71
75
|
def add_accessors(fields)
|
72
|
-
|
76
|
+
klass.send(:attr_accessor, *fields)
|
73
77
|
end
|
74
78
|
end
|
75
79
|
|
@@ -119,42 +123,32 @@ adding methods to your class or by extending it for more complex logics
|
|
119
123
|
```
|
120
124
|
|
121
125
|
the instance will respond to the methods
|
122
|
-
```name``` ```name=``` ```name_valid?```
|
123
|
-
```surname``` ```surname=``` ```surname_valid?```
|
124
|
-
```age``` ```age=``` ```age_valid?```
|
125
|
-
```legs``` ```legs=``` ```legs_valid?```
|
126
|
-
```valid?```
|
126
|
+
```name``` ```name=``` ```name_valid?```
|
127
|
+
```surname``` ```surname=``` ```surname_valid?```
|
128
|
+
```age``` ```age=``` ```age_valid?```
|
129
|
+
```legs``` ```legs=``` ```legs_valid?```
|
130
|
+
```valid?```
|
127
131
|
|
128
132
|
```ruby
|
133
|
+
|
129
134
|
valid_object = MyClass.new(
|
130
135
|
name: :name,
|
131
136
|
surname: 'surname',
|
132
137
|
age: 20,
|
133
138
|
legs: 2
|
134
139
|
)
|
135
|
-
valid_object.valid?
|
136
|
-
```
|
137
|
-
|
138
|
-
returns
|
139
|
-
|
140
|
-
```
|
141
|
-
true
|
140
|
+
valid_object.valid? # returns true
|
142
141
|
```
|
143
142
|
|
144
143
|
```ruby
|
144
|
+
|
145
145
|
invalid_object = MyClass.new(
|
146
146
|
name: 'name',
|
147
147
|
surname: 'surname',
|
148
148
|
age: 20,
|
149
149
|
legs: 2
|
150
150
|
)
|
151
|
-
invalid_object.valid?
|
152
|
-
```
|
153
|
-
|
154
|
-
returns
|
155
|
-
|
156
|
-
```
|
157
|
-
false
|
151
|
+
invalid_object.valid? # returns false
|
158
152
|
```
|
159
153
|
|
160
154
|
RSspec matcher
|
@@ -162,64 +156,67 @@ RSspec matcher
|
|
162
156
|
|
163
157
|
You can use the provided matcher to check that your builder is adding a method correctly
|
164
158
|
|
165
|
-
```ruby
|
166
|
-
class DefaultValue
|
167
|
-
delegate :build, to: :builder
|
168
|
-
attr_reader :klass, :method, :value
|
159
|
+
```ruby
|
169
160
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
161
|
+
class DefaultValue
|
162
|
+
delegate :build, to: :builder
|
163
|
+
attr_reader :klass, :method, :value
|
164
|
+
|
165
|
+
def initialize(klass, method, value)
|
166
|
+
@klass = klass
|
167
|
+
@method = method
|
168
|
+
@value = value
|
169
|
+
end
|
175
170
|
|
176
|
-
|
171
|
+
private
|
177
172
|
|
178
|
-
|
179
|
-
|
180
|
-
|
173
|
+
def builder
|
174
|
+
@builder ||= Sinclair.new(klass).tap do |b|
|
175
|
+
b.add_method(method) { value }
|
176
|
+
end
|
181
177
|
end
|
182
178
|
end
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
config.include Sinclair::Matchers
|
187
|
-
end
|
188
|
-
|
189
|
-
RSpec.describe DefaultValue do
|
190
|
-
let(:klass) { Class.new }
|
191
|
-
let(:method) { :the_method }
|
192
|
-
let(:value) { Random.rand(100) }
|
193
|
-
let(:builder) { described_class.new(klass, method, value) }
|
194
|
-
let(:instance) { klass.new }
|
195
|
-
|
196
|
-
context 'when the builder runs' do
|
197
|
-
it do
|
198
|
-
expect do
|
199
|
-
described_class.new(klass, method, value).build
|
200
|
-
end.to add_method(method).to(instance)
|
201
|
-
end
|
179
|
+
|
180
|
+
RSpec.configure do |config|
|
181
|
+
config.include Sinclair::Matchers
|
202
182
|
end
|
203
183
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
184
|
+
RSpec.describe DefaultValue do
|
185
|
+
let(:klass) { Class.new }
|
186
|
+
let(:method) { :the_method }
|
187
|
+
let(:value) { Random.rand(100) }
|
188
|
+
let(:builder) { described_class.new(klass, method, value) }
|
189
|
+
let(:instance) { klass.new }
|
190
|
+
|
191
|
+
context 'when the builder runs' do
|
192
|
+
it do
|
193
|
+
expect do
|
194
|
+
described_class.new(klass, method, value).build
|
195
|
+
end.to add_method(method).to(instance)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
context 'when the builder runs' do
|
200
|
+
it do
|
201
|
+
expect do
|
202
|
+
described_class.new(klass, method, value).build
|
203
|
+
end.to add_method(method).to(klass)
|
204
|
+
end
|
209
205
|
end
|
210
206
|
end
|
211
|
-
|
212
|
-
```
|
207
|
+
```
|
213
208
|
|
214
|
-
```bash
|
215
|
-
> bundle exec rspec
|
216
|
-
```
|
209
|
+
```bash
|
217
210
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
when the builder runs
|
223
|
-
should add method 'the_method' to #<Class:0x0000000143a1b0> instances
|
211
|
+
> bundle exec rspec
|
212
|
+
```
|
213
|
+
|
214
|
+
```string
|
224
215
|
|
225
|
-
|
216
|
+
DefaultValue
|
217
|
+
when the builder runs
|
218
|
+
should add method 'the_method' to #<Class:0x0000000146c160> instances
|
219
|
+
when the builder runs
|
220
|
+
should add method 'the_method' to #<Class:0x0000000143a1b0> instances
|
221
|
+
|
222
|
+
```
|
data/lib/sinclair.rb
CHANGED
@@ -1,6 +1,25 @@
|
|
1
1
|
require 'active_support'
|
2
2
|
require 'active_support/all'
|
3
3
|
|
4
|
+
# Builder that add instance methods to a class
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
#
|
8
|
+
# class MyModel
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# buider = Sinclair.new(MyModel)
|
12
|
+
#
|
13
|
+
# value = 10
|
14
|
+
# builder.add_method(:default_value) { value }
|
15
|
+
# builder.add_method(:value, '@value || default_value')
|
16
|
+
# builder.add_method(:value=) { |val| @value = val }
|
17
|
+
# builder.build
|
18
|
+
#
|
19
|
+
# instance = MyModel.new
|
20
|
+
# instance.value # returns 10
|
21
|
+
# instance.value = 20
|
22
|
+
# instance.value # returns 20
|
4
23
|
class Sinclair
|
5
24
|
require 'sinclair/options_parser'
|
6
25
|
|
@@ -10,29 +29,166 @@ class Sinclair
|
|
10
29
|
|
11
30
|
include OptionsParser
|
12
31
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
32
|
+
# @param klass [Class] to receive the methods
|
33
|
+
# @param options [Hash] open hash options to be used by builders inheriting from Sinclair
|
34
|
+
# through the Sinclair::OptionsParser concern
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
#
|
38
|
+
# class Purchase
|
39
|
+
# def initialize(value, quantity)
|
40
|
+
# @value = value
|
41
|
+
# @quantity = quantity
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# builder = Sinclair.new(Purchase)
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
#
|
49
|
+
# builder = Sinclair.new(Purchase, rescue_error: true)
|
50
|
+
def initialize(klass, options = {})
|
51
|
+
@klass = klass
|
17
52
|
@options = options
|
18
53
|
end
|
19
54
|
|
55
|
+
# builds all the methods added into the klass
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
#
|
59
|
+
# class MyModel
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# buider = Sinclair.new(MyModel)
|
63
|
+
#
|
64
|
+
# builder.add_method(:default_value) { value }
|
65
|
+
#
|
66
|
+
# MyModel.new.respond_to(:default_value) # returns false
|
67
|
+
#
|
68
|
+
# builder.build
|
69
|
+
#
|
70
|
+
# MyModel.new.respond_to(:default_value) # returns true
|
20
71
|
def build
|
21
72
|
definitions.each do |definition|
|
22
|
-
definition.build(
|
73
|
+
definition.build(klass)
|
23
74
|
end
|
24
75
|
end
|
25
76
|
|
77
|
+
# add a method to the method list to be created on klass
|
78
|
+
#
|
79
|
+
# @overload add_method(name, code)
|
80
|
+
# @param name [String/Symbol] name of the method to be added
|
81
|
+
# @param code [String] code to be evaluated when the method is ran
|
82
|
+
#
|
83
|
+
# @example
|
84
|
+
# class Person
|
85
|
+
# attr_reader :first_name, :last_name
|
86
|
+
#
|
87
|
+
# def initialize(first_name, last_name)
|
88
|
+
# @first_name = first_name
|
89
|
+
# @last_name = last_name
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# builder = Sinclair.new(Person)
|
94
|
+
# builder.add_method(:full_name, '[first_name, last_name].join(" ")')
|
95
|
+
# builder.build
|
96
|
+
#
|
97
|
+
# Person.new('john', 'wick').full_name # returns 'john wick'
|
98
|
+
#
|
99
|
+
# @overload add_method(name, &block)
|
100
|
+
# @param name [String/Symbol] name of the method to be added
|
101
|
+
# @param block [Proc] block to be ran as method
|
102
|
+
#
|
103
|
+
# @example
|
104
|
+
#
|
105
|
+
# builder = Sinclair.new(Person)
|
106
|
+
# builder.add_method(:bond_name) { "#{last_name}, #{full_name}" }
|
107
|
+
# builder.build
|
108
|
+
#
|
109
|
+
# Person.new('john', 'wick').bond_name # returns 'wick, john wick'
|
26
110
|
def add_method(name, code = nil, &block)
|
27
111
|
definitions << MethodDefinition.new(name, code, &block)
|
28
112
|
end
|
29
113
|
|
114
|
+
# evaluetes a block which will result in a [String] to be
|
115
|
+
# then used as code for the method
|
116
|
+
#
|
117
|
+
# @example
|
118
|
+
#
|
119
|
+
# module InitialValuer
|
120
|
+
# extend ActiveSupport::Concern
|
121
|
+
#
|
122
|
+
# class_methods do
|
123
|
+
# def initial_value_for(attribute, value)
|
124
|
+
# builder = Sinclair.new(self, initial_value: value)
|
125
|
+
# builder.eval_and_add_method(attribute) do
|
126
|
+
# "@#{attribute} ||= #{options_object.initial_value}"
|
127
|
+
# end
|
128
|
+
# builder.build
|
129
|
+
# end
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
#
|
133
|
+
# class MyClass
|
134
|
+
# include InitialValuer
|
135
|
+
# attr_writer :age
|
136
|
+
# initial_value_for :age, 20
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
# object = MyClass.new
|
140
|
+
# object.age # 20
|
141
|
+
# object.age = 30
|
142
|
+
# object.age # 30
|
143
|
+
#
|
144
|
+
# @example
|
145
|
+
#
|
146
|
+
# class Purchase
|
147
|
+
# def initialize(value, quantity)
|
148
|
+
# @value = value
|
149
|
+
# @quantity = quantity
|
150
|
+
# end
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# builder = Sinclair.new(Purchase)
|
154
|
+
#
|
155
|
+
# builder.eval_and_add_method(:total_price) do
|
156
|
+
# code = 'self.value * self.quantity'
|
157
|
+
# code.concat ' rescue 0' if options_object.rescue_error
|
158
|
+
# code
|
159
|
+
# end
|
160
|
+
#
|
161
|
+
# builder.build
|
162
|
+
#
|
163
|
+
# Purchase.new(2.3, 5).total_price # raises error
|
164
|
+
#
|
165
|
+
# @example
|
166
|
+
#
|
167
|
+
# builder = Sinclair.new(Purchase, rescue_error: true)
|
168
|
+
#
|
169
|
+
# builder.eval_and_add_method(:total_price) do
|
170
|
+
# code = 'self.value * self.quantity'
|
171
|
+
# code.concat ' rescue 0' if options_object.rescue_error
|
172
|
+
# code
|
173
|
+
# end
|
174
|
+
#
|
175
|
+
# builder.build
|
176
|
+
#
|
177
|
+
# Purchase.new(2.3, 5).total_price # returns 0
|
178
|
+
#
|
179
|
+
# class Purchase
|
180
|
+
# attr_reader :value, :quantity
|
181
|
+
# end
|
182
|
+
#
|
183
|
+
# Purchase.new(2.3, 5).total_price # returns 11.5
|
30
184
|
def eval_and_add_method(name, &block)
|
31
185
|
add_method(name, instance_eval(&block))
|
32
186
|
end
|
33
187
|
|
34
188
|
private
|
35
189
|
|
190
|
+
attr_reader :klass
|
191
|
+
|
36
192
|
def definitions
|
37
193
|
@definitions ||= []
|
38
194
|
end
|
data/lib/sinclair/matchers.rb
CHANGED
@@ -1,8 +1,33 @@
|
|
1
1
|
class Sinclair
|
2
|
+
# Matchers module will have the DSL to be included in RSpec in order to have
|
3
|
+
# access to the matchers
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# RSpec.configure do |config|
|
7
|
+
# config.include Sinclair::Matchers
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# class MyModel
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# RSpec.describe 'my test' do
|
14
|
+
# let(:klass) { Class.new(MyModel) }
|
15
|
+
# let(:builder) { Sinclair.new(klass) }
|
16
|
+
#
|
17
|
+
# before do
|
18
|
+
# builder.add_method(:class_name, 'self.class.name')
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# it do
|
22
|
+
# expect { builder.build }.to add_method(:class_name).to(klass)
|
23
|
+
# end
|
24
|
+
# end
|
2
25
|
module Matchers
|
3
26
|
autoload :AddMethod, 'sinclair/matchers/add_method'
|
4
27
|
autoload :AddMethodTo, 'sinclair/matchers/add_method_to'
|
5
28
|
|
29
|
+
# DSL to AddMethod
|
30
|
+
# @return [AddMethod] RSpec Matcher
|
6
31
|
def add_method(method)
|
7
32
|
Sinclair::Matchers::AddMethod.new(method)
|
8
33
|
end
|
@@ -1,19 +1,29 @@
|
|
1
1
|
class Sinclair
|
2
2
|
module Matchers
|
3
|
+
# AddMethod is able to build an instance of Sinclair::Matchers::AddMethodTo
|
3
4
|
class AddMethod < RSpec::Matchers::BuiltIn::BaseMatcher
|
4
|
-
|
5
|
-
|
5
|
+
# as any matcher is expected to implement matches?, we raise a warning on the usage as
|
6
|
+
# this is only a builder for AddMethodTo
|
6
7
|
def matches?(_actual)
|
7
8
|
raise SyntaxError, 'You should specify which instance the method is being added to' \
|
8
9
|
"add_method(:#{method}).to(instance)"
|
9
10
|
end
|
10
11
|
|
11
|
-
|
12
|
+
# @param method [String/Symbol] the method, to be checked, name
|
13
|
+
def initialize(method)
|
12
14
|
@method = method
|
13
15
|
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
+
# @return [AddMethodTo] the correct matcher
|
18
|
+
# @overload to(klass)
|
19
|
+
# @param [Class] klass
|
20
|
+
# class where the method should be added to
|
21
|
+
#
|
22
|
+
# @overload to(instance)
|
23
|
+
# @param [Object] instance
|
24
|
+
# instance of the class where the method should be added to
|
25
|
+
def to(target = nil)
|
26
|
+
AddMethodTo.new(target, method)
|
17
27
|
end
|
18
28
|
|
19
29
|
def equal?(other)
|
@@ -21,9 +31,14 @@ class Sinclair
|
|
21
31
|
other.method == method
|
22
32
|
end
|
23
33
|
|
34
|
+
# definition needed for block matchers
|
24
35
|
def supports_block_expectations?
|
25
36
|
true
|
26
37
|
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
attr_reader :method
|
27
42
|
end
|
28
43
|
end
|
29
44
|
end
|
@@ -1,37 +1,76 @@
|
|
1
1
|
class Sinclair
|
2
2
|
module Matchers
|
3
|
+
# AddMethodTo checks whether a method was or not added by the call of a block
|
4
|
+
#
|
5
|
+
# This is used with a RSpec DSL method add_method(method_name).to(class_object)
|
6
|
+
#
|
7
|
+
# @author darthjee
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# RSpec.configure do |config|
|
11
|
+
# config.include Sinclair::Matchers
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# class MyModel
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# RSpec.describe 'my test' do
|
18
|
+
# let(:klass) { Class.new(MyModel) }
|
19
|
+
# let(:builder) { Sinclair.new(klass) }
|
20
|
+
#
|
21
|
+
# before do
|
22
|
+
# builder.add_method(:class_name, 'self.class.name')
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# it do
|
26
|
+
# expect { builder.build }.to add_method(:class_name).to(klass)
|
27
|
+
# end
|
28
|
+
# end
|
3
29
|
class AddMethodTo < RSpec::Matchers::BuiltIn::BaseMatcher
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
30
|
+
# @overload initialize(klass, method)
|
31
|
+
# @param [Class] klass
|
32
|
+
# class where the method should be added to
|
33
|
+
#
|
34
|
+
# @overload initialize(instance, method)
|
35
|
+
# @param [Object] klass
|
36
|
+
# instance of the class where the method should be added to
|
37
|
+
#
|
38
|
+
# @param method
|
39
|
+
# method name
|
40
|
+
def initialize(target, method)
|
41
|
+
if target.is_a?(Class)
|
42
|
+
@klass = target
|
9
43
|
else
|
10
|
-
@instance =
|
44
|
+
@instance = target
|
11
45
|
end
|
12
46
|
@method = method
|
13
47
|
end
|
14
48
|
|
49
|
+
# @return [String] expectation description
|
15
50
|
def description
|
16
|
-
"add method '#{method}' to #{
|
51
|
+
"add method '#{method}' to #{klass} instances"
|
17
52
|
end
|
18
53
|
|
54
|
+
# @return [String] message on expectation failure
|
19
55
|
def failure_message_for_should
|
20
|
-
"expected '#{method}' to be added to #{
|
56
|
+
"expected '#{method}' to be added to #{klass} but " \
|
21
57
|
"#{@initial_state ? 'it already existed' : "it didn't"}"
|
22
58
|
end
|
23
59
|
|
60
|
+
# @return [String] message on expectation failure for negative expectation
|
24
61
|
def failure_message_for_should_not
|
25
|
-
"expected '#{method}' not to be added to #{
|
62
|
+
"expected '#{method}' not to be added to #{klass} but it was"
|
26
63
|
end
|
27
64
|
|
65
|
+
# @return [Boolean] expectation check
|
28
66
|
def matches?(event_proc)
|
29
67
|
return false unless event_proc.is_a?(Proc)
|
30
68
|
raise_block_syntax_error if block_given?
|
31
69
|
perform_change(event_proc)
|
32
|
-
|
70
|
+
added?
|
33
71
|
end
|
34
72
|
|
73
|
+
# definition needed for block matchers
|
35
74
|
def supports_block_expectations?
|
36
75
|
true
|
37
76
|
end
|
@@ -44,7 +83,11 @@ class Sinclair
|
|
44
83
|
|
45
84
|
protected
|
46
85
|
|
47
|
-
|
86
|
+
attr_reader :method, :instance
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def added?
|
48
91
|
!@initial_state && @final_state
|
49
92
|
end
|
50
93
|
|
@@ -55,11 +98,11 @@ class Sinclair
|
|
55
98
|
end
|
56
99
|
|
57
100
|
def method_defined?
|
58
|
-
|
101
|
+
klass.method_defined?(method)
|
59
102
|
end
|
60
103
|
|
61
|
-
def
|
62
|
-
@
|
104
|
+
def klass
|
105
|
+
@klass ||= instance.class
|
63
106
|
end
|
64
107
|
|
65
108
|
def raise_block_syntax_error
|
@@ -1,35 +1,52 @@
|
|
1
|
-
class Sinclair
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
@name
|
6
|
-
|
7
|
-
@
|
8
|
-
|
1
|
+
class Sinclair
|
2
|
+
# Definition of the code or block to be aded as method
|
3
|
+
class MethodDefinition
|
4
|
+
# @overload initialize(name, code)
|
5
|
+
# @overload initialize(name, &block)
|
6
|
+
#
|
7
|
+
# @param name [String/Symbol] name of the method
|
8
|
+
# @param code [String] code to be evaluated as method
|
9
|
+
# @param block [Proc] block with code to be added as method
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# Sinclair::Method.new(:name, '@name')
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# Sinclair::Method.new(:name) { @name }
|
16
|
+
def initialize(name, code = nil, &block)
|
17
|
+
@name = name
|
18
|
+
@code = code
|
19
|
+
@block = block
|
20
|
+
end
|
9
21
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
22
|
+
# Adds the method to given klass
|
23
|
+
# @param klass [Class] class which will receive the new method
|
24
|
+
def build(klass)
|
25
|
+
if code.is_a?(String)
|
26
|
+
build_code_method(klass)
|
27
|
+
else
|
28
|
+
build_block_method(klass)
|
29
|
+
end
|
15
30
|
end
|
16
|
-
end
|
17
31
|
|
18
|
-
|
32
|
+
private
|
19
33
|
|
20
|
-
|
21
|
-
clazz.send(:define_method, name, block)
|
22
|
-
end
|
34
|
+
attr_reader :name, :code, :block
|
23
35
|
|
24
|
-
|
25
|
-
|
26
|
-
|
36
|
+
def build_block_method(klass)
|
37
|
+
klass.send(:define_method, name, block)
|
38
|
+
end
|
27
39
|
|
28
|
-
|
29
|
-
|
40
|
+
def build_code_method(klass)
|
41
|
+
klass.module_eval(code_definition, __FILE__, __LINE__ + 1)
|
42
|
+
end
|
43
|
+
|
44
|
+
def code_definition
|
45
|
+
<<-CODE
|
30
46
|
def #{name}
|
31
47
|
#{code}
|
32
48
|
end
|
33
|
-
|
49
|
+
CODE
|
50
|
+
end
|
34
51
|
end
|
35
52
|
end
|
@@ -1,9 +1,35 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
class Sinclair
|
2
|
+
# Concern for easily adding options
|
3
|
+
#
|
4
|
+
# @example
|
5
|
+
# class Sinclair
|
6
|
+
# class OptionsParser::Dummy
|
7
|
+
# include OptionsParser
|
8
|
+
#
|
9
|
+
# def initialize(options)
|
10
|
+
# @options = options.deep_dup
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# def the_method
|
15
|
+
# return 'missing option' if options_object.switch.nil?
|
16
|
+
#
|
17
|
+
# if options_object.switch
|
18
|
+
# "The value is #{options_object.option_1}"
|
19
|
+
# else
|
20
|
+
# "The value is not #{options_object.option_1} but #{options_object.option_2}"
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
module OptionsParser
|
25
|
+
extend ActiveSupport::Concern
|
3
26
|
|
4
|
-
|
27
|
+
private
|
5
28
|
|
6
|
-
|
7
|
-
|
29
|
+
attr_reader :options
|
30
|
+
|
31
|
+
def options_object
|
32
|
+
@options_object ||= OpenStruct.new options
|
33
|
+
end
|
8
34
|
end
|
9
35
|
end
|
data/lib/sinclair/version.rb
CHANGED
data/sinclair.gemspec
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'Stand Alone' do
|
4
|
-
let(:instance) {
|
5
|
-
let(:
|
6
|
-
let(:builder) { Sinclair.new(
|
4
|
+
let(:instance) { klass.new }
|
5
|
+
let(:klass) { Class.new }
|
6
|
+
let(:builder) { Sinclair.new(klass) }
|
7
7
|
|
8
8
|
before do
|
9
9
|
builder.add_method(:twenty, '10 + 10')
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'yard' do
|
4
|
+
describe Sinclair::Matchers::AddMethodTo do
|
5
|
+
let(:klass) { Class.new(MyModel) }
|
6
|
+
let(:builder) { Sinclair.new(klass) }
|
7
|
+
|
8
|
+
before do
|
9
|
+
builder.add_method(:class_name, 'self.class.name')
|
10
|
+
end
|
11
|
+
|
12
|
+
it do
|
13
|
+
expect { builder.build }.to add_method(:class_name).to(klass)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'yard' do
|
4
|
+
describe Sinclair::OptionsParser do
|
5
|
+
let(:options) { { switch: false, option_1: 10, option_2: 20 } }
|
6
|
+
subject { described_class::Dummy.new(options) }
|
7
|
+
|
8
|
+
describe '#the_method' do
|
9
|
+
it 'returns the value for option given' do
|
10
|
+
expect(subject.the_method).to eq('The value is not 10 but 20')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'yarn' do
|
4
|
+
describe Sinclair do
|
5
|
+
let(:klass) { Class.new(MyModel) }
|
6
|
+
let(:instance) { klass.new }
|
7
|
+
let(:builder) { Sinclair.new(klass) }
|
8
|
+
let(:default_value) { 10 }
|
9
|
+
|
10
|
+
describe '#initialize' do
|
11
|
+
describe '#total_price' do
|
12
|
+
before do
|
13
|
+
subject.eval_and_add_method(:total_price) do
|
14
|
+
code = 'self.value * self.quantity'
|
15
|
+
code.concat ' rescue 0' if options_object.rescue_error
|
16
|
+
code
|
17
|
+
end
|
18
|
+
|
19
|
+
subject.build
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'without options' do
|
23
|
+
subject { Sinclair.new(klass, rescue_error: true) }
|
24
|
+
|
25
|
+
let(:klass) { Class.new(Purchase) }
|
26
|
+
let(:instance) { klass.new(2.3, 5) }
|
27
|
+
|
28
|
+
it 'evaluates into default_value' do
|
29
|
+
expect(instance.total_price).to eq(0)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'with options' do
|
34
|
+
subject { Sinclair.new(klass) }
|
35
|
+
|
36
|
+
let(:klass) { Class.new(Purchase) }
|
37
|
+
let(:instance) { klass.new(2.3, 5) }
|
38
|
+
|
39
|
+
it do
|
40
|
+
expect { instance.total_price }.to raise_error(NoMethodError)
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'with attribute readers' do
|
44
|
+
before do
|
45
|
+
klass.send(:attr_reader, :value, :quantity)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'calculates total price' do
|
49
|
+
expect(instance.total_price).to eq(11.5)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#build' do
|
57
|
+
before do
|
58
|
+
value = default_value
|
59
|
+
builder.add_method(:default_value) { value }
|
60
|
+
builder.add_method(:value, '@value || default_value')
|
61
|
+
builder.add_method(:value=) { |val| @value = val }
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'after the build' do
|
65
|
+
before { builder.build }
|
66
|
+
it 'creates the expected methods' do
|
67
|
+
expect(instance.value).to eq(10)
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'when default value is overwritten' do
|
71
|
+
before do
|
72
|
+
instance.value = 20
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'returns the new written value' do
|
76
|
+
expect(instance.value).to eq(20)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'calling the build' do
|
82
|
+
it do
|
83
|
+
expect do
|
84
|
+
builder.build
|
85
|
+
end.to change { instance.respond_to?(:default_value) }.to(true)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#add_method' do
|
91
|
+
let(:klass) { Class.new(Person) }
|
92
|
+
let(:instance) { klass.new('john', 'wick') }
|
93
|
+
|
94
|
+
before do
|
95
|
+
builder.add_method(:full_name, '[first_name, last_name].join(" ")')
|
96
|
+
builder.add_method(:bond_name) { "#{last_name}, #{first_name} #{last_name}" }
|
97
|
+
builder.build
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#full_name' do
|
101
|
+
let(:klass) { Class.new(Person) }
|
102
|
+
let(:instance) { klass.new('john', 'wick') }
|
103
|
+
|
104
|
+
before do
|
105
|
+
builder.add_method(:full_name, '[first_name, last_name].join(" ")')
|
106
|
+
builder.build
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'returns the full name' do
|
110
|
+
expect(instance.bond_name).to eq('wick, john wick')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe '#bond_style' do
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#eval_and_add_method' do
|
119
|
+
subject { klass.new }
|
120
|
+
|
121
|
+
let(:klass) do
|
122
|
+
Class.new do
|
123
|
+
include InitialValuer
|
124
|
+
attr_writer :age
|
125
|
+
initial_value_for :age, 20
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe '#age' do
|
130
|
+
context 'when it has not been initialized' do
|
131
|
+
it do
|
132
|
+
expect(subject.age).to eq(20)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'when it has been initialized' do
|
137
|
+
before do
|
138
|
+
subject.age
|
139
|
+
subject.age = 30
|
140
|
+
end
|
141
|
+
|
142
|
+
it do
|
143
|
+
expect(subject.age).to eq(30)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Sinclair::MethodDefinition do
|
4
|
-
let(:
|
5
|
-
let(:instance) {
|
4
|
+
let(:klass) { Class.new }
|
5
|
+
let(:instance) { klass.new }
|
6
6
|
|
7
7
|
describe '#build' do
|
8
8
|
let(:method_name) { :the_method }
|
@@ -13,10 +13,10 @@ describe Sinclair::MethodDefinition do
|
|
13
13
|
subject { described_class.new(method_name, code) }
|
14
14
|
|
15
15
|
before do
|
16
|
-
subject.build(
|
16
|
+
subject.build(klass)
|
17
17
|
end
|
18
18
|
|
19
|
-
it 'adds the method to the
|
19
|
+
it 'adds the method to the klass instance' do
|
20
20
|
expect(instance).to respond_to(method_name)
|
21
21
|
end
|
22
22
|
|
@@ -33,10 +33,10 @@ describe Sinclair::MethodDefinition do
|
|
33
33
|
end
|
34
34
|
|
35
35
|
before do
|
36
|
-
subject.build(
|
36
|
+
subject.build(klass)
|
37
37
|
end
|
38
38
|
|
39
|
-
it 'adds the method to the
|
39
|
+
it 'adds the method to the klass instance' do
|
40
40
|
expect(instance).to respond_to(method_name)
|
41
41
|
end
|
42
42
|
|
@@ -1,13 +1,13 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Sinclair::OptionsParser do
|
4
|
-
let(:
|
4
|
+
let(:klass) { described_class::Dummy }
|
5
5
|
let(:switched) { true }
|
6
6
|
let(:value_1) { 'value1' }
|
7
7
|
let(:options) { { switch: switched, option_1: value_1, option_2: 2} }
|
8
8
|
|
9
9
|
subject do
|
10
|
-
|
10
|
+
klass.new(options)
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'enables the given options to be acced' do
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module InitialValuer
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
class_methods do
|
5
|
+
def initial_value_for(attribute, value)
|
6
|
+
builder = Sinclair.new(self, initial_value: value)
|
7
|
+
builder.eval_and_add_method(attribute) do
|
8
|
+
"@#{attribute} ||= #{options_object.initial_value}"
|
9
|
+
end
|
10
|
+
builder.build
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class ValidationBuilder < Sinclair
|
2
2
|
delegate :expected, to: :options_object
|
3
3
|
|
4
|
-
def initialize(
|
4
|
+
def initialize(klass, options={})
|
5
5
|
super
|
6
6
|
end
|
7
7
|
|
@@ -10,6 +10,6 @@ class ValidationBuilder < Sinclair
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def add_accessors(fields)
|
13
|
-
|
13
|
+
klass.send(:attr_accessor, *fields)
|
14
14
|
end
|
15
15
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinclair
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- DarthJee
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-07-
|
11
|
+
date: 2018-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: yard
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
111
125
|
description: Gem for easy concern creation
|
112
126
|
email:
|
113
127
|
- darthjee@gmail.com
|
@@ -138,6 +152,9 @@ files:
|
|
138
152
|
- spec/integration/readme/matcher_spec.rb
|
139
153
|
- spec/integration/readme/my_class_spec.rb
|
140
154
|
- spec/integration/readme_spec.rb
|
155
|
+
- spec/integration/yard/matchers/add_method_to_spec.rb
|
156
|
+
- spec/integration/yard/options_parser_spec.rb
|
157
|
+
- spec/integration/yard/sinclair_spec.rb
|
141
158
|
- spec/lib/sinclair/matchers/add_method_spec.rb
|
142
159
|
- spec/lib/sinclair/matchers/add_method_to_spec.rb
|
143
160
|
- spec/lib/sinclair/matchers_spec.rb
|
@@ -148,9 +165,13 @@ files:
|
|
148
165
|
- spec/support/fixture_helpers.rb
|
149
166
|
- spec/support/models/dummy_builder.rb
|
150
167
|
- spec/support/models/dummy_options_parser.rb
|
168
|
+
- spec/support/models/initial_valuer.rb
|
151
169
|
- spec/support/models/my_builder.rb
|
152
170
|
- spec/support/models/my_class.rb
|
153
171
|
- spec/support/models/my_concern.rb
|
172
|
+
- spec/support/models/my_model.rb
|
173
|
+
- spec/support/models/person.rb
|
174
|
+
- spec/support/models/purchase.rb
|
154
175
|
- spec/support/models/validator_builder.rb
|
155
176
|
homepage: https://github.com/darthjee/sinclair
|
156
177
|
licenses: []
|