finite_machine 0.11.3 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +564 -569
  4. data/Rakefile +5 -1
  5. data/benchmarks/memory_profile.rb +11 -0
  6. data/benchmarks/memory_usage.rb +16 -9
  7. data/finite_machine.gemspec +10 -3
  8. data/lib/finite_machine.rb +34 -46
  9. data/lib/finite_machine/async_call.rb +5 -21
  10. data/lib/finite_machine/callable.rb +4 -4
  11. data/lib/finite_machine/catchable.rb +4 -2
  12. data/lib/finite_machine/choice_merger.rb +19 -19
  13. data/lib/finite_machine/const.rb +16 -0
  14. data/lib/finite_machine/definition.rb +2 -2
  15. data/lib/finite_machine/dsl.rb +66 -149
  16. data/lib/finite_machine/env.rb +4 -2
  17. data/lib/finite_machine/event_definition.rb +7 -15
  18. data/lib/finite_machine/{events_chain.rb → events_map.rb} +39 -51
  19. data/lib/finite_machine/hook_event.rb +60 -61
  20. data/lib/finite_machine/hooks.rb +44 -36
  21. data/lib/finite_machine/listener.rb +2 -2
  22. data/lib/finite_machine/logger.rb +5 -4
  23. data/lib/finite_machine/message_queue.rb +39 -30
  24. data/lib/finite_machine/observer.rb +55 -37
  25. data/lib/finite_machine/safety.rb +12 -10
  26. data/lib/finite_machine/state_definition.rb +3 -5
  27. data/lib/finite_machine/state_machine.rb +83 -64
  28. data/lib/finite_machine/state_parser.rb +51 -79
  29. data/lib/finite_machine/subscribers.rb +1 -1
  30. data/lib/finite_machine/threadable.rb +3 -1
  31. data/lib/finite_machine/transition.rb +30 -31
  32. data/lib/finite_machine/transition_builder.rb +23 -32
  33. data/lib/finite_machine/transition_event.rb +12 -11
  34. data/lib/finite_machine/two_phase_lock.rb +3 -1
  35. data/lib/finite_machine/undefined_transition.rb +5 -6
  36. data/lib/finite_machine/version.rb +2 -2
  37. data/spec/integration/system_spec.rb +36 -38
  38. data/spec/performance/benchmark_spec.rb +13 -21
  39. data/spec/unit/alias_target_spec.rb +22 -41
  40. data/spec/unit/async_callbacks_spec.rb +8 -13
  41. data/spec/unit/auto_methods_spec.rb +44 -0
  42. data/spec/unit/callable/call_spec.rb +1 -3
  43. data/spec/unit/callbacks_spec.rb +372 -463
  44. data/spec/unit/can_spec.rb +13 -23
  45. data/spec/unit/cancel_callbacks_spec.rb +46 -0
  46. data/spec/unit/choice_spec.rb +105 -141
  47. data/spec/unit/define_spec.rb +31 -31
  48. data/spec/unit/definition_spec.rb +24 -41
  49. data/spec/unit/event_names_spec.rb +6 -10
  50. data/spec/unit/events_map/add_spec.rb +23 -0
  51. data/spec/unit/events_map/choice_transition_spec.rb +25 -0
  52. data/spec/unit/events_map/clear_spec.rb +13 -0
  53. data/spec/unit/events_map/events_spec.rb +16 -0
  54. data/spec/unit/events_map/inspect_spec.rb +22 -0
  55. data/spec/unit/{events_chain → events_map}/match_transition_spec.rb +12 -14
  56. data/spec/unit/{events_chain → events_map}/move_to_spec.rb +14 -17
  57. data/spec/unit/events_map/states_for_spec.rb +17 -0
  58. data/spec/unit/events_spec.rb +91 -160
  59. data/spec/unit/handlers_spec.rb +34 -66
  60. data/spec/unit/hook_event/any_state_or_event_spec.rb +13 -0
  61. data/spec/unit/hook_event/build_spec.rb +1 -3
  62. data/spec/unit/hook_event/eql_spec.rb +1 -3
  63. data/spec/unit/hook_event/initialize_spec.rb +2 -4
  64. data/spec/unit/hook_event/notify_spec.rb +2 -4
  65. data/spec/unit/hooks/clear_spec.rb +1 -1
  66. data/spec/unit/hooks/{call_spec.rb → find_spec.rb} +4 -9
  67. data/spec/unit/hooks/inspect_spec.rb +16 -8
  68. data/spec/unit/hooks/register_spec.rb +4 -9
  69. data/spec/unit/if_unless_spec.rb +76 -115
  70. data/spec/unit/initial_spec.rb +50 -82
  71. data/spec/unit/inspect_spec.rb +14 -9
  72. data/spec/unit/is_spec.rb +12 -18
  73. data/spec/unit/log_transitions_spec.rb +4 -10
  74. data/spec/unit/logger_spec.rb +1 -3
  75. data/spec/unit/{event_queue_spec.rb → message_queue_spec.rb} +15 -8
  76. data/spec/unit/new_spec.rb +50 -0
  77. data/spec/unit/respond_to_spec.rb +2 -6
  78. data/spec/unit/state_parser/parse_spec.rb +9 -12
  79. data/spec/unit/states_spec.rb +12 -18
  80. data/spec/unit/subscribers_spec.rb +1 -3
  81. data/spec/unit/target_spec.rb +60 -93
  82. data/spec/unit/terminated_spec.rb +15 -25
  83. data/spec/unit/transition/check_conditions_spec.rb +16 -15
  84. data/spec/unit/transition/inspect_spec.rb +6 -6
  85. data/spec/unit/transition/matches_spec.rb +5 -7
  86. data/spec/unit/transition/states_spec.rb +5 -7
  87. data/spec/unit/transition/to_state_spec.rb +5 -13
  88. data/spec/unit/trigger_spec.rb +5 -9
  89. data/spec/unit/undefined_transition/eql_spec.rb +1 -3
  90. metadata +86 -49
  91. data/.gitignore +0 -18
  92. data/.rspec +0 -5
  93. data/.travis.yml +0 -27
  94. data/Gemfile +0 -16
  95. data/assets/finite_machine_logo.png +0 -0
  96. data/lib/finite_machine/async_proxy.rb +0 -55
  97. data/spec/unit/async_events_spec.rb +0 -107
  98. data/spec/unit/events_chain/add_spec.rb +0 -25
  99. data/spec/unit/events_chain/cancel_transitions_spec.rb +0 -22
  100. data/spec/unit/events_chain/choice_transition_spec.rb +0 -28
  101. data/spec/unit/events_chain/clear_spec.rb +0 -15
  102. data/spec/unit/events_chain/events_spec.rb +0 -18
  103. data/spec/unit/events_chain/inspect_spec.rb +0 -24
  104. data/spec/unit/events_chain/states_for_spec.rb +0 -17
  105. data/spec/unit/hook_event/infer_default_name_spec.rb +0 -13
  106. data/spec/unit/state_parser/inspect_spec.rb +0 -25
