flow 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5e98d3763503c8a061cbe3a1e4e84636d762d2f32fbf84c36965c806da76e99
4
- data.tar.gz: 5e3bea912c975a3201ef2d8774be2b92c992ccb575b21942026f0db91ea99f58
3
+ metadata.gz: 300011640bd90fa1ca5ac862e617d0bd297d4653680f03a518a71ee5dcc84e03
4
+ data.tar.gz: ca376a90cb8981bf509c90f4e1ed666add065cb8a952743904af0546b8844b40
5
5
  SHA512:
6
- metadata.gz: fd20f873b326ca51c19b802cebd4ff91c90c2ba3a4d07944c22a7ec24a7578db6482fa6da9b73b02fa67b3d1312c0a8d6f0874bf9873bd4e4e4d2e0ea06c2380
7
- data.tar.gz: f4175a7dbb1c5d03c39b9b24a9ac253b79fbe90fc238d1c9359d4045dc40f37294b3041d263131ca70681706bbac432062c58ce8d15163c3df70f2b4f9c3ba8b
6
+ metadata.gz: 5cb710f38dd83e6b7ddb44f2c90235f3aea87f4270e2938a8ef4325ff72e2e281592d92663b51036d95d2617897d9c9eb05932245aa8486ef317ea4cc48fb415
7
+ data.tar.gz: 98d96900e861b15e13a3f30364628d1f0c8c55584e9e34cb400cd84b5f93c754325166370729997d324054d43aa3b721dc810d734c13850e7833ec36e286d2de
data/README.md CHANGED
@@ -5,44 +5,1290 @@
5
5
  [![Maintainability](https://api.codeclimate.com/v1/badges/02131658005b10c289e0/maintainability)](https://codeclimate.com/github/Freshly/flow/maintainability)
6
6
  [![Test Coverage](https://api.codeclimate.com/v1/badges/02131658005b10c289e0/test_coverage)](https://codeclimate.com/github/Freshly/flow/test_coverage)
7
7
 
8
- Business logic is like nuclear fuel.
8
+ * [Installation](#installation)
9
+ * [Getting Started](#getting-started)
10
+ * [What is Flow?](#what-is-flow)
11
+ * [How it Works](#how-it-works)
12
+ * [Flows](#flows)
13
+ * [Operations](#operations)
14
+ * [States](#states)
15
+ * [Input](#input)
16
+ * [Mutable Data](#mutable-data)
17
+ * [Derivative Data](#derivative-data)
18
+ * [State Concerns](#state-concerns)
19
+ * [Validations](#validations)
20
+ * [Errors](#errors)
21
+ * [Exceptions](#exceptions)
22
+ * [Failures](#failures)
23
+ * [Callback Events](#callback-events)
24
+ * [Reverting a Flow](#reverting-a-flow)
25
+ * [Undoing Operations](#undoing-operations)
26
+ * [Manual Revert](#manual-revert)
27
+ * [Transactions](#transactions)
28
+ * [Around a Flow](#around-a-flow)
29
+ * [Around an Operation](#around-an-operation)
30
+ * [Statuses](#statuses)
31
+ * [Utilities](#utilities)
32
+ * [Callbacks](#callbacks)
33
+ * [Memoization](#memoization)
34
+ * [Logging](#logging)
35
+ * [Inheritance](#inheritance)
36
+ * [Testing](#testing)
37
+ * [Testing Setup](#testing-setup)
38
+ * [Testing Flows](#testing-flows)
39
+ * [Testing Operations](#testing-operations)
40
+ * [Testing States](#testing-states)
41
+ * [Integration Testing](#integration-testing)
42
+ * [Contributing](#contributing)
43
+ * [Development](#development)
44
+ * [License](#license)
9
45
 
10
- Managed properly, it's incredibly powerful and can do a lot of good.
46
+ ## Installation
11
47
 
12
- Managed improperly, and it turns your application into a superfund site.
48
+ Add this line to your application's Gemfile:
13
49
 
14
- Complexity is an endless river, and `flow` is how you harness it safely.
50
+ ```ruby
51
+ gem "flow"
52
+ ```
15
53
 
16
- ## Installation
54
+ Then, in your project directory:
17
55
 
18
- Add this line to your application's Gemfile:
56
+ ```bash
57
+ $ bundle install
58
+ $ rails generate flow:install
59
+ ```
60
+
61
+ ## Getting Started
62
+
63
+ Flow comes with some nice rails generators. You are encouraged to use them!
64
+
65
+ ```bash
66
+ $ rails generate flow Foo
67
+ invoke state
68
+ invoke rspec
69
+ create spec/states/foo_state_spec.rb
70
+ create app/states/foo_state.rb
71
+ invoke rspec
72
+ create spec/flows/foo_flow_spec.rb
73
+ create app/flows/foo_flow.rb
74
+ $ rails generate flow:state Bar
75
+ invoke rspec
76
+ create spec/states/bar_state_spec.rb
77
+ create app/states/bar_state.rb
78
+ $ rails generate flow:operation MakeTheThingDoTheStuff
79
+ invoke rspec
80
+ create spec/operations/make_the_thing_do_the_stuff_spec.rb
81
+ create app/operations/make_the_thing_do_the_stuff.rb
82
+ ```
83
+
84
+ ## What is Flow?
85
+
86
+ Flow is a [SOLID](https://en.wikipedia.org/wiki/SOLID) implementation of the [Command Pattern](https://en.wikipedia.org/wiki/Command_pattern) for Ruby on Rails.
87
+
88
+ Flows allow you to encapsulate your application's [business logic](http://en.wikipedia.org/wiki/Business_logic) into a set of extensible and reusable objects.
89
+
90
+ ## How it Works
91
+
92
+ ![Flow Basics](docs/images/flow.png)
93
+
94
+ There are three important concepts to distinguish here: [Flows](#Flows), [Operations](#Operations), and [States](#States).
95
+
96
+ ### Flows
97
+
98
+ A **Flow** is a collection of procedurally executed **Operations** sharing a common **State**.
99
+
100
+ All Flows should be named with the `Flow` suffix (ex: `FooFlow`).
19
101
 
20
102
  ```ruby
21
- gem 'flow'
103
+ class CalculateTimetablesFlow < ApplicationFlow
104
+ operations ClearExistingTimetables,
105
+ CalculateTimetables,
106
+ SummarizeTimetables,
107
+ DestroyEmptyTimetableCells
108
+ end
22
109
  ```
23
110
 
24
- And then execute:
111
+ The `operations` are an ordered list of the behaviors which are executed with (and possibly change) the Flow's state.
25
112
 
26
- $ bundle
113
+ Flows accept input representing the arguments and options which define the initial state.
27
114
 
28
- Or install it yourself as:
115
+ ```ruby
116
+ CalculateTimetablesFlow.trigger(timeframe: Day.today)
117
+ ```
29
118
 
30
- $ gem install flow
119
+ Triggering a Flow executes all its operations in sequential order if **and only if** it has a valid state.
31
120
 
32
- ## Usage
121
+ When `#trigger` is called on a Flow, `#execute` is called on Operations sequentially in their given order (referred to as a **flux**).
33
122
 
34
- TODO: Coming soon...
123
+ Unless otherwise specified a **Flow** assumes its state class shares a common name.
35
124
 
36
- ## Development
125
+ Ex: `FooBarBazFlow` assumes there is a defined `FooBarBazState`.
37
126
 
38
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
127
+ If you want to customize this behavior, define the state class explicitly:
39
128
 
40
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
129
+ ```ruby
130
+ class ExampleFlow < ApplicationState
131
+ def self.state_class
132
+ MyCoolState
133
+ end
134
+ end
135
+ ```
136
+
137
+ ### Operations
138
+
139
+ An **Operation** is a service object which is executed with a **State**.
140
+
141
+ Operations should **not** be named with the `Operation` suffix; name them what they do!
142
+
143
+ ```ruby
144
+ class ClearExistingTimetables < ApplicationOperation
145
+ def behavior
146
+ state.existing_timetable_cells.update_all(total_minutes: 0)
147
+ end
148
+ end
149
+ ```
150
+
151
+ ```ruby
152
+ class CalculateTimetables < ApplicationOperation
153
+ def behavior
154
+ state.minutes_by_project_employee.each do |project_employee, total_minutes|
155
+ project_id, employee_id = project_employee
156
+ timetable = state.timeframe.timetables.find_or_create_by!(project_id: project_id)
157
+ cell = timetable.cells.find_or_create_by!(employee_id: employee_id)
158
+
159
+ cell.update!(total_minutes: total_minutes)
160
+ end
161
+ end
162
+ end
163
+ ```
164
+
165
+ ```ruby
166
+ class SummarizeTimetables < ApplicationOperation
167
+ def behavior
168
+ state.timetables.each do |timetable|
169
+ timetable.update!(total_minutes: timetable.cells.sum(:total_minutes))
170
+ end
171
+ end
172
+ end
173
+ ```
174
+
175
+ ```ruby
176
+ class DestroyEmptyTimetableCells < ApplicationOperation
177
+ def behavior
178
+ state.empty_cells.destroy_all
179
+ end
180
+ end
181
+ ```
182
+
183
+ Operations take a state as input and define a `#behavior` that occurs when `#execute` is called.
184
+
185
+ 💁‍ *Pro Tip*: Operations are just objects! They can be used outside of Flows. Just give them a State (or a State-like object) and you can use them in isolation!
186
+
187
+ ```ruby
188
+ class ExampleOperation < ApplicationOperation
189
+ def behavior
190
+ puts "Hello, #{state.first_name}"
191
+ end
192
+ end
193
+
194
+ operation = ExampleOperation.new(OpenStruct.new(first_name: "Eric"))
195
+ operation.execute
196
+ # Hello, Eric
197
+ operation.executed? # => true
198
+ ```
199
+
200
+ ### States
201
+
202
+ A **State** is an aggregation of input and derived data.
203
+
204
+ All States should be named with the `State` suffix (ex: `FooState`).
205
+
206
+ ```ruby
207
+ class CalculateTimetablesState < ApplicationState
208
+ argument :timeframe
209
+
210
+ def existing_timetable_cells
211
+ @existing_timetable_cells ||= TimetableCell.where(timetable: existing_timetables)
212
+ end
213
+
214
+ def minutes_by_project_employee
215
+ @minutes_by_project_employee ||= data_by_employee_project.transform_values do |values|
216
+ values.sum(&:total_minutes)
217
+ end
218
+ end
219
+
220
+ def timetables
221
+ @timetables ||= Timetable.where(project_id: project_ids)
222
+ end
223
+
224
+ def empty_cells
225
+ @empty_cells ||= TimetableCell.
226
+ joins(:timetable).
227
+ where(total_minutes: 0, timetables: { project_id: project_ids })
228
+ end
229
+
230
+ private
231
+
232
+ delegate :timesheets, to: :timeframe
233
+
234
+ def existing_timetables
235
+ @existing_timetables ||= timeframe.timetables.where(project_id: project_ids)
236
+ end
237
+
238
+ def project_ids
239
+ @project_ids ||= timesheet_data.map(&:project_id).uniq
240
+ end
241
+
242
+ def data_by_employee_project
243
+ @data_by_employee_project ||= timesheet_data.group_by do |data|
244
+ [ data.project_id, data.employee_id ]
245
+ end
246
+ end
247
+
248
+ def timesheet_data
249
+ @timesheet_data ||= timesheets.
250
+ reportable.
251
+ summarizable.
252
+ joins(:timeclock).
253
+ select("timeclocks.project_id, timeclocks.employee_id, timesheets.total_minutes")
254
+ end
255
+ end
256
+ ```
257
+
258
+ #### Input
259
+
260
+ A state accepts input represented by **arguments** and **options** which initialize it.
261
+
262
+ **Arguments** describe input required to define the initial state.
263
+
264
+ If any arguments are missing, an `ArgumentError` is raised.
265
+
266
+ ```ruby
267
+ class ExampleFlow < ApplicationFlow; end
268
+ class ExampleState < ApplicationState
269
+ argument :foo
270
+ argument :bar
271
+ end
272
+
273
+ ExampleFlow.trigger # => ArgumentError (Missing arguments: foo, bar)
274
+ ExampleFlow.trigger(foo: :foo) # => ArgumentError (Missing argument: bar)
275
+ ExampleFlow.trigger(foo: :foo, bar: :bar) # => #<ExampleFlow:0x00007ff7b7d92ae0 ...>
276
+ ```
277
+
278
+ **Options** describe input which may be provided to define or override the initial state.
279
+
280
+ Options can optionally define a default value.
281
+
282
+ If no default is specified, the value will be `nil`.
283
+
284
+ If the default value is static, it can be specified in the class definition.
285
+
286
+ If the default value is dynamic, you may provide a block to compute the default value.
287
+
288
+ ⚠️‍ **Heads Up**: The default value blocks **DO NOT** provide access to the state or it's other variables!
289
+
290
+ ```ruby
291
+ class ExampleFlow < ApplicationFlow; end
292
+ class ExampleState < ApplicationState
293
+ option :attribution_source
294
+ option :favorite_foods, default: %w[pizza ice_cream gluten]
295
+ option(:favorite_color) { SecureRandom.hex(3) }
296
+ end
297
+
298
+ result = ExampleFlow.trigger(favorite_foods: %w[avocado hummus nutritional_yeast])
299
+ state = result.state
300
+
301
+ state.attribution_source # => nil
302
+ state.favorite_color # => "1a1f1e"
303
+ state.favorite_foods # => ["avocado", "hummus" ,"nutritional_yeast"]
304
+ ```
305
+
306
+ #### Mutable Data
307
+
308
+ States can define objects specifically to be populated by operations as they run.
309
+
310
+ Mutable operation data is not technically distinct from other operation data.
311
+
312
+ This section is really just a heads up that you can do things like this:
313
+
314
+ ```ruby
315
+ class ExampleState < ApplicationState
316
+ option :string_buffer, default: []
317
+ end
318
+
319
+ class AskAQuestion < ApplicationOperation
320
+ def behavior
321
+ state.string_buffer << "Bah Bah, Black Sheep. Have you any wool?"
322
+ end
323
+ end
324
+
325
+ class GiveAnAnswer < ApplicationOperation
326
+ def behavior
327
+ state.string_buffer << "Yes sir, yes sir! Three bags full!"
328
+ end
329
+ end
330
+
331
+ class ExampleFlow < ApplicationFlow
332
+ operations AskAQuestion, GiveAnAnswer
333
+ end
334
+
335
+ result = ExampleFlow.trigger(string_buffer: ["A conversation, for your consideration:"])
336
+ result.state.string_buffer.join("\n")
337
+ # A conversation, for your consideration:
338
+ # Bah Bah, Black Sheep. Have you any wool?
339
+ # Yes sir, yes sir! Three bags full!
340
+ ```
341
+
342
+ If you are planning to create some object during your operation at runtime, use `attribute`:
343
+
344
+ ```ruby
345
+ class ExampleState < ApplicationState
346
+ attribute :the_foo
347
+ end
348
+
349
+ class CreateFoo < ApplicationOperation
350
+ def behavior
351
+ state.the_foo = Foo.create!
352
+ end
353
+
354
+ def undo
355
+ state.the_foo.destroy!
356
+ state.the_foo = nil
357
+ end
358
+ end
359
+ ```
360
+
361
+ If your attribute should have a default value, you can use a hook to define that default:
362
+
363
+ ```ruby
364
+ class ExampleState < ApplicationState
365
+ attribute :the_foo
366
+ set_callback(:initialize, :after) { self.the_foo = [] }
367
+ end
368
+ ```
369
+
370
+ Under the hood `attribute` uses `attr_accessor` so you could override the default reader instead:
371
+
372
+ ```ruby
373
+ class ExampleState < ApplicationState
374
+ attribute :the_foo
375
+
376
+ def the_foo
377
+ @the_foo ||= []
378
+ end
379
+ end
380
+ ```
381
+
382
+ Use whatever method seems more readable to you or appropriate to your use case!
383
+
384
+ 💁‍ *Pro Tip*: You don't need to use `attribute` for mutable data, but you are highly encouraged to! If you do not use it, and opt instead of a simple `attr_accessor`, the value will not be output in the string.
385
+
386
+ ```ruby
387
+ class AccessorState < ApplicationState
388
+ attr_accessor :foo
389
+ end
390
+
391
+ class AttributeState < ApplicationState
392
+ attribute :foo
393
+ end
394
+
395
+ accr_state = AccessorState.new
396
+ accr_state.foo = "some value!"
397
+ accr_state.to_s # => #<AccessorState >
398
+ accr_state.foo # => "some value!"
399
+
400
+ attr_state = AttributeState.new
401
+ attr_state.foo = "some value!"
402
+ attr_state.to_s # => #<AttributeState foo="some value!">
403
+ attr_state.foo # => "some value!"
404
+ ```
405
+
406
+ #### Derivative Data
407
+
408
+ States provide you with a clear place to put any logic related to pre-processing of data.
409
+
410
+ They also can help give developers a clear picture of how your data fits together:
411
+
412
+ ```ruby
413
+ class ExampleState < ApplicationState
414
+ argument :user
415
+
416
+ def most_actionable_order
417
+ editable_orders.order(ship_date: :desc).first
418
+ end
419
+
420
+ private
421
+
422
+ def editable_orders
423
+ user.orders.unshipped.paid
424
+ end
425
+ end
426
+ ```
427
+
428
+ #### State Concerns
429
+
430
+ The architecture of each Flow having it's own state introduces a code reuse constraint.
431
+
432
+ Consider the following example:
433
+
434
+ ```ruby
435
+ class MyExampleState < ApplicationState
436
+ argument :user
437
+
438
+ def most_actionable_order
439
+ editable_orders.order(ship_date: :desc).first
440
+ end
441
+
442
+ private
443
+
444
+ def editable_orders
445
+ user.orders.unshipped.paid
446
+ end
447
+ end
448
+
449
+ class MyOtherExampleState < ApplicationState
450
+ argument :user
451
+
452
+ def least_actionable_order
453
+ editable_orders.order(ship_date: :desc).last
454
+ end
455
+
456
+ private
457
+
458
+ def editable_orders
459
+ user.orders.unshipped.paid
460
+ end
461
+ end
462
+ ```
463
+
464
+ The recommended way to share common code between your states is by using concerns.
465
+
466
+ For example, we could create `app/states/concerns/actionable_user_orders.rb`:
467
+
468
+ ```ruby
469
+ module ActionableUserOrders
470
+ extend ActiveSupport::Concern
471
+
472
+ included do
473
+ argument :user
474
+ end
475
+
476
+ private
477
+
478
+ def orders_by_ship_data
479
+ editable_orders.order(ship_date: :desc)
480
+ end
481
+
482
+ def editable_orders
483
+ user.orders.unshipped.paid
484
+ end
485
+ end
486
+ ```
487
+
488
+ Then your states become nice and clean:
489
+
490
+ ```ruby
491
+ class MyExampleState < ApplicationState
492
+ include ActionableUserOrders
493
+
494
+ def most_actionable_order
495
+ orders_by_ship_data.first
496
+ end
497
+ end
498
+
499
+ class MyOtherExampleState < ApplicationState
500
+ include ActionableUserOrders
501
+
502
+ def least_actionable_order
503
+ orders_by_ship_data.last
504
+ end
505
+ end
506
+ ```
507
+
508
+ #### Validations
509
+
510
+ States are ActiveModels which means they have access to [ActiveModel::Validations](https://api.rubyonrails.org/classes/ActiveModel/Validations.html).
511
+
512
+ It is considered a best practice to write validations in your states.
513
+
514
+ Flows which have an invalid state will NOT execute any Operations, so it is inherently the safest and clearest way to proactively communicate about missed expectations.
515
+
516
+ 💁‍ **Pro Tip**: There is a `trigger!` method on Flows that will raise certain errors that are normally silenced. Invalid states are one such example!
517
+
518
+ ```ruby
519
+ class ExampleFlow < ApplicationFlow; end
520
+ class ExampleState < ApplicationState
521
+ argument :first_name
522
+
523
+ validates :first_name, length: { minimum: 2 }
524
+ end
525
+
526
+ ExampleFlow.trigger!(first_name: "a") # => raises Flow::Errors::StateInvalid
527
+
528
+ result = ExampleFlow.trigger(first_name: "a")
529
+ result.success? # => false
530
+ result.failed? # => false
531
+ result.triggered? # => false
532
+ result.state.errors.messages # => {:first_name=>["is too short (minimum is 2 characters)"]}
533
+ ```
534
+
535
+ ## Errors
536
+
537
+ ![Flow Errors](docs/images/error.png)
538
+
539
+ When `#execute` is unsuccessful, expected problems are **failures** and unexpected problems are **Exceptions**.
540
+
541
+ Errors handling can be either either **proactive** or **reactive**; ideally all errors that can be are *proactive*.
542
+
543
+ **Proactive** error handling is a form of defensive programming. Instead of letting an error occur, you fail with a very clear signal as to why. Explicit failures are more desirable than than letting unexpected behavior dictate the program flow.
544
+
545
+ **Reactive** error handling should be used to handle areas of the code where you do not control the underlying behaviors, such as integrations with third party gems. When you know something you can't prevent could happen, you can define a reactive error handler to cleanly translate an *exception* into a *failure*.
546
+
547
+ ### Exceptions
548
+
549
+ When an exception is raised during during execution, but a handler can rescue, it causes a failure instead.
550
+
551
+ Otherwise, an unhandled exception will raise through both the Operation and Flow.
552
+ `
553
+ ```ruby
554
+ class ExampleState < ApplicationState
555
+ argument :number
556
+ end
557
+
558
+ class ExampleOperation < ApplicationOperation
559
+ handle_error RuntimeError
560
+
561
+ def behavior
562
+ raise (state.number % 2 == 0 ? StandardError : RuntimeError)
563
+ end
564
+ end
565
+
566
+ class ExampleFlow < ApplicationFlow
567
+ operations ExampleOperation
568
+ end
569
+
570
+ ExampleFlow.trigger(number: 0) # => raises StandardError
571
+ result = ExampleFlow.trigger(number: 1)
572
+ result.failed? # => true
573
+
574
+ operation_failure = result.failed_operation.operation_failure
575
+ operation_failure.problem # => :runtime_error
576
+ operation_failure.details.exception # => #<RuntimeError: RuntimeError>
577
+ ```
578
+
579
+ Handlers are inherited. They are searched from right to left, from bottom to top, and up the hierarchy. The handler of the first class for which exception.is_a?(klass) holds true is the one invoked, if any.
580
+
581
+ If no problem is specified explicitly, a demodulized underscored version of the error is used.
582
+
583
+ ```ruby
584
+ class ExampleOperation < ApplicationOperation
585
+ handle_error RuntimeError, problem: :something_bad_happened
586
+ handle_error ActiveRecord::RecordInvalid
587
+
588
+ def behavior
589
+ raise (state.number % 2 == 0 ? ActiveRecord::RecordInvalid : RuntimeError)
590
+ end
591
+ end
592
+
593
+ result0 = ExampleFlow.trigger(number: 0)
594
+ operation_failure = result0.failed_operation.operation_failure
595
+ operation_failure.problem # => :record_invalid
596
+ operation_failure.details.exception # => #<ActiveRecord::RecordInvalid: Record invalid>
597
+
598
+ result1 = ExampleFlow.trigger(number: 1)
599
+ result1.failed_operation.operation_failure.problem # => :something_bad_happened
600
+ ```
601
+
602
+ You can also provide handlers in the form of either a block or a method name:
603
+
604
+ ```ruby
605
+ class ExampleOperation < ApplicationOperation
606
+ handle_error RuntimeError, with: :handle_some_error
607
+ handle_error ActiveRecord::RecordInvalid do
608
+ # Do something here
609
+ end
610
+
611
+ def behavior
612
+ raise (state.number % 2 == 0 ? ActiveRecord::RecordInvalid : RuntimeError)
613
+ end
614
+
615
+ private
616
+
617
+ def handle_some_error
618
+ # Do something different here
619
+ end
620
+ end
621
+ ```
622
+
623
+ ### Failures
624
+
625
+ In theory, failures should *never* occur in your Flows. Any guard clause you can put inside of an Operation to proactively fail you should be able to put inside of the state as a validation.
626
+
627
+ In practice, failures will *always* occur in your Flows. Any sufficiently large organization will receive contributions from developers of all skill and business-specific knowledge levels. The suggested use of one State class per Flow means that if every state is responsible for proactive validation, you will eventually have a misstep and forget to include it.
628
+
629
+ Having your Operation proactively fail is an example of [contract programming](https://en.wikipedia.org/wiki/Design_by_contract) and provides developers with a clear and non-brittle expectation of how it should be used.
630
+
631
+ From a conceptual standpoint, you should consider your Operations as the most atomic expression of your business logic. Flows, and (by extension) the States that support them, are most effective when built up around a well defined set of Operations.
632
+
633
+ When your system has multiple consistent ways to defend against corrupt data or prevent executions that generate exceptions, it's robust not redundant.
634
+
635
+ `</rant>`
636
+
637
+ Failures are part of the class definition of your Operation.
638
+
639
+ ```ruby
640
+ class PassBottlesAround < ApplicationOperation
641
+ failure :too_generous
642
+
643
+ def behavior
644
+ too_generous_failure! if state.number_to_take_down >= 4
645
+ end
646
+ end
647
+ ```
648
+
649
+ When you define a failure a `#{failure_name}_failure!` method is defined for you.
650
+
651
+ Calling this `_failure!` method will raise an exception which Flow handles by default, meaning it will not be raised as an exception from the Flow.
652
+
653
+ An unstructured hash of data can be provided to the `_failure!` method and will be available in the `operation_failure` object:
654
+
655
+ ```ruby
656
+ class PassBottlesAround < ApplicationOperation
657
+ failure :too_generous
658
+
659
+ def behavior
660
+ if state.number_to_take_down >= 4
661
+ disappointment_level = state.number_to_take_down >= 10 ? :wow_very_disappoint : :am_disappoint
662
+ too_generous_failure!(disappointment_level: disappointment_level)
663
+ end
664
+ end
665
+ end
666
+
667
+ result5 = ExampleFlow.trigger(number_to_take_down: 5)
668
+ operation_failure5 = result5.failed_operation.operation_failure
669
+ operation_failure5.problem # => :too_generous
670
+ operation_failure5.details.disappointment_level # => :am_disappoint
671
+
672
+ result11 = ExampleFlow.trigger(number_to_take_down: 11)
673
+ operation_failure11 = result11.failed_operation.operation_failure
674
+ operation_failure11.problem # => :too_generous
675
+ operation_failure11.details.disappointment_level # => :wow_very_disappoint
676
+ ```
677
+
678
+ ### Callback Events
679
+
680
+ Operations feature error events which are triggered when a problem occurs.
681
+
682
+ This works for explictly defined failures:
683
+
684
+ ```ruby
685
+ class OperationOne < ApplicationOperation
686
+ failure :too_greedy
687
+
688
+ on_too_greedy_failure do
689
+ SlackClient.send_message(:engineering, "Someones trying to give away too much stuff!")
690
+ end
691
+ end
692
+ ```
693
+
694
+ As well as manually handled errors (using the demodulized underscored name of the error):
695
+
696
+ ```ruby
697
+ class OperationTwo < ApplicationOperation
698
+ handle_error ActiveRecord::RecordInvalid
699
+
700
+ on_record_invalid_failure do
701
+ Redis.incr("operation_two:invalid_records")
702
+ end
703
+ end
704
+ ```
705
+
706
+ You can also listen for any problems using the generic failure event:
707
+
708
+ ```ruby
709
+ class OperationThree < ApplicationOperation
710
+ handle_error RuntimeError
711
+
712
+ on_failure do
713
+ EngineeringMailer.on_runtime_error(self.class.name)
714
+ end
715
+ end
716
+ ```
717
+
718
+ ## Reverting a Flow
719
+
720
+ ![Flow Revert](docs/images/revert.png)
721
+
722
+ When something goes wrong in Flow `#revert` is called.
723
+
724
+ This calls `#rewind` on Operations to `#undo` their behavior.
725
+
726
+ Reverting a Flow rewinds all its executed operations in reverse order (referred to as an **ebb**).
727
+
728
+ Reverting is automatic and happens by default. You cannot opt out of the revert process, but you can choose to not define any `#undo` methods on your Operations.
729
+
730
+ ```ruby
731
+ class ExampleState < ApplicationState; end
732
+
733
+ class GenericOperation < ApplicationOperation
734
+ def behavior
735
+ puts "#{self.class.name}#behavior"
736
+ end
737
+
738
+ def undo
739
+ puts "#{self.class.name}#undo"
740
+ end
741
+ end
742
+
743
+ class ExampleFlow < ApplicationFlow
744
+ operations OperationOne, OperationTwo, OperationThree, OperationFour
745
+ end
746
+
747
+ class OperationOne < GenericOperation; end
748
+ class OperationTwo < GenericOperation; end
749
+ class OperationThree < GenericOperation
750
+ failure :bad_stuff
751
+
752
+ def behavior
753
+ super
754
+ bad_stuff_failure!
755
+ end
756
+ end
757
+ class OperationFour < GenericOperation; end
758
+
759
+ ExampleFlow.trigger
760
+
761
+ # Prints:
762
+ # OperationOne#behavior
763
+ # OperationTwo#behavior
764
+ # OperationThree#behavior
765
+ # OperationTwo#undo
766
+ # OperationOne#undo
767
+ ```
768
+
769
+ ⚠️ **Heads Up**: For the Operation that failed, `#undo` is **NOT** called. Only operations which execute successfully can be undone.
770
+
771
+ ### Undoing Operations
772
+
773
+ ```ruby
774
+ class ReserveQuantity < ApplicationOperation
775
+ delegate :product, :quantity, to: :state
776
+ delegate :available_inventory_count, to: :product
777
+
778
+ def behavior
779
+ product.update!(available_inventory_count: available_inventory_count - quantity)
780
+ end
781
+
782
+ def undo
783
+ product.update!(available_inventory_count: available_inventory_count + quantity)
784
+ end
785
+ end
786
+ ```
787
+
788
+ 💁‍ *Note*: If you omit the `#undo`, a revert will essentially pass over that Operation.
789
+
790
+ If your Operation should not be undone and you want it to halt reverting, call a defined failure in `#undo`.
791
+
792
+ ```ruby
793
+ class ExampleOperation < ApplicationOperation
794
+ failure :irreversible_behavior
795
+
796
+ def behavior
797
+ PurchaseService.charge_customer(state.customer)
798
+ end
799
+
800
+ def undo
801
+ irreversible_behavior_failure!
802
+ end
803
+ end
804
+ ```
805
+
806
+ ### Manual Revert
807
+
808
+ Flows in which an error occur are reverted automatically.
809
+
810
+ You can also manually revert a completed flow, even if it was fully successful.
811
+
812
+ ```ruby
813
+ class ExampleFlow < ApplicationFlow
814
+ operations OperationOne, OperationTwo, OperationThree, OperationFour
815
+ end
816
+
817
+ flow = SomeExampleFlow.trigger
818
+ # OperationOne#behavior
819
+ # OperationTwo#behavior
820
+ # OperationThree#behavior
821
+ # OperationFour#behavior
822
+ flow.success? # => true
823
+ flow.revert
824
+ # OperationFour#undo
825
+ # OperationThree#undo
826
+ # OperationTwo#undo
827
+ # OperationOne#undo
828
+ flow.reverted? # => true
829
+ ```
830
+
831
+ ## Transactions
832
+
833
+ ![Flow Transactions](docs/images/transaction.png)
834
+
835
+ Flow features a callback driven approach to wrap business logic within database transaction.
836
+
837
+ Both **Flows** and **Operations** can be wrapped with a transaction.
838
+
839
+ 🚨 *Be Aware*: Unless otherwise specified, transactions apply to **both** success **and** failure cases. You can pass `only:` or `except:` options to `wrap_in_transaction` to alter this behavior.
840
+
841
+ ### Around a Flow
842
+
843
+ Flows where no operation should be persisted unless all are successful should use a transaction.
844
+
845
+ ```ruby
846
+ class ExampleFlow < ApplicationFlow
847
+ wrap_in_transaction
848
+
849
+ operations OperationOne, OperationTwo, OperationThree
850
+ end
851
+ ```
852
+
853
+ Flows can transaction wrap `:flux` (caused by `#trigger`) or `:ebb` (caused by `#revert`).
854
+
855
+ ```ruby
856
+ class ExampleFlow < ApplicationFlow
857
+ wrap_in_transaction only: :flux
858
+ end
859
+
860
+ class ExampleFlow < ApplicationFlow
861
+ wrap_in_transaction except: :ebb
862
+ end
863
+ ```
864
+
865
+ ### Around an Operation
866
+
867
+ Operations which modify several persisted objects together should use a transaction.
868
+
869
+ ```ruby
870
+ class OperationTwo < ApplicationFlow
871
+ wrap_in_transaction
872
+
873
+ def behavior
874
+ # do a thing
875
+ end
876
+ end
877
+ ```
878
+
879
+ Operations can transaction wrap `:behavior` or `:undo`.
880
+
881
+ ```ruby
882
+ class ExampleOperation < ApplicationOperation
883
+ wrap_in_transaction only: :behavior
884
+ end
885
+
886
+ class ExampleOperation < ApplicationOperation
887
+ wrap_in_transaction except: :undo
888
+ end
889
+ ```
890
+
891
+ ## Statuses
892
+
893
+ Flows and Operations each have a set of predicate methods to describe their current status.
894
+
895
+ | Object | Status | Description |
896
+ | --------- | ------------ | ------------------------- |
897
+ | Operation | `executed?` | `#execute` was called. |
898
+ | Operation | `failed?` | Execution failed. |
899
+ | Operation | `success?` | Execution succeeded. |
900
+ | Flow | `pending?` | `#trigger` not called. |
901
+ | Flow | `triggered?` | `#trigger` was called. |
902
+ | Flow | `failed?` | Some operation failed. |
903
+ | Flow | `success?` | All operations succeeded. |
904
+ | Flow | `reverted?` | `#revert` was called. |
905
+
906
+ ## Utilities
907
+
908
+ Flow offers a number of utilities which allow you to tap into and extend it's functionality.
909
+
910
+ ### Callbacks
911
+
912
+ Flows, Operations, and States all make use of [ActiveSupport::Callbacks](https://api.rubyonrails.org/classes/ActiveSupport/Callbacks.html) to compose advanced functionality.
913
+
914
+ ```ruby
915
+ class TakeBottlesDown < OperationBase
916
+ set_callback(:execute, :before) { bottle_count_term }
917
+ set_callback(:execute, :after) { state.output.push("You take #{bottle_count_term} down.") }
918
+
919
+ def bottle_count_term
920
+ return "it" if state.bottles.number_on_the_wall == 1
921
+ return "one" if state.taking_down_one?
922
+
923
+ state.number_to_take_down
924
+ end
925
+ end
926
+ ```
927
+
928
+ Please consult the `ActiveSupport::Callbacks` documentation for guidance on how to use them.
929
+
930
+ The callbacks which are available on each class are:
931
+
932
+ | Class Name | Callbacks | Fired When... |
933
+ | ------------- | ------------- | -------------------------------------- |
934
+ | Flow | `:initialize` | When a new flow is being constructed. |
935
+ | Flow | `:trigger` | When `#trigger` is called on a flow. |
936
+ | Flow | `:flux` | When `#trigger` is called on a flow. |
937
+ | Flow | `:revert` | When `#revert` is called on a flow. |
938
+ | Flow | `:ebb` | When `#revert` is called on a flow. |
939
+ | State | `:initialize` | When a new state is being constructed. |
940
+ | Operation | `:execute` | When `#execute` is called. |
941
+ | Operation | `:behavior` | When `#execute` is called. |
942
+ | Operation | `:rewind` | When `#rewind` is called. |
943
+ | Operation | `:undo` | When `#rewind` is called. |
944
+ | Operation | `:failure` | When any type of error occurs. |
945
+ | Operation | `$problem` | When an error of type $problem occurs. |
946
+
947
+ ### Memoization
948
+
949
+ Flow includes the very awesome [ShortCircuIt](https://github.com/Freshly/spicerack/tree/develop/short_circu_it) gem.
950
+
951
+ To leverage it, just add `memoize :method_name` to your Flows, Operations, or States.
952
+
953
+ ```ruby
954
+ class TakeBottlesDown < OperationBase
955
+ def bottle_count_term
956
+ return "it" if state.bottles.number_on_the_wall == 1
957
+ return "one" if state.taking_down_one?
958
+
959
+ state.number_to_take_down
960
+ end
961
+ memoize :bottle_count_term
962
+ end
963
+ ```
964
+
965
+ Consult the documentation for `ShorCircuIt` for more info on how to use it.
966
+
967
+ ### Logging
968
+
969
+ Flow includes the [Technologic](https://github.com/Freshly/spicerack/tree/develop/technologic) gem.
970
+
971
+ The gems adds methods to Flows, Operations, and States which share names with log levels.
972
+
973
+ | Level | Used For |
974
+ | ------- | -------------------------------------- |
975
+ | `debug` | Extra data; usually off in production. |
976
+ | `info` | Standard data you always want to have. |
977
+ | `warn` | Unexpected (but not exceptional) data. |
978
+ | `error` | Exceptional cases representing issues. |
979
+ | `fatal` | Highly actionable and critical issues. |
980
+
981
+ ```ruby
982
+ class ExampleOperation < OperationBase
983
+ def behavior
984
+ warn(:nothing_to_do, { empty_object: obj }) and return if obj.empty?
985
+
986
+ debug(:doing_a_thing)
987
+
988
+ results = do_thing
989
+
990
+ log(:did_a_thing, results: results)
991
+ end
992
+ end
993
+ ```
994
+
995
+ Flows and States come with automated out-of-the-box logging.
996
+
997
+ The following is an example of what is logged without any extra log lines:
998
+
999
+ ```text
1000
+ I, [2019-03-06T12:31:06.008329 #25951] INFO -- : {:event=>"trigger_started.CalculateWorksheetsFlow"}
1001
+ I, [2019-03-06T12:31:06.008551 #25951] INFO -- : {:event=>"execute_started.AssignCommitsToWorksheet"}
1002
+ I, [2019-03-06T12:31:07.402005 #25951] INFO -- : {:event=>"execute_finished.AssignCommitsToWorksheet", :duration=>1.393346}
1003
+ I, [2019-03-06T12:31:07.402217 #25951] INFO -- : {:event=>"execute_started.AssignCommentsToWorksheet"}
1004
+ I, [2019-03-06T12:31:07.438144 #25951] INFO -- : {:event=>"execute_finished.AssignCommentsToWorksheet"}
1005
+ I, [2019-03-06T12:31:07.438235 #25951] INFO -- : {:event=>"trigger_finished.CalculateWorksheetsFlow", :duration=>1.429788}
1006
+ ```
1007
+
1008
+ Consult the documentation for `Technologic` for more info on how to use it.
1009
+
1010
+ ## Inheritance
1011
+
1012
+ Flows, Operations, and States all support inheritance of class definitions.
1013
+
1014
+ ```ruby
1015
+ class ParentState < ApplicationState
1016
+ argument :foo
1017
+ end
1018
+
1019
+ class ChildState < ParentState
1020
+ argument :bar
1021
+ end
1022
+
1023
+ class ChildFlow < ApplicationFlow; end
1024
+ ChildFlow.trigger(bar: :bar) # => ArgumentError (Missing argument: foo)
1025
+ ```
1026
+
1027
+ A common pattern in Flow is to use inheritance to DRY and conceptually related flows.
1028
+
1029
+ Take for example the case of `Calculation` and `Recalculation`
1030
+
1031
+ ```ruby
1032
+ class CalculateFooFlow < ApplicationFlow
1033
+ operations ClearOldFooCalculations, CaclulcateFoo, EmailFooReport
1034
+ end
1035
+
1036
+ class CalculateFooState < ApplicationState
1037
+ def foos
1038
+ Foo.where(calculated_at: nil)
1039
+ end
1040
+ end
1041
+
1042
+ # =================
1043
+
1044
+ class RecalculateFooFlow < CalculateFooFlow; end
1045
+ class RecalculateFooState < ApplicationState
1046
+ def foos
1047
+ Foo.all
1048
+ end
1049
+ end
1050
+ ```
1051
+
1052
+ There communicates that there is no difference between the Flows other than their States!
1053
+
1054
+ ## Testing
1055
+
1056
+ If you plan on writing `RSpec` tests `Flow` comes packaged with some custom matchers.
1057
+
1058
+ ### Testing Setup
1059
+
1060
+ Add the following to your `spec/rails_helper.rb` file:
1061
+
1062
+ ```ruby
1063
+ require "flow/spec_helper"
1064
+ ```
1065
+
1066
+ Flow works best with [shoulda-matchers](https://github.com/thoughtbot/shoulda-matchers) and [rspice](https://github.com/Freshly/spicerack/tree/develop/rspice).
1067
+
1068
+ Add those to the `development` and `test` group of your Gemfile:
1069
+
1070
+ ```ruby
1071
+ group :development, :test do
1072
+ gem "shoulda-matchers", git: "https://github.com/thoughtbot/shoulda-matchers.git", branch: "rails-5"
1073
+ gem "rspice"
1074
+ end
1075
+ ```
1076
+
1077
+ Then run `bundle install` and add the following into `spec/rails_helper.rb`:
1078
+
1079
+ ```ruby
1080
+ require "rspec/rails"
1081
+ require "rspice"
1082
+ require "flow/spec_helper"
1083
+
1084
+ # Configuration for the shoulda-matchers gem
1085
+ Shoulda::Matchers.configure do |config|
1086
+ config.integrate do |with|
1087
+ with.test_framework :rspec
1088
+ with.library :rails
1089
+ end
1090
+ end
1091
+ ```
1092
+
1093
+ This will allow you to use the following custom matchers:
1094
+ * [define_argument](lib/flow/custom_matchers/define_argument.rb)
1095
+ * [define_attribute](lib/flow/custom_matchers/define_attribute.rb)
1096
+ * [define_option](lib/flow/custom_matchers/define_option.rb)
1097
+ * [use_operations](lib/flow/custom_matchers/use_operations.rb)
1098
+
1099
+ ### Testing Flows
1100
+
1101
+ The best way to test a Flow is with an integration test.
1102
+
1103
+ The easiest way to test a Flow is with a unit test.
1104
+
1105
+ Flow are generated with the following RSPec template:
1106
+
1107
+ ```ruby
1108
+ # frozen_string_literal: true
1109
+
1110
+ require "rails_helper"
1111
+
1112
+ RSpec.describe FooFlow, type: :flow do
1113
+ subject(:flow) { described_class.new(**input) }
1114
+
1115
+ let(:input) do
1116
+ {}
1117
+ end
1118
+
1119
+ it { is_expected.to inherit_from ApplicationFlow }
1120
+ # it { is_expected.to use_operations ExampleOperation }
1121
+
1122
+ describe "#trigger" do
1123
+ subject(:trigger) { flow.trigger! }
1124
+
1125
+ pending "describe the effects of a successful `Flow#flux` (or delete) #{__FILE__}"
1126
+ end
1127
+
1128
+ describe "#revert" do
1129
+ before { flow.trigger! }
1130
+
1131
+ subject(:revert) { flow.revert }
1132
+
1133
+ pending "describe the effects of a successful `Flow#ebb` (or delete) #{__FILE__}"
1134
+ end
1135
+ end
1136
+ ```
1137
+
1138
+ ### Testing Operations
1139
+
1140
+ The easiest and best way to test an Operation is with a unit test.
1141
+
1142
+ Operation unit tests work best when you treat them like integration tests! (Read: **No Mocking!**)
1143
+
1144
+ Operations are generated with the following RSPec template:
1145
+
1146
+ ```ruby
1147
+ # frozen_string_literal: true
1148
+
1149
+ require "rails_helper"
1150
+
1151
+ RSpec.describe MakeTheThingDoTheStuff, type: :operation do
1152
+ subject(:operation) { described_class.new(state) }
1153
+
1154
+ let(:state) { example_state_class.new(**state_input) }
1155
+ let(:example_state_class) do
1156
+ Class.new(ApplicationState) do
1157
+ # argument :foo
1158
+ # option :bar
1159
+ end
1160
+ end
1161
+ let(:state_input) do
1162
+ {}
1163
+ end
1164
+
1165
+ it { is_expected.to inherit_from ApplicationOperation }
1166
+
1167
+ describe "#execute!" do
1168
+ subject(:execute!) { operation.execute! }
1169
+
1170
+ pending "describe `Operation#behavior` (or delete) #{__FILE__}"
1171
+ end
1172
+
1173
+ describe "#rewind" do
1174
+ before { operation.execute! }
1175
+
1176
+ subject(:execute!) { operation.rewind }
1177
+
1178
+ pending "describe `Operation#undo` (or delete) #{__FILE__}"
1179
+ end
1180
+ end
1181
+ ```
1182
+
1183
+ ⚠️ *Warning*: You have to do a little work to write a good test state!
1184
+
1185
+ In the boilerplate from the generator, there is the following snippet:
1186
+
1187
+ ```ruby
1188
+ let(:example_state_class) do
1189
+ Class.new(ApplicationState) do
1190
+ # argument :foo
1191
+ # option :bar
1192
+ end
1193
+ end
1194
+ ```
1195
+
1196
+ By default, your operation specs are broken! The reason for this is to encourage resilient test writing.
1197
+
1198
+ Let's say that you have an Operation in your system called `CreateFoo` which is part of the `CreateFooFlow` and therefore is only ever called with a `CreateFooState`. You may be tempted to write something like:
1199
+
1200
+ ```ruby
1201
+ let(:example_state_class) { CreateFooState }
1202
+ ```
1203
+
1204
+ You are heavily encouraged *not* to do that. If your Operation is used by several different Flows, you don't want to have your test arbitrarily using some state for the test.
1205
+
1206
+ Instead, use the spec as a way to communicate the contract of the Operation with the next developer. By boiling out a very clean example state that only includes what is necessary for the operation, you provide clear guidance on what the Operation's minimum requirements for a state are in a very transparent way.
1207
+
1208
+ ```ruby
1209
+ let(:example_state_class) do
1210
+ Class.new(ApplicationState) do
1211
+ argument :foo
1212
+ option :bar
1213
+ attribute :baz
1214
+ end
1215
+
1216
+ let(:state_input) do
1217
+ { foo: foo, bar: bar }
1218
+ end
1219
+
1220
+ let(:foo) { ... }
1221
+ let(:bar) { ... }
1222
+ end
1223
+ ```
1224
+
1225
+ ### Testing States
1226
+
1227
+ The easiest and best way to test a State is with a unit test.
1228
+
1229
+ States are generated with the following RSPec template:
1230
+
1231
+ ```ruby
1232
+ # frozen_string_literal: true
1233
+
1234
+ require "rails_helper"
1235
+
1236
+ RSpec.describe FooState, type: :state do
1237
+ subject(:state) { described_class.new(**input) }
1238
+
1239
+ let(:input) do
1240
+ {}
1241
+ end
1242
+
1243
+ it { is_expected.to inherit_from ApplicationState }
1244
+ # it { is_expected.to define_argument :foo }
1245
+ # it { is_expected.to define_option(:foo) }
1246
+ # it { is_expected.to define_option(:foo).with_default_value(:bar) }
1247
+ # it { is_expected.to define_option(:foo).with_default_value_block }
1248
+ # it { is_expected.to validate_presence_of ... }
1249
+ # it { is_expected.to define_attribute :foo }
1250
+ end
1251
+ ```
1252
+
1253
+ 💡 **Reminder**: You need to install `shoulda-matchers` to use things like `.to validate_presence_of ...`, `rspice` for `.to inherit_from ...`, and the `flow/spec_helper.rb` for `define_argument` and the like.
1254
+
1255
+ ### Integration Testing
1256
+
1257
+ The best integration tests are the simplest!
1258
+
1259
+ Create a state of the universe and confirm your flow changes it.
1260
+
1261
+ ```ruby
1262
+ describe "integration test" do
1263
+ subject(:flow) { ChangePreferencesFlow.trigger(user: user, favorite_food: new_favorite_food) }
1264
+
1265
+ let(:user) { create :user, favorite_food: original_favorite_food }
1266
+ let(:original_favorite_food) { Faker::Lorem.unique.word }
1267
+ let(:new_favorite_food) { Faker::Lorem.unique.word }
1268
+
1269
+ it "changes User#favorite_food" do
1270
+ expect { flow }.
1271
+ to change { user.favorite_food }.
1272
+ from(original_favorite_food).
1273
+ to(new_favorite_food)
1274
+ end
1275
+ end
1276
+ ```
1277
+
1278
+ 💡 *Note*: It's considered a best practice to put your integration test in the same file as the unit test.
1279
+
1280
+ And always remember: **Good integration tests don't use mocking**!
41
1281
 
42
1282
  ## Contributing
43
1283
 
44
1284
  Bug reports and pull requests are welcome on GitHub at https://github.com/freshly/flow.
45
1285
 
1286
+ ### Development
1287
+
1288
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
1289
+
1290
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
1291
+
46
1292
  ## License
47
1293
 
48
1294
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).