sinclair 1.10.0 → 1.12.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/README.md +444 -324
  4. data/config/check_specs.yml +5 -5
  5. data/config/yardstick.yml +4 -0
  6. data/lib/sinclair/matchers/add_class_method.rb +0 -1
  7. data/lib/sinclair/method_builder/base.rb +32 -2
  8. data/lib/sinclair/method_builder/block_method_builder.rb +1 -12
  9. data/lib/sinclair/method_builder/call_method_builder.rb +10 -27
  10. data/lib/sinclair/method_builder/string_method_builder.rb +2 -29
  11. data/lib/sinclair/method_builder.rb +4 -20
  12. data/lib/sinclair/method_definition/block_definition.rb +2 -0
  13. data/lib/sinclair/method_definition/call_definition.rb +15 -18
  14. data/lib/sinclair/method_definition/parameter_builder.rb +89 -0
  15. data/lib/sinclair/method_definition/parameter_helper.rb +124 -0
  16. data/lib/sinclair/method_definition/string_definition.rb +26 -2
  17. data/lib/sinclair/method_definition.rb +42 -3
  18. data/lib/sinclair/method_definitions.rb +20 -22
  19. data/lib/sinclair/version.rb +1 -1
  20. data/lib/sinclair.rb +149 -62
  21. data/spec/integration/readme/sinclair/types_of_definition_spec.rb +61 -0
  22. data/spec/integration/yard/sinclair/add_class_method_spec.rb +51 -0
  23. data/spec/integration/yard/sinclair/add_method_spec.rb +60 -0
  24. data/spec/integration/yard/sinclair/eval_and_add_method_spec.rb +26 -0
  25. data/spec/integration/yard/sinclair_spec.rb +0 -83
  26. data/spec/lib/sinclair/method_builder/base_spec.rb +15 -0
  27. data/spec/lib/sinclair/method_builder/string_method_builder_spec.rb +24 -1
  28. data/spec/lib/sinclair/method_definition/call_definition_spec.rb +14 -17
  29. data/spec/lib/sinclair/method_definition/parameter_builder_spec.rb +81 -0
  30. data/spec/lib/sinclair/method_definition/string_definition_spec.rb +60 -29
  31. data/spec/lib/sinclair/method_definition_spec.rb +77 -2
  32. data/spec/lib/sinclair/method_definitions_spec.rb +15 -16
  33. data/spec/lib/sinclair_spec.rb +6 -160
  34. data/spec/support/models/dummy_builder.rb +5 -1
  35. data/spec/support/models/dummy_class_builder.rb +4 -0
  36. data/spec/support/models/person.rb +1 -1
  37. data/spec/support/shared_examples/sinclair.rb +118 -0
  38. metadata +11 -2
@@ -10,27 +10,24 @@ describe Sinclair::MethodDefinition::CallDefinition do
10
10
  let(:call_name) { :method_call }
11
11
  let(:attributes) { %i[key1 value2] }
12
12
 
13
- describe '#code_string' do
14
- let(:expected) { 'method_call :key1, :value2' }
15
-
16
- it 'returns the code string' do
17
- expect(definition.code_string)
18
- .to eq(expected)
13
+ describe '#code_block' do
14
+ let(:instance) { klass.new }
15
+ let(:klass) do
16
+ Class.new do
17
+ def method_call(*args)
18
+ args
19
+ end
20
+ end
19
21
  end
20
- end
21
22
 
22
- describe '#class_code_string' do
23
- let(:expected) do
24
- <<-RUBY
25
- class << self
26
- method_call :key1, :value2
27
- end
28
- RUBY
23
+ it do
24
+ expect(definition.code_block)
25
+ .to be_a(Proc)
29
26
  end
30
27
 
31
- it 'returns the code string' do
32
- expect(definition.class_code_string.gsub(/^ */, ''))
33
- .to eq(expected.gsub(/^ */, ''))
28
+ it 'returns a proc with the method call' do
29
+ expect(instance.instance_eval(&definition.code_block))
30
+ .to eq(attributes)
34
31
  end
35
32
  end