@@ -1,6 +1,4 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
1
+ # frozen_string_literal: true
4
2
 
5
3
  RSpec.describe FiniteMachine, 'initial' do
6
4
 
@@ -15,23 +13,19 @@ RSpec.describe FiniteMachine, 'initial' do
15
13
  }
16
14
 
17
15
  it "defaults initial state to :none" do
18
- fsm = FiniteMachine.define do
19
- events {
20
- event :slow, :green => :yellow
21
- event :stop, :yellow => :red
22
- }
16
+ fsm = FiniteMachine.new do
17
+ event :slow, :green => :yellow
18
+ event :stop, :yellow => :red
23
19
  end
24
20
 
25
21
  expect(fsm.current).to eql(:none)
26
22
  end
27
23
 
28
24
  it "requires initial state transition from :none" do
29
- fsm = FiniteMachine.define do
30
- events {
31
- event :init, :none => :green
32
- event :slow, :green => :yellow
33
- event :stop, :yellow => :red
34
- }
25
+ fsm = FiniteMachine.new do
26
+ event :init, :none => :green
27
+ event :slow, :green => :yellow
28
+ event :stop, :yellow => :red
35
29
  end
36
30
 
37
31
  expect(fsm.current).to eql(:none)
@@ -41,40 +35,33 @@ RSpec.describe FiniteMachine, 'initial' do
41
35
 
