sinclair 1.1.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.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
|