36
33
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Sinclair::MethodDefinition::ParameterBuilder do
6
+ describe '.from' do
7
+ let(:parameters) { nil }
8
+ let(:named_parameters) { nil }
9
+
10
+ context 'when parameters and named_parameters are nil' do
11
+ it do
12
+ expect(described_class.from(parameters, named_parameters))
13
+ .to eq('')
14
+ end
15
+ end
16
+
17
+ context 'when parameters is empty' do
18
+ let(:parameters) { [] }
19
+
20
+ it do
21
+ expect(described_class.from(parameters, named_parameters))
22
+ .to eq('')
23
+ end
24
+ end
25
+
26
+ context 'when named_parameters is empty' do
27
+ let(:named_parameters) { [] }
28
+
29
+ it do
30
+ expect(described_class.from(parameters, named_parameters))
31
+ .to eq('')
32
+ end
33
+ end
34
+
35
+ context 'when parameters has no default values' do
36
+ let(:parameters) { %i[x y] }
37
+
38
+ it do
39
+ expect(described_class.from(parameters, named_parameters))
40
+ .to eq('(x, y)')
41
+ end
42
+ end
43
+
44
+ context 'when named_parameters has no default values' do
45
+ let(:named_parameters) { %i[x y] }
46
+
47
+ it do
48
+ expect(described_class.from(parameters, named_parameters))
49
+ .to eq('(x:, y:)')
50
+ end
51
+ end
52
+
53
+ context 'when parameters has only default values' do
54
+ let(:parameters) { [{ x: 1, y: 3 }] }
55
+
56
+ it do
57
+ expect(described_class.from(parameters, named_parameters))
58
+ .to eq('(x = 1, y = 3)')
59
+ end
60
+ end
61
+
62
+ context 'when named parameters has only default values' do
63
+ let(:named_parameters) { [{ x: 1, y: 3 }] }
64
+
65
+ it do
66
+ expect(described_class.from(parameters, named_parameters))
67
+ .to eq('(x: 1, y: 3)')
68
+ end
69
+ end
70
+
71
+ context 'when all options are present' do
72
+ let(:parameters) { [:x, { y: 2 }] }
73
+ let(:named_parameters) { [:a, { b: 3 }] }
74
+
75
+ it do
76
+ expect(described_class.from(parameters, named_parameters))
77
+ .to eq('(x, y = 2, a:, b: 3)')
78
+ end
79
+ end
80
+ end
81
+ end
@@ -11,59 +11,90 @@ describe Sinclair::MethodDefinition::StringDefinition do
11
11
  let(:code) { 'Random.rand' }
12
12
  let(:options) { {} }
13
13
 
14
- describe '#code_line' do
15
- let(:klass) { Class.new }
16
- let(:instance) { klass.new }
17
- let(:code_line) { definition.code_line }
18
-
19
- it 'returns the code' do
20
- expect(definition.code_line).to eq(code)
14
+ describe '#code_definition' do
15
+ let(:klass) { Class.new }
16
+ let(:instance) { klass.new }
17
+ let(:code_definition) { definition.code_definition }
18
+ let(:expected_code) do
19
+ <<-CODE
20
+ def #{method_name}
21
+ #{code}
22
+ end
23
+ CODE
21
24
  end
22
25
 
23
26
  it 'returns a code with no cache' do
24
- expect(instance.instance_eval(code_line))
25
- .not_to eq(instance.instance_eval(code_line))
27
+ expect(definition.code_definition.gsub(/^ */, ''))
28
+ .to eq(expected_code.gsub(/^ */, ''))
26
29
  end
27
30
 
28
- context 'when cache true is given' do
29
- let(:options) { { cached: true } }
31
+ context 'when parameters are given with defaults' do
32
+ let(:options) { { parameters: [:x, { y: 10 }] } }
33
+ let(:code) { 'x + y' }
34
+ let(:expected_code) do
35
+ <<-CODE
36
+ def #{method_name}(x, y = 10)
37
+ #{code}
38
+ end
39
+ CODE
40
+ end
30
41
 
31
42
  it 'returns the code with simple cache' do
32
- expect(definition.code_line)
33
- .to eq("@#{method_name} ||= #{code}")
43
+ expect(definition.code_definition.gsub(/^ */, ''))
44
+ .to eq(expected_code.gsub(/^ */, ''))
34
45
  end
46
+ end
35
47
 