42
36
  it "allows to specify inital state" do
43
37
  called = []
44
- fsm = FiniteMachine.define do
38
+ fsm = FiniteMachine.new do
45
39
  initial :green
46
40
 
47
- events {
48
- event :slow, :green => :yellow
49
- event :stop, :yellow => :red
50
- }
51
- callbacks {
52
- on_exit :none do |event| called << 'on_exit_none' end
53
- on_enter :green do |event| called << 'on_enter_green' end
54
- }
41
+ event :slow, :green => :yellow
42
+ event :stop, :yellow => :red
43
+
44
+ on_exit :none do |event| called << 'on_exit_none' end
45
+ on_enter :green do |event| called << 'on_enter_green' end
55
46
  end
56
47
  expect(fsm.current).to eql(:green)
57
48
  expect(called).to be_empty
58
49
  end
59
50
 
60
51
  it "allows to specify initial state through parameter" do
61
- fsm = FiniteMachine.define initial: :green do
62
- events {
63
- event :slow, :green => :yellow
64
- event :stop, :yellow => :red
65
- }
52
+ fsm = FiniteMachine.new initial: :green do
53
+ event :slow, :green => :yellow
54
+ event :stop, :yellow => :red
66
55
  end
67
56
  expect(fsm.current).to eql(:green)
68
57
  end
69
58
 
70
59
  it "allows to specify deferred inital state" do
71
- fsm = FiniteMachine.define do
60
+ fsm = FiniteMachine.new do
72
61
  initial :green, defer: true
73
62
 
74
- events {
75
- event :slow, :green => :yellow
76
- event :stop, :yellow => :red
77
- }
63
+ event :slow, :green => :yellow
64
+ event :stop, :yellow => :red
78
65
  end
79
66
 
80
67
  expect(fsm.current).to eql(:none)
@@ -84,25 +71,21 @@ RSpec.describe FiniteMachine, 'initial' do
84
71
 
85
72
  it "raises error when specyfying initial without state name" do
86
73
  expect {
87
- FiniteMachine.define do
74
+ FiniteMachine.new do
88
75
  initial defer: true
89
76
 
90
- events {
91
- event :slow, :green => :yellow
92
- event :stop, :yellow => :red
93
- }
77
+ event :slow, :green => :yellow
78
+ event :stop, :yellow => :red
94
79
  end
95
80
  }.to raise_error(FiniteMachine::MissingInitialStateError)
96
81
  end
97
82
 
98
83
  it "allows to specify inital start event" do
99
- fsm = FiniteMachine.define do
84
+ fsm = FiniteMachine.new do
100
85
  initial :green, event: :start
101
86
 
102
- events {
103
- event :slow, :green => :none
104
- event :stop, :yellow => :red
105
- }
87
+ event :slow, :green => :none
88
+ event :stop, :yellow => :red
106
89
  end
107
90
 
108
91
  expect(fsm.current).to eql(:green)
@@ -113,13 +96,11 @@ RSpec.describe FiniteMachine, 'initial' do
113
96
  end
114
97
 
115
98
  it "allows to specify deferred inital start event" do
116
- fsm = FiniteMachine.define do
99
+ fsm = FiniteMachine.new do
117
100
  initial :green, event: :start, defer: true
118
101
 
119
- events {
120
- event :slow, :green => :yellow
121
- event :stop, :yellow => :red
122
- }
102
+ event :slow, :green => :yellow
103
+ event :stop, :yellow => :red
123
104
  end
124
105
 
125
106
  expect(fsm.current).to eql(:none)
@@ -129,24 +110,21 @@ RSpec.describe FiniteMachine, 'initial' do
129
110
 
130
111
  it "evaluates initial state" do
131
112
  logger = DummyLogger.new
132
- fsm = FiniteMachine.define do
113
+ fsm = FiniteMachine.new do
133
114
  initial logger.level
134
115
 
