sinclair 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|