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.
@@ -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