135
- events {
136
- event :slow, :green => :none
137
- event :stop, :yellow => :red
138
- }
116
+ event :slow, :green => :none
117
+ event :stop, :yellow => :red
139
118
  end
140
119
  expect(fsm.current).to eql(:pending)
141
120
  end
142
121
 
143
122
  it "doesn't care about state type" do
144
- fsm = FiniteMachine.define do
123
+ fsm = FiniteMachine.new do
145
124
  initial 1
146
- events {
147
- event :a, 1 => 2
148
- event :b, 2 => 3
149
- }
125
+
126
+ event :a, 1 => 2
127
+ event :b, 2 => 3
150
128
  end
151
129
  expect(fsm.current).to eql(1)
152
130
  fsm.a
@@ -156,13 +134,11 @@ RSpec.describe FiniteMachine, 'initial' do
156
134
  end
157
135
 
158
136
  it "allows to retrieve initial state" do
159
- fsm = FiniteMachine.define do
137
+ fsm = FiniteMachine.new do
160
138
  initial :green
161
139
 
162
- events {
163
- event :slow, :green => :yellow
164
- event :stop, :yellow => :red
165
- }
140
+ event :slow, :green => :yellow
141
+ event :stop, :yellow => :red
166
142
  end
167
143
  expect(fsm.current).to eq(:green)
168
144
  expect(fsm.initial_state).to eq(:green)
@@ -172,13 +148,11 @@ RSpec.describe FiniteMachine, 'initial' do
172
148
  end
173
149
 
174
150
  it "allows to retrieve initial state for deferred" do
175
- fsm = FiniteMachine.define do
151
+ fsm = FiniteMachine.new do
176
152
  initial :green, defer: true
177
153
 
178
- events {
179
- event :slow, :green => :yellow
180
- event :stop, :yellow => :red
181
- }
154
+ event :slow, :green => :yellow
155
+ event :stop, :yellow => :red
182
156
  end
183
157
  expect(fsm.current).to eq(:none)
184
158
  expect(fsm.initial_state).to eq(:none)
@@ -189,15 +163,12 @@ RSpec.describe FiniteMachine, 'initial' do
189
163
 
190
164
  it "allows to trigger callbacks on initial with :silent option" do
191
165
  called = []
192
- fsm = FiniteMachine.define do
166
+ fsm = FiniteMachine.new do
193
167
  initial :green, silent: false
194
168
 
195
- events {
196
- event :slow, :green => :yellow
197
- }
198
- callbacks {
199
- on_enter :green do |event| called << 'on_enter_green' end
200
- }
169
+ event :slow, :green => :yellow
170
+
171
+ on_enter :green do |event| called << 'on_enter_green' end
201
172
  end
202
173
  expect(fsm.current).to eq(:green)
203
174
  expect(called).to eq(['on_enter_green'])
@@ -205,15 +176,12 @@ RSpec.describe FiniteMachine, 'initial' do
205
176
 
206
177
  it "allows to trigger callbacks on deferred initial state" do
207
178
  called = []
208
- fsm = FiniteMachine.define do
179
+ fsm = FiniteMachine.new do
209
180
  initial :green, silent: false, defer: true
210
181
 
211
- events {
212
- event :slow, :green => :yellow
213
- }
214
- callbacks {
215
- on_enter :green do |event| called << 'on_enter_green' end
216
- }
182
+ event :slow, :green => :yellow
183
+
184
+ on_enter :green do |event| called << 'on_enter_green' end
217
185
  end
218
186
  expect(fsm.current).to eq(:none)
219
187
  fsm.init
@@ -1,17 +1,22 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
1
+ # frozen_string_literal
4
2
 
5
3
  RSpec.describe FiniteMachine, '#inspect' do
6
4
  it "print useful information about state machine" do
7
- fsm = FiniteMachine.define do
5
+ fsm = FiniteMachine.new do
8
6
  initial :green
9
7
 
10
- events {
11
- event :slow, :green => :yellow
12
- event :stop, :yellow => :red
13
- }
8
+ event :slow, :green => :yellow
9
+ event :stop, :yellow => :red
14
10
  end