36
- it 'returns a code with cache' do
37
- expect(instance.instance_eval(code_line))
38
- .to eq(instance.instance_eval(code_line))
48
+ context 'when parameters are given' do
49
+ let(:options) { { parameters: %i[x y] } }
50
+ let(:code) { 'x + y' }
51
+ let(:expected_code) do
52
+ <<-CODE
53
+ def #{method_name}(x, y)
54
+ #{code}
55
+ end
56
+ CODE
57
+ end
58
+
59
+ it 'returns the code with simple cache' do
60
+ expect(definition.code_definition.gsub(/^ */, ''))
61
+ .to eq(expected_code.gsub(/^ */, ''))
39
62
  end
63
+ end
40
64
 
41
- it 'returns a code that does not cache nil' do
42
- instance.instance_variable_set("@#{method_name}", nil)
65
+ context 'when cache true is given' do
66
+ let(:options) { { cached: true } }
67
+ let(:expected_code) do
68
+ <<-CODE
69
+ def #{method_name}
70
+ @#{method_name} ||= #{code}
71
+ end
72
+ CODE
73
+ end
43
74
 
44
- expect(instance.instance_eval(code_line)).not_to be_nil
75
+ it 'returns the code with simple cache' do
76
+ expect(definition.code_definition.gsub(/^ */, ''))
77
+ .to eq(expected_code.gsub(/^ */, ''))
45
78
  end
46
79
  end
47
80
 
48
81
  context 'when cache full is given' do
49
82
  let(:options) { { cached: :full } }
50
83
  let(:expected) do
