sinclair 1.1.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # @overload initialize(name, &block)
18
+ # @example
19
+ # Sinclair::MethodDefinition.new(:name, '@name')
13
20
  #
14
- # @param name [String,Symbol] name of the method
15
- # @param code [String] code to be evaluated as method
16
- # @param block [Proc] block with code to be added as method
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
- # @example
22
- # Sinclair::Method.new(:name) { @name }
23
- def initialize(name, code = nil, &block)
24
- @name = name
25
- @code = code
26
- @block = block
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, block)
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
- #{code}
154
+ #{code_line}
74
155
  end
75
156
  CODE
76
157
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Sinclair
4
- VERSION = '1.1.3'
4
+ VERSION = '1.2.0'
5
5
  end
@@ -3,10 +3,11 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe MyClass do
6
- subject { klass.new(attributes) }
7
- let(:klass) { MyClass }
8
- let(:name) { 'name' }
9
- let(:age) { 20 }
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(subject).to respond_to(field)
22
+ expect(model).to respond_to(field)
22
23
  end
23
24
 
24
25
  it do
25
- expect(subject).to respond_to("#{field}_valid?")
26
+ expect(model).to respond_to("#{field}_valid?")
26
27
  end
27
28
  end
28
29
 
29
30
  it do
30
- expect(subject).to respond_to(:valid?)
31
+ expect(model).to respond_to(:valid?)
31
32
  end
32
33
 
33
34
  describe '#valid?' do
34
35
  it do
35
- expect(subject).to be_valid
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(subject).not_to be_valid
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(subject).not_to be_valid
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 'matchers' do
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 do
90
- expectation
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 }
@@ -2,8 +2,8 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
- describe 'yard' do
6
- describe Sinclair::Matchers::AddMethodTo do
5
+ describe Sinclair::Matchers::AddMethodTo do
6
+ describe 'yard' do
7
7
  let(:klass) { Class.new(MyModel) }
8
8
  let(:builder) { Sinclair.new(klass) }
9
9
 
@@ -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