15
- expect(fsm.inspect).to match(/^<#FiniteMachine::StateMachine:0x#{fsm.object_id.to_s(16)} @states=\[:none, :green, :yellow, :red\], @events=\[:init, :slow, :stop\], @transitions=\[{:none=>:green}, {:green=>:yellow}, {:yellow=>:red}\]>$/)
11
+ inspected = fsm.inspect
12
+ expect(inspected).to match(/^<#FiniteMachine::StateMachine:0x#{fsm.object_id.to_s(16)} @states=\[.*\], @events=\[.*\], @transitions=\[.*\]>$/)
13
+
14
+ event_names = eval inspected[/events=\[(.*?)\]/]
15
+ states = eval inspected[/states=\[(.*?)\]/]
16
+ transitions = eval inspected[/transitions=\[(.*?)\]/]
17
+
18
+ expect(event_names).to match_array([:init, :slow, :stop])
19
+ expect(states).to match_array([:none, :green, :yellow, :red])
20
+ expect(transitions).to match_array([{:none => :green}, {:green => :yellow}, {:yellow => :red}])
16
21
  end
17
22
  end
@@ -1,19 +1,15 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine, 'is?' do
3
+ RSpec.describe FiniteMachine, '#is?' do
6
4
 
7
5
  it "allows to check if state is reachable" do
8
- fsm = FiniteMachine.define do
6
+ fsm = FiniteMachine.new do
9
7
  initial :green
10
8
 
11
- events {
12
- event :slow, :green => :yellow
13
- event :stop, :yellow => :red
14
- event :ready, :red => :yellow
15
- event :go, :yellow => :green
16
- }
9
+ event :slow, :green => :yellow
10
+ event :stop, :yellow => :red
11
+ event :ready, :red => :yellow
12
+ event :go, :yellow => :green
17
13
  end
18
14
 
19
15
  expect(fsm.current).to eql(:green)
@@ -32,15 +28,13 @@ RSpec.describe FiniteMachine, 'is?' do
32
28
  end
33
29
 
34
30
  it "defines helper methods to check current state" do
35
- fsm = FiniteMachine.define do
31
+ fsm = FiniteMachine.new do
36
32
  initial :green
37
33
 
38
- events {
39
- event :slow, :green => :yellow
40
- event :stop, :yellow => :red
41
- event :ready, :red => :yellow
42
- event :go, :yellow => :green
43
- }
34
+ event :slow, :green => :yellow
35
+ event :stop, :yellow => :red
36
+ event :ready, :red => :yellow
37
+ event :go, :yellow => :green
44
38
  end
45
39
  expect(fsm.current).to eql(:green)
46
40
 
@@ -1,8 +1,4 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe FiniteMachine, 'log_transitions' do
1
+ RSpec.describe FiniteMachine, ':log_transitions' do
6
2
  let(:output) { StringIO.new('', 'w+')}
7
3
 
8
4
  before { FiniteMachine.logger = ::Logger.new(output) }
@@ -10,13 +6,11 @@ RSpec.describe FiniteMachine, 'log_transitions' do
10
6
  after { FiniteMachine.logger = ::Logger.new($stderr) }
11
7
 
12
8
  it "logs transitions" do
13
- fsm = FiniteMachine.define log_transitions: true do
9
+ fsm = FiniteMachine.new log_transitions: true do
14
10
  initial :green
15
11
 
16
- events {
17
- event :slow, :green => :yellow
18
- event :stop, :yellow => :red
19
- }
12
+ event :slow, :green => :yellow
13
+ event :stop, :yellow => :red
20
14
  end
21
15
 
22
16
  fsm.slow
@@ -1,6 +1,4 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
1
+ # frozen_string_literal: true
4
2
 
5
3
  RSpec.describe FiniteMachine::Logger do
6
4
  let(:message) { 'error' }
@@ -1,55 +1,62 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe FiniteMachine::MessageQueue do
4
-
5
- subject(:event_queue) { described_class.new }
6
-
7
4
  it "dispatches all events" do
5
+ event_queue = FiniteMachine::MessageQueue.new
8
6
  event_queue.start
9
7
  called = []
10
8
  event1 = double(:event1, dispatch: called << 'event1_dispatched')
11
9
  event2 = double(:event2, dispatch: called << 'event2_dispatched')
10
+
12
11
  expect(event_queue.size).to be_zero
12
+
13
13
  event_queue << event1
14
14
  event_queue << event2
15
15
  event_queue.join(0.001)
16
+
16
17
  expect(called).to match_array(['event1_dispatched', 'event2_dispatched'])
18
+ event_queue.shutdown
17
19
  end
18
20
 
19
21
  it "logs error" do
22
+ event_queue = FiniteMachine::MessageQueue.new
20
23
  event_queue.start
21
24
  event = spy(:event)
22
25
  allow(event).to receive(:dispatch) { raise }
23
26
  expect(FiniteMachine::Logger).to receive(:error)
24
27
  event_queue << event
25
- event_queue.join(0.01)
28
+ event_queue.join(0.02)
26
29
  expect(event_queue).to be_empty
27
30
  end
28
31
 
29
32
  it "notifies listeners" do
33
+ event_queue = FiniteMachine::MessageQueue.new
30
34
  event_queue.start
31
35
  called = []
32
36
  event1 = double(:event1, dispatch: true)
33
37
  event2 = double(:event2, dispatch: true)
34
38
  event3 = double(:event3, dispatch: true)
35
39
  event_queue.subscribe(:listener1) { |event| called << event }
36
- event_queue << event1 << event2 << event3
40
+ event_queue << event1
41
+ event_queue << event2
42
+ event_queue << event3
37
43
  event_queue.join(0.02)
38
44
  event_queue.shutdown
39
45
  expect(called).to match_array([event1, event2, event3])
40
46
  end
41
47
 
42
48
  it "allows to shutdown event queue" do
49
+ event_queue = FiniteMachine::MessageQueue.new
43
50
  event_queue.start
44
51
  event1 = double(:event1, dispatch: true)
45
52
  event2 = double(:event2, dispatch: true)
46
53
  event3 = double(:event3, dispatch: true)
47
- expect(event_queue.alive?).to be(true)
54
+ expect(event_queue.running?).to be(true)
48
55
  event_queue << event1
49
56
  event_queue << event2
50
57
  event_queue.shutdown
51
58
  event_queue << event3
59
+ expect(event_queue.running?).to be(false)
52
60
  event_queue.join(0.001)
53
- expect(event_queue.alive?).to be(false)
54
61
  end
55
62
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FiniteMachine, '.new' do
4
+ context 'with block' do
5
+ it "creates system state machine" do
6
+ fsm = FiniteMachine.new do
7
+ initial :green
8
+
9
+ event :slow, :green => :yellow
10
+ event :stop, :yellow => :red
11
+ event :ready, :red => :yellow
12
+ event :go, :yellow => :green
13
+ end
14
+
15
+ expect(fsm.current).to eql(:green)
16
+
17
+ fsm.slow
18
+ expect(fsm.current).to eql(:yellow)
19
+ fsm.stop
20
+ expect(fsm.current).to eql(:red)
21
+ fsm.ready
22
+ expect(fsm.current).to eql(:yellow)
23
+ fsm.go
24
+ expect(fsm.current).to eql(:green)
25
+ end
26
+ end
27
+
28
+ context 'without block' do
29
+ it "creates state machine" do
30
+ called = []
31
+ fsm = FiniteMachine.new
32
+ fsm.initial(:green)
33
+ fsm.event(:slow, :green => :yellow)
34
+ fsm.event(:stop, :yellow => :red)
35
+ fsm.event(:ready,:red => :yellow)
36
+ fsm.event(:go, :yellow => :green)
37
+ fsm.on_enter(:yellow) { |event| called << 'on_enter_yellow' }
38
+ fsm.handle(FiniteMachine::InvalidStateError) { |exception|
39
+ called << 'error_handler'
40
+ }
41
+ fsm.init
42
+ expect(fsm.current).to eql(:green)
43
+ fsm.slow
44
+ expect(fsm.current).to eql(:yellow)
45
+ fsm.ready
46
+ expect(fsm.current).to eql(:yellow)
47
+ expect(called).to match_array(['on_enter_yellow', 'error_handler'])
48
+ end
49
+ end
50
+ end