51
- "defined?(@#{method_name}) ? @#{method_name} : (@#{method_name} = #{code})"
84
+ <<-CODE
85
+ def #{method_name}
86
+ defined?(@#{method_name}) ? @#{method_name} : (@#{method_name} = #{code})
87
+ end
88
+ CODE
52
89
  end
53
90
 
54
91
  it 'returns the code with full cache' do
55
- expect(definition.code_line).to eq(expected)
92
+ expect(definition.code_definition.gsub(/^ */, '')).to eq(expected.gsub(/^ */, ''))
56
93
  end
57
94
 
58
95
  it 'returns a code with cache' do
59
- expect(instance.instance_eval(code_line))
60
- .to eq(instance.instance_eval(code_line))
61
- end
62
-
63
- it 'returns a code that caches nil' do
64
- instance.instance_variable_set("@#{method_name}", nil)
65
-
66
- expect(instance.instance_eval(code_line)).to be_nil
96
+ expect(instance.instance_eval(code_definition))
97
+ .to eq(instance.instance_eval(code_definition))
67
98
  end
68
99
  end
69
100
  end
@@ -5,6 +5,58 @@ require 'spec_helper'
5
5
  describe Sinclair::MethodDefinition do
6
6
  let(:method_name) { :the_method }
7
7
 
8
+ describe '.build' do
9
+ subject(:definition) do
10
+ definition_class.new(method_name, **options)
11
+ end
12
+
13
+ let(:definition_class) { Class.new(described_class) }
14
+ let(:options) { {} }
15
+ let(:klass) { Class.new }
16
+ let(:type) { Sinclair::MethodBuilder::CLASS_METHOD }
17
+
18
+ context 'when the builder has not been defined' do
19
+ it do
20
+ expect { definition.build(klass, type) }
21
+ .to raise_error(NotImplementedError)
22
+ end
23
+ end
24
+
25
+ context 'when the builder has been defined' do
26
+ let(:definition_class) do
27
+ Class.new(described_class) do
28
+ attr_reader :name
29
+
30
+ def initialize(name)
31
+ @name = name
32
+ end
33
+
34
+ def code_definition
35
+ <<-CODE
36
+ def #{name}
37
+ 10
38
+ end
39
+ CODE
40
+ end
41
+ end
42
+ end
43
+
44
+ before do
45
+ definition_class.build_with(Sinclair::MethodBuilder::StringMethodBuilder)
46
+ end
47
+
48
+ it do
49
+ expect { definition.build(klass, type) }
50
+ .not_to raise_error
51
+ end
52
+
53
+ it 'builds the method using the builder' do
54
+ expect { definition.build(klass, type) }
55
+ .to add_class_method(method_name).to(klass)
56
+ end
57
+ end
58
+ end
59
+
8
60
  describe '.default_value' do
9
61
  subject(:klass) { Class.new(described_class) }
10
62
 
@@ -44,6 +96,8 @@ describe Sinclair::MethodDefinition do
44
96
  end
45
97
 
46
98
  describe '.for' do
99
+ let(:klass) { Class.new }
100
+
47
101
  context 'when there are no options nor block' do
48
102
  let(:type) { :call }
49
103
  let(:arguments) { %i[attr_reader some_attribute other_attribute] }
@@ -59,8 +113,29 @@ describe Sinclair::MethodDefinition do
59
113
  end
60
114
 
61
115
  it 'initializes it correctly' do
62
- expect(described_class.for(type, *arguments).code_string)
63
- .to eq('attr_reader :some_attribute, :other_attribute')
116
+ expect { klass.module_eval(&described_class.for(type, *arguments).code_block) }
117
+ .to add_method(:some_attribute).to(klass)
118
+ end
119
+ end
120
+
121
+ context 'when type is not given' do
122
+ let(:type) { nil }
123
+ let(:method_name) { :the_method }
124
+ let(:block) { proc { 10 } }
125
+
126
+ it do
127
+ expect(described_class.for(type, method_name, &block))
128
+ .to be_a(described_class)
129
+ end
130
+
131
+ it 'infers the definition from arguments' do
132
+ expect(described_class.for(type, method_name, &block))
133
+ .to be_a(described_class::BlockDefinition)
134
+ end
135
+
136
+ it 'initializes it correctly' do
137
+ expect(described_class.for(type, method_name, &block).name)
138
+ .to eq(method_name)
64
139
  end
65
140
  end
66
141
 
@@ -6,7 +6,8 @@ describe Sinclair::MethodDefinitions do
6
6
  subject(:definitions) { described_class.new }
7
7
 
8
8
  describe '#add' do
9
- let(:name) { :the_method }
9
+ let(:name) { :the_method }
10
+ let(:klass) { Class.new }
10
11
 
11
12
  context 'when passing block' do
12
13
  it 'returns the resulting array' do
@@ -41,31 +42,29 @@ describe Sinclair::MethodDefinitions do
41
42
  .to be_a(Sinclair::MethodDefinition::StringDefinition)
42
43
  end
43
44
  end
44
- end
45
45
 
46
- describe '#add_definition' do
47
46
  context 'when there are no options nor block' do
48
47
  let(:type) { :call }
49
48
  let(:arguments) { %i[attr_reader some_attribute other_attribute] }
50
49
 
51
50
  it do
52
- expect(definitions.add_definition(type, *arguments))
51
+ expect(definitions.add(*arguments, type: type))
53
52
  .to be_a(Array)
54
53
  end
55
54
 
56
55
  it 'creates a new definition' do
57
- expect(definitions.add_definition(type, *arguments).last)
56
+ expect(definitions.add(*arguments, type: type).last)
58
57
  .to be_a(Sinclair::MethodDefinition)
59
58
  end
60
59
 
61
60
  it 'creates a new definition of the chosen type' do
62
- expect(definitions.add_definition(type, *arguments).last)
61
+ expect(definitions.add(*arguments, type: type).last)
63
62
  .to be_a(Sinclair::MethodDefinition::CallDefinition)
64
63
  end
65
64
 
66
65
  it 'initializes it correctly' do
67
- expect(definitions.add_definition(type, *arguments).last.code_string)
68
- .to eq('attr_reader :some_attribute, :other_attribute')
66
+ expect { klass.module_eval(&definitions.add(*arguments, type: type).last.code_block) }
67
+ .to add_method(:some_attribute).to(klass)
69
68
  end
70
69
  end
71
70
 
@@ -75,22 +74,22 @@ describe Sinclair::MethodDefinitions do
75
74
  let(:block) { proc { 10 } }
76
75
 
77
76
  it do
78
- expect(definitions.add_definition(type, method_name, &block))
77
+ expect(definitions.add(type, method_name, &block))
79
78
  .to be_a(Array)
80
79
  end
81
80
 
82
81
  it 'creates a new definition' do
83
- expect(definitions.add_definition(type, method_name, &block).last)
82
+ expect(definitions.add(type, method_name, &block).last)
84
83
  .to be_a(Sinclair::MethodDefinition)
85
84
  end
86
85
 
87
86
  it 'creates a new definition of the chosen type' do
88
- expect(definitions.add_definition(type, method_name, &block).last)
87
+ expect(definitions.add(type, method_name, &block).last)
89
88
  .to be_a(Sinclair::MethodDefinition::BlockDefinition)
90
89
  end
91
90
 
92
91
  it 'initializes it correctly' do
93
- expect(definitions.add_definition(type, method_name, &block).last.name)
92
+ expect(definitions.add(method_name, type: type, &block).last.name)
94
93
  .to eq(method_name)
95
94
  end
96
95
  end
@@ -101,22 +100,22 @@ describe Sinclair::MethodDefinitions do
101
100
  let(:code) { '10' }
102
101
 
103
102
  it do
104
- expect(definitions.add_definition(type, method_name, code))
103
+ expect(definitions.add(method_name, code, type: type))
105
104
  .to be_a(Array)
106
105
  end
107
106
 
108
107
  it 'creates a new definition' do
109
- expect(definitions.add_definition(type, method_name, code).last)
108
+ expect(definitions.add(method_name, code, type: type).last)
110
109
  .to be_a(Sinclair::MethodDefinition)
111
110
  end
112
111
 
113
112
  it 'creates a new definition of the chosen type' do
114
- expect(definitions.add_definition(type, method_name, code).last)
113
+ expect(definitions.add(method_name, code, type: type).last)
115
114
  .to be_a(Sinclair::MethodDefinition::StringDefinition)
116
115
  end
117
116
 
118
117
  it 'initializes it correctly' do
119
- expect(definitions.add_definition(type, method_name, code).last.name)
118
+ expect(definitions.add(method_name, code, type: type).last.name)
120
119
  .to eq(method_name)
121
120
  end
122
121
  end
@@ -11,86 +11,15 @@ describe Sinclair do
11
11
  let(:builder_class) { described_class }
12
12
 
13
13
  describe '#add_method' do
14
- context 'when extending the class' do
15
- let(:builder_class) { described_class::DummyBuilder }
14
+ let(:object) { instance }
16
15
 
17
- before do
18
- builder.init
19
- builder.build
20
- end
21
-
22
- context 'when describing a method with block' do
23
- it 'creates a method with the block' do
24
- expect(instance.blocked).to eq(1)
25
- end
26
- end
27
-
28
- context 'when describing a method with string' do
29
- it 'creates a method using the string definition' do
30
- expect(instance.defined).to eq(1)
31
- expect(instance.defined).to eq(2)
32
- end
33
- end
34
-
35
- context 'when passing options' do
36
- let(:options) { { increment: 2 } }
37
-
38
- it 'parses the options' do
39
- expect(instance.defined).to eq(2)
40
- expect(instance.defined).to eq(4)
41
- end
42
- end
43
- end
44
-
45
- context 'when using the builder without extending' do
46
- context 'when declaring a method with a block' do
47
- before do
48
- builder.add_method(:blocked) { 1 }
49
- builder.add_method(:blocked) { 2 }
50
- builder.build
51
- end
52
-
53
- it 'respect the order of method addtion' do
54
- expect(instance.blocked).to eq(2)
55
- end
56
- end
57
-
58
- context 'when declaring a method string' do
59
- before do
60
- builder.add_method(:string, '1')
61
- builder.add_method(:string, '2')
62
- builder.build
63
- end
64
-
65
- it 'respect the order of method addtion' do
66
- expect(instance.string).to eq(2)
67
- end
68
- end
69
-
70
- context 'when declaring block and string' do
71
- before do
72
- builder.add_method(:value) { 1 }
73
- builder.add_method(:value, '2')
74
- builder.build
75
- end
76
-
77
- it 'respect the order of method addtion' do
78
- expect(instance.value).to eq(2)
79
- end
80
- end
16
+ it_behaves_like 'A sinclair builder', :instance
17
+ end
81
18
 
82
- context 'when declaring string and block' do
83
- before do
84
- builder.add_method(:value, '1')
85
- builder.add_method(:value) { 2 }
86
- builder.build
87
- end
19
+ describe '#add_class_method' do
20
+ let(:object) { dummy_class }
88
21
 
89
- it 'respect the order of method addtion' do
90
- expect(instance.value).to eq(2)
91
- end
92
- end
93
- end
22
+ it_behaves_like 'A sinclair builder', :class
94
23
  end
95
24
 
96
25
  describe '#eval_and_add_method' do
@@ -149,87 +78,4 @@ describe Sinclair do
149
78
  end
150
79
  end
151
80
  end
152
-
153
- describe '#add_class_method' do
154
- context 'when extending the class' do
155
- let(:builder_class) { described_class::DummyClassBuilder }
156
-
157
- before do
158
- builder.init
159
- builder.build
160
- end
161
-
162
- context 'when describing a method with block' do
163
- it 'creates a method with the block' do
164
- expect(dummy_class.blocked).to eq(1)
165
- end
166
- end
167
-
168
- context 'when describing a method with string' do
169
- it 'creates a method using the string definition' do
170
- expect(dummy_class.defined).to eq(1)
171
- expect(dummy_class.defined).to eq(2)
172
- end
173
- end
174
-
175
- context 'when passing options' do
176
- let(:options) { { increment: 2 } }
177
-
178
- it 'parses the options' do
179
- expect(dummy_class.defined).to eq(2)
180
- expect(dummy_class.defined).to eq(4)
181
- end
182
- end
183
- end
184
-
185
- context 'when using the builder without extending' do
186
- context 'when declaring a method with a block' do
187
- before do
188
- builder.add_class_method(:blocked) { 1 }
189
- builder.add_class_method(:blocked) { 2 }
190
- builder.build
191
- end
192
-
193
- it 'respect the order of method addtion' do
194
- expect(dummy_class.blocked).to eq(2)
195
- end
196
- end
197
-
198
- context 'when declaring a method string' do
199
- before do
200
- builder.add_class_method(:string, '1')
201
- builder.add_class_method(:string, '2')
202
- builder.build
203
- end
204
-
205
- it 'respect the order of method addtion' do
206
- expect(dummy_class.string).to eq(2)
207
- end
208
- end
209
-
210
- context 'when declaring block and string' do
211
- before do
212
- builder.add_class_method(:value) { 1 }
213
- builder.add_class_method(:value, '2')
214
- builder.build
215
- end
216
-
217
- it 'respect the order of method addtion' do
218
- expect(dummy_class.value).to eq(2)
219
- end
220
- end
221
-
222
- context 'when declaring string and block' do
223
- before do
224
- builder.add_class_method(:value, '1')
225
- builder.add_class_method(:value) { 2 }
226
- builder.build
227
- end
228
-
229
- it 'respect the order of method addtion' do
230
- expect(dummy_class.value).to eq(2)
231
- end
232
- end
233
- end
234
- end
235
81
  end
@@ -5,7 +5,11 @@ class Sinclair
5
5
  def init
6
6
  add_method(:blocked) { 1 }
7
7
  add_method(:defined, "@value = value + #{options_object&.increment || 1}")
8
- add_method(:value, '@value ||= 0')
8
+ add_method(:sum, 'x + y', parameters: %i[x y])
9
+ add_method(:value, cached: true) { 0 }
10
+ add_method(:type_block, type: :block) { 3 }
11
+ add_method(:type_string, '10', type: :string)
12
+ add_method(:attr_accessor, :some_attribute, type: :call)
9
13
  end
10
14
  end
11
15
  end
@@ -5,7 +5,11 @@ class Sinclair
5
5
  def init
6
6
  add_class_method(:blocked) { 1 }
7
7
  add_class_method(:defined, "@value = value + #{options_object&.increment || 1}")
8
+ add_class_method(:sum, 'x + y', parameters: %i[x y])
8
9
  add_class_method(:value, '@value ||= 0')
10
+ add_class_method(:type_block, type: :block) { 3 }
11
+ add_class_method(:type_string, '10', type: :string)
12
+ add_class_method(:attr_accessor, :some_attribute, type: :call)
9
13
  end
10
14
  end
11
15
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Person
4
- attr_reader :first_name, :last_name
4
+ attr_accessor :first_name, :last_name
5
5
 
6
6
  def initialize(first_name, last_name)
7
7
  @first_name = first_name