sinclair 1.1.3 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +24 -0
- data/.rubocop_todo.yml +1 -1
- data/README.md +126 -103
- data/lib/sinclair.rb +40 -10
- data/lib/sinclair/method_definition.rb +96 -15
- data/lib/sinclair/version.rb +1 -1
- data/spec/integration/readme/my_class_spec.rb +13 -10
- data/spec/integration/readme/my_model_spec.rb +29 -0
- data/spec/integration/readme/sinclair/matchers_spec.rb +24 -0
- data/spec/integration/readme/sinclair_spec.rb +25 -0
- data/spec/integration/{matcher_spec.rb → sinclair/matchers_spec.rb} +7 -7
- data/spec/integration/yard/my_builder_spec.rb +27 -0
- data/spec/integration/yard/{matchers → sinclair/matchers}/add_method_spec.rb +2 -2
- data/spec/integration/yard/{matchers → sinclair/matchers}/add_method_to_spec.rb +2 -2
- data/spec/integration/yard/sinclair/method_definition_spec.rb +54 -0
- data/spec/integration/yard/{options_parser_spec.rb → sinclair/options_parser_spec.rb} +5 -4
- data/spec/integration/yard/sinclair_spec.rb +17 -19
- data/spec/lib/sinclair/matchers/add_method_spec.rb +7 -8
- data/spec/lib/sinclair/matchers/add_method_to_spec.rb +28 -26
- data/spec/lib/sinclair/method_definition_spec.rb +75 -20
- data/spec/lib/sinclair/options_parser_spec.rb +14 -15
- data/spec/lib/sinclair_spec.rb +43 -43
- data/spec/spec_helper.rb +0 -1
- data/spec/support/models/default_value.rb +20 -0
- data/spec/support/models/my_builder.rb +14 -13
- metadata +12 -8
- data/spec/integration/readme/matcher_spec.rb +0 -27
- data/spec/integration/readme_spec.rb +0 -23
@@ -6,30 +6,82 @@ class Sinclair
|
|
6
6
|
#
|
7
7
|
# Definition of the code or block to be aded as method
|
8
8
|
class MethodDefinition
|
9
|
+
include Sinclair::OptionsParser
|
10
|
+
# Default options of initialization
|
11
|
+
DEFAULT_OPTIONS = {
|
12
|
+
cached: false
|
13
|
+
}.freeze
|
14
|
+
|
9
15
|
# Returns a new instance of MethodDefinition
|
10
16
|
#
|
11
17
|
# @overload initialize(name, code)
|
12
|
-
#
|
18
|
+
# @example
|
19
|
+
# Sinclair::MethodDefinition.new(:name, '@name')
|
13
20
|
#
|
14
|
-
# @
|
15
|
-
#
|
16
|
-
# @
|
17
|
-
#
|
18
|
-
# @example
|
19
|
-
# Sinclair::Method.new(:name, '@name')
|
21
|
+
# @overload initialize(name, &block)
|
22
|
+
# @example
|
23
|
+
# Sinclair::MethodDefinition.new(:name) { @name }
|
20
24
|
#
|
21
|
-
# @
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
# @param name [String,Symbol] name of the method
|
26
|
+
# @param code [String] code to be evaluated as method
|
27
|
+
# @param block [Proc] block with code to be added as method
|
28
|
+
# @param options [Hash] Options of construction
|
29
|
+
# @option options cached [Boolean] Flag telling to create
|
30
|
+
# a method with cache
|
31
|
+
def initialize(name, code = nil, **options, &block)
|
32
|
+
@name = name
|
33
|
+
@code = code
|
34
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
35
|
+
@block = block
|
27
36
|
end
|
28
37
|
|
29
38
|
# Adds the method to given klass
|
30
39
|
#
|
31
40
|
# @param klass [Class] class which will receive the new method
|
32
41
|
#
|
42
|
+
# @example Using string method with no options
|
43
|
+
# class MyModel
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# instance = MyModel.new
|
47
|
+
#
|
48
|
+
# method_definition = Sinclair::MethodDefinition.new(
|
49
|
+
# :sequence, '@x = @x.to_i ** 2 + 1'
|
50
|
+
# )
|
51
|
+
#
|
52
|
+
# method_definition.build(klass) # adds instance_method :sequence to
|
53
|
+
# # MyModel instances
|
54
|
+
#
|
55
|
+
# instance.instance_variable_get(:@x) # returns nil
|
56
|
+
#
|
57
|
+
# instance.sequence # returns 1
|
58
|
+
# instance.sequence # returns 2
|
59
|
+
# instance.sequence # returns 5
|
60
|
+
#
|
61
|
+
# instance.instance_variable_get(:@x) # returns 5
|
62
|
+
#
|
63
|
+
# @example Using string method with no options
|
64
|
+
# class MyModel
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# instance = MyModel.new
|
68
|
+
#
|
69
|
+
# method_definition = Sinclair::MethodDefinition.new(:sequence) do
|
70
|
+
# @x = @x.to_i ** 2 + 1
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# method_definition.build(klass) # adds instance_method :sequence to
|
74
|
+
# # MyModel instances
|
75
|
+
#
|
76
|
+
# instance.instance_variable_get(:@sequence) # returns nil
|
77
|
+
# instance.instance_variable_get(:@x) # returns nil
|
78
|
+
#
|
79
|
+
# instance.sequence # returns 1
|
80
|
+
# instance.sequence # returns 1 (cached value)
|
81
|
+
#
|
82
|
+
# instance.instance_variable_get(:@sequence) # returns 1
|
83
|
+
# instance.instance_variable_get(:@x) # returns 1
|
84
|
+
#
|
33
85
|
# @return [Symbol] name of the created method
|
34
86
|
def build(klass)
|
35
87
|
if code.is_a?(String)
|
@@ -43,6 +95,14 @@ class Sinclair
|
|
43
95
|
|
44
96
|
# @private
|
45
97
|
attr_reader :name, :code, :block
|
98
|
+
delegate :cached, to: :options_object
|
99
|
+
|
100
|
+
# @private
|
101
|
+
#
|
102
|
+
# Flag telling to use cached method
|
103
|
+
#
|
104
|
+
# @return [Boolean]
|
105
|
+
alias cached? cached
|
46
106
|
|
47
107
|
# @private
|
48
108
|
#
|
@@ -50,7 +110,27 @@ class Sinclair
|
|
50
110
|
#
|
51
111
|
# @return [Symbol] name of the created method
|
52
112
|
def build_block_method(klass)
|
53
|
-
klass.send(:define_method, name,
|
113
|
+
klass.send(:define_method, name, method_block)
|
114
|
+
end
|
115
|
+
|
116
|
+
# @private
|
117
|
+
#
|
118
|
+
# Returns the block that will be used for method creattion
|
119
|
+
#
|
120
|
+
# @return [Proc]
|
121
|
+
def method_block
|
122
|
+
return block unless cached?
|
123
|
+
|
124
|
+
inner_block = block
|
125
|
+
method_name = name
|
126
|
+
|
127
|
+
proc do
|
128
|
+
instance_variable_get("@#{method_name}") ||
|
129
|
+
instance_variable_set(
|
130
|
+
"@#{method_name}",
|
131
|
+
instance_eval(&inner_block)
|
132
|
+
)
|
133
|
+
end
|
54
134
|
end
|
55
135
|
|
56
136
|
# @private
|
@@ -68,9 +148,10 @@ class Sinclair
|
|
68
148
|
#
|
69
149
|
# @return [String]
|
70
150
|
def code_definition
|
151
|
+
code_line = cached? ? "@#{name} ||= #{code}" : code
|
71
152
|
<<-CODE
|
72
153
|
def #{name}
|
73
|
-
#{
|
154
|
+
#{code_line}
|
74
155
|
end
|
75
156
|
CODE
|
76
157
|
end
|
data/lib/sinclair/version.rb
CHANGED
@@ -3,10 +3,11 @@
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
describe MyClass do
|
6
|
-
subject { klass.new(attributes) }
|
7
|
-
|
8
|
-
let(:
|
9
|
-
let(:
|
6
|
+
subject(:model) { klass.new(attributes) }
|
7
|
+
|
8
|
+
let(:klass) { described_class }
|
9
|
+
let(:name) { 'name' }
|
10
|
+
let(:age) { 20 }
|
10
11
|
let(:attributes) do
|
11
12
|
{
|
12
13
|
name: name,
|
@@ -18,34 +19,36 @@ describe MyClass do
|
|
18
19
|
|
19
20
|
%i[name surname age legs].each do |field|
|
20
21
|
it do
|
21
|
-
expect(
|
22
|
+
expect(model).to respond_to(field)
|
22
23
|
end
|
23
24
|
|
24
25
|
it do
|
25
|
-
expect(
|
26
|
+
expect(model).to respond_to("#{field}_valid?")
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
29
30
|
it do
|
30
|
-
expect(
|
31
|
+
expect(model).to respond_to(:valid?)
|
31
32
|
end
|
32
33
|
|
33
34
|
describe '#valid?' do
|
34
35
|
it do
|
35
|
-
expect(
|
36
|
+
expect(model).to be_valid
|
36
37
|
end
|
37
38
|
|
38
39
|
context 'when a string attribute is a symbol' do
|
39
40
|
let(:name) { :name }
|
41
|
+
|
40
42
|
it do
|
41
|
-
expect(
|
43
|
+
expect(model).not_to be_valid
|
42
44
|
end
|
43
45
|
end
|
44
46
|
|
45
47
|
context 'when an attribute is nil' do
|
46
48
|
let(:age) { nil }
|
49
|
+
|
47
50
|
it do
|
48
|
-
expect(
|
51
|
+
expect(model).not_to be_valid
|
49
52
|
end
|
50
53
|
end
|
51
54
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe MyModel do
|
6
|
+
subject(:model) { klass.new }
|
7
|
+
|
8
|
+
let(:klass) { Class.new(described_class) }
|
9
|
+
let(:builder) { Sinclair.new(klass) }
|
10
|
+
|
11
|
+
before do
|
12
|
+
klass.send(:attr_accessor, :base, :expoent)
|
13
|
+
|
14
|
+
builder.add_method(:cached_power, cached: true) do
|
15
|
+
base**expoent
|
16
|
+
end
|
17
|
+
|
18
|
+
builder.build
|
19
|
+
|
20
|
+
model.base = 3
|
21
|
+
model.expoent = 2
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'caches the result of the method' do
|
25
|
+
expect { model.expoent = 3 }
|
26
|
+
.not_to change(model, :cached_power)
|
27
|
+
expect(model.cached_power).to eq(9)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Sinclair::Matchers do
|
6
|
+
let(:klass) { Class.new }
|
7
|
+
let(:method) { :the_method }
|
8
|
+
let(:value) { Random.rand(100) }
|
9
|
+
let(:builder_class) { DefaultValue }
|
10
|
+
let(:builder) { builder_class.new(klass, method, value) }
|
11
|
+
let(:instance) { klass.new }
|
12
|
+
|
13
|
+
context 'when the builder runs' do
|
14
|
+
it do
|
15
|
+
expect { builder.build }.to add_method(method).to(instance)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when the builder runs' do
|
20
|
+
it do
|
21
|
+
expect { builder.build }.to add_method(method).to(klass)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Sinclair do
|
6
|
+
describe 'Stand Alone' do
|
7
|
+
let(:instance) { klass.new }
|
8
|
+
let(:klass) { Class.new }
|
9
|
+
let(:builder) { described_class.new(klass) }
|
10
|
+
|
11
|
+
before do
|
12
|
+
builder.add_method(:twenty, '10 + 10')
|
13
|
+
builder.add_method(:eighty) { 4 * twenty }
|
14
|
+
builder.build
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'knows how to add string defined methods' do
|
18
|
+
expect("Twenty => #{instance.twenty}").to eq('Twenty => 20')
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'knows how to add block defined methods' do
|
22
|
+
expect("Eighty => #{instance.eighty}").to eq('Eighty => 80')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe
|
5
|
+
describe Sinclair::Matchers do
|
6
6
|
describe 'add_method_to' do
|
7
7
|
let(:method) { :the_method }
|
8
8
|
let(:klass) { Class.new }
|
@@ -83,15 +83,15 @@ describe 'matchers' do
|
|
83
83
|
expect { block.call }.to add_method(method)
|
84
84
|
end
|
85
85
|
let(:block) { proc { klass.send(:define_method, method) {} } }
|
86
|
+
let(:expected_error) do
|
87
|
+
'You should specify which instance the method is being added to' \
|
88
|
+
"add_method(:#{method}).to(instance)"
|
89
|
+
end
|
86
90
|
|
87
91
|
context 'when not calling to' do
|
88
92
|
it 'raises error' do
|
89
|
-
expect
|
90
|
-
|
91
|
-
end.to raise_error(
|
92
|
-
SyntaxError,
|
93
|
-
'You should specify which instance the method is being added to' \
|
94
|
-
"add_method(:#{method}).to(instance)"
|
93
|
+
expect { expectation }.to raise_error(
|
94
|
+
SyntaxError, expected_error
|
95
95
|
)
|
96
96
|
end
|
97
97
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe MyBuilder do
|
6
|
+
describe '#yard' do
|
7
|
+
subject(:builder) do
|
8
|
+
described_class.new(klass, rescue_error: true)
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:klass) { Class.new }
|
12
|
+
let(:instance) { klass.new }
|
13
|
+
|
14
|
+
describe '#build' do
|
15
|
+
before do
|
16
|
+
builder.add_methods
|
17
|
+
builder.build
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when calling built method' do
|
21
|
+
it 'returns default value' do
|
22
|
+
expect(instance.symbolize).to eq(:default)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -5,7 +5,7 @@ require 'spec_helper'
|
|
5
5
|
RSpec.describe Sinclair::Matchers::AddMethod do
|
6
6
|
describe 'yard' do
|
7
7
|
describe '#to' do
|
8
|
-
context 'checking against Class' do
|
8
|
+
context 'when checking against Class' do
|
9
9
|
let(:clazz) { Class.new }
|
10
10
|
let(:builder) { Sinclair.new(clazz) }
|
11
11
|
|
@@ -18,7 +18,7 @@ RSpec.describe Sinclair::Matchers::AddMethod do
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
context 'checking against instance' do
|
21
|
+
context 'when checking against instance' do
|
22
22
|
let(:clazz) { Class.new }
|
23
23
|
let(:builder) { Sinclair.new(clazz) }
|
24
24
|
let(:instance) { clazz.new }
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Sinclair::MethodDefinition do
|
4
|
+
describe 'yard' do
|
5
|
+
describe '#build' do
|
6
|
+
describe 'using string method with no options' do
|
7
|
+
subject(:method_definition) do
|
8
|
+
described_class.new(name, code)
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:klass) { Class.new }
|
12
|
+
let(:instance) { klass.new }
|
13
|
+
let(:code) { '@x = @x.to_i ** 2 + 1' }
|
14
|
+
let(:name) { :sequence }
|
15
|
+
|
16
|
+
it 'adds a dynamic method' do
|
17
|
+
expect { method_definition.build(klass) }.to add_method(name).to(instance)
|
18
|
+
expect { instance.sequence }
|
19
|
+
.to change { instance.instance_variable_get(:@x) }.from(nil).to 1
|
20
|
+
expect(instance.sequence).to eq(2)
|
21
|
+
expect(instance.sequence).to eq(5)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'changes instance variable' do
|
25
|
+
method_definition.build(klass)
|
26
|
+
|
27
|
+
expect { instance.sequence }
|
28
|
+
.to change { instance.instance_variable_get(:@x) }
|
29
|
+
.from(nil).to 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'using block with cache option' do
|
34
|
+
subject(:method_definition) do
|
35
|
+
described_class.new(name, cached: true) do
|
36
|
+
@x = @x.to_i**2 + 1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
let(:klass) { Class.new }
|
41
|
+
let(:instance) { klass.new }
|
42
|
+
let(:name) { :sequence }
|
43
|
+
|
44
|
+
it 'adds a dynamic method' do
|
45
|
+
expect { method_definition.build(klass) }.to add_method(name).to(instance)
|
46
|
+
expect { instance.sequence }
|
47
|
+
.to change { instance.instance_variable_get(:@x) }.from(nil).to 1
|
48
|
+
expect { instance.sequence }.not_to change(instance, :sequence)
|
49
|
+
expect(instance.instance_variable_get(:@sequence)).to eq(1)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|