statesman 12.0.0 → 12.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,4 +1,6 @@
1
+ <!-- markdownlint-disable no-inline-html first-line-heading -->
1
2
  <p align="center"><img src="https://user-images.githubusercontent.com/110275/106792848-96e4ee80-664e-11eb-8fd1-16ff24b41eb2.png" alt="Statesman" width="512"></p>
3
+ <!-- markdownlint-enable no-inline-html first-line-heading -->
2
4
 
3
5
  A statesmanlike state machine library.
4
6
 
@@ -8,7 +10,7 @@ For our policy on compatibility with Ruby and Rails versions, see [COMPATIBILITY
8
10
  [![CircleCI](https://circleci.com/gh/gocardless/statesman.svg?style=shield)](https://circleci.com/gh/gocardless/statesman)
9
11
  [![Code Climate](https://codeclimate.com/github/gocardless/statesman.svg)](https://codeclimate.com/github/gocardless/statesman)
10
12
  [![Gitter](https://badges.gitter.im/join.svg)](https://gitter.im/gocardless/statesman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
11
- [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=statesman&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=statesman&package-manager=bundler&version-scheme=semver)
13
+ [![SemVer](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=statesman&package-manager=bundler&version-scheme=semver&previous-version=11.0.0&new-version=12.0.0)](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=statesman&package-manager=bundler&version-scheme=semver&previous-version=11.0.0&new-version=12.0.0)
12
14
 
13
15
  Statesman is an opinionated state machine library designed to provide a robust
14
16
  audit trail and data integrity. It decouples the state machine logic from the
@@ -16,6 +18,7 @@ underlying model and allows for easy composition with one or more model classes.
16
18
 
17
19
  As such, the design of statesman is a little different from other state machine
18
20
  libraries:
21
+
19
22
  - State behaviour is defined in a separate, "state machine" class, rather than
20
23
  added directly onto a model. State machines are then instantiated with the model
21
24
  to which they should apply.
@@ -30,7 +33,7 @@ protection.
30
33
  To get started, just add Statesman to your `Gemfile`, and then run `bundle`:
31
34
 
32
35
  ```ruby
33
- gem 'statesman', '~> 10.0.0'
36
+ gem 'statesman', '~> 12.0.0'
34
37
  ```
35
38
 
36
39
  ## Usage
@@ -136,7 +139,7 @@ end
136
139
  class Circular
137
140
  include Statesman::Machine
138
141
  extend Template
139
-
142
+
140
143
  define_states
141
144
  define_transitions
142
145
  end
@@ -144,10 +147,10 @@ end
144
147
  class Linear
145
148
  include Statesman::Machine
146
149
  extend Template
147
-
150
+
148
151
  define_states
149
152
  define_transitions
150
-
153
+
151
154
  remove_transitions from: :c, to: :a
152
155
  end
153
156
 
@@ -179,7 +182,7 @@ end
179
182
  Generate the transition model:
180
183
 
181
184
  ```bash
182
- $ rails g statesman:active_record_transition Order OrderTransition
185
+ rails g statesman:active_record_transition Order OrderTransition
183
186
  ```
184
187
 
185
188
  Your transition class should
@@ -212,13 +215,14 @@ class Order < ActiveRecord::Base
212
215
  :transition_to!, :transition_to, :in_state?, to: :state_machine
213
216
  end
214
217
  ```
215
- #### Using PostgreSQL JSON column
218
+
219
+ ### Using PostgreSQL JSON column
216
220
 
217
221
  By default, Statesman uses `serialize` to store the metadata in JSON format.
218
222
  It is also possible to use the PostgreSQL JSON column if you are using Rails 4
219
223
  or 5. To do that
220
224
 
221
- * Change `metadata` column type in the transition model migration to `json` or `jsonb`
225
+ - Change `metadata` column type in the transition model migration to `json` or `jsonb`
222
226
 
223
227
  ```ruby
224
228
  # Before
@@ -229,7 +233,7 @@ or 5. To do that
229
233
  t.json :metadata, default: {}
230
234
  ```
231
235
 
232
- * Remove the `include Statesman::Adapters::ActiveRecordTransition` statement from
236
+ - Remove the `include Statesman::Adapters::ActiveRecordTransition` statement from
233
237
  your transition model. (If you want to customise your transition class's "updated
234
238
  timestamp column", as described above, you should define a
235
239
  `.updated_timestamp_column` method on your class and return the name of the column
@@ -238,63 +242,73 @@ or 5. To do that
238
242
 
239
243
  ## Configuration
240
244
 
241
- #### `storage_adapter`
245
+ ### `storage_adapter`
242
246
 
243
247
  ```ruby
244
248
  Statesman.configure do
245
249
  storage_adapter(Statesman::Adapters::ActiveRecord)
246
250
  end
247
251
  ```
252
+
248
253
  Statesman defaults to storing transitions in memory. If you're using rails, you
249
254
  can instead configure it to persist transitions to the database by using the
250
255
  ActiveRecord adapter.
251
256
 
252
257
  Statesman will fallback to memory unless you specify a transition_class when instantiating your state machine. This allows you to only persist transitions on certain state machines in your app.
253
258
 
254
-
255
259
  ## Class methods
256
260
 
257
- #### `Machine.state`
261
+ ### `Machine.state`
262
+
258
263
  ```ruby
259
264
  Machine.state(:some_state, initial: true)
260
265
  Machine.state(:another_state)
261
266
  ```
267
+
262
268
  Define a new state and optionally mark as the initial state.
263
269
 
264
- #### `Machine.transition`
270
+ ### `Machine.transition`
271
+
265
272
  ```ruby
266
273
  Machine.transition(from: :some_state, to: :another_state)
267
274
  ```
275
+
268
276
  Define a transition rule. Both method parameters are required, `to` can also be
269
277
  an array of states (`.transition(from: :some_state, to: [:another_state, :some_other_state])`).
270
278
 
271
- #### `Machine.guard_transition`
279
+ ### `Machine.guard_transition`
280
+
272
281
  ```ruby
273
282
  Machine.guard_transition(from: :some_state, to: :another_state) do |object|
274
283
  object.some_boolean?
275
284
  end
276
285
  ```
286
+
277
287
  Define a guard. `to` and `from` parameters are optional, a nil parameter means
278
288
  guard all transitions. The passed block should evaluate to a boolean and must
279
289
  be idempotent as it could be called many times. The guard will pass when it
280
290
  evaluates to a truthy value and fail when it evaluates to a falsey value (`nil` or `false`).
281
291
 
282
- #### `Machine.before_transition`
292
+ ### `Machine.before_transition`
293
+
283
294
  ```ruby
284
295
  Machine.before_transition(from: :some_state, to: :another_state) do |object|
285
296
  object.side_effect
286
297
  end
287
298
  ```
299
+
288
300
  Define a callback to run before a transition. `to` and `from` parameters are
289
301
  optional, a nil parameter means run before all transitions. This callback can
290
302
  have side-effects as it will only be run once immediately before the transition.
291
303
 
292
- #### `Machine.after_transition`
304
+ ### `Machine.after_transition`
305
+
293
306
  ```ruby
294
307
  Machine.after_transition(from: :some_state, to: :another_state) do |object, transition|
295
308
  object.side_effect
296
309
  end
297
310
  ```
311
+
298
312
  Define a callback to run after a successful transition. `to` and `from`
299
313
  parameters are optional, a nil parameter means run after all transitions. The
300
314
  model object and transition object are passed as arguments to the callback.
@@ -304,12 +318,14 @@ after the transition.
304
318
  If you specify `after_commit: true`, the callback will be executed once the
305
319
  transition has been committed to the database.
306
320
 
307
- #### `Machine.after_transition_failure`
321
+ ### `Machine.after_transition_failure`
322
+
308
323
  ```ruby
309
324
  Machine.after_transition_failure(from: :some_state, to: :another_state) do |object, exception|
310
325
  Logger.info("transition to #{exception.to} failed for #{object.id}")
311
326
  end
312
327
  ```
328
+
313
329
  Define a callback to run if `Statesman::TransitionFailedError` is raised
314
330
  during the execution of transition callbacks. `to` and `from`
315
331
  parameters are optional, a nil parameter means run after all transitions.
@@ -318,12 +334,14 @@ This is executed outside of the transaction wrapping other callbacks.
318
334
  If using `transition!` the exception is re-raised after these callbacks are
319
335
  executed.
320
336
 
321
- #### `Machine.after_guard_failure`
337
+ ### `Machine.after_guard_failure`
338
+
322
339
  ```ruby
323
340
  Machine.after_guard_failure(from: :some_state, to: :another_state) do |object, exception|
324
341
  Logger.info("guard failed during transition to #{exception.to} for #{object.id}")
325
342
  end
326
343
  ```
344
+
327
345
  Define a callback to run if `Statesman::GuardFailedError` is raised
328
346
  during the execution of guard callbacks. `to` and `from`
329
347
  parameters are optional, a nil parameter means run after all transitions.
@@ -332,29 +350,35 @@ This is executed outside of the transaction wrapping other callbacks.
332
350
  If using `transition!` the exception is re-raised after these callbacks are
333
351
  executed.
334
352
 
353
+ ### `Machine.new`
335
354
 
336
- #### `Machine.new`
337
355
  ```ruby
338
356
  my_machine = Machine.new(my_model, transition_class: MyTransitionModel)
339
357
  ```
358
+
340
359
  Initialize a new state machine instance. `my_model` is required. If using the
341
360
  ActiveRecord adapter `my_model` should have a `has_many` association with
342
361
  `MyTransitionModel`.
343
362
 
344
- #### `Machine.retry_conflicts`
363
+ ### `Machine.retry_conflicts`
364
+
345
365
  ```ruby
346
366
  Machine.retry_conflicts { instance.transition_to(:new_state) }
347
367
  ```
368
+
348
369
  Automatically retry the given block if a `TransitionConflictError` is raised.
349
370
  If you know you want to retry a transition if it fails due to a race condition
350
371
  call it from within this block. Takes an (optional) argument for the maximum
351
372
  number of retry attempts (defaults to 1).
352
373
 
353
- #### `Machine.states`
374
+ ### `Machine.states`
375
+
354
376
  Returns an array of all possible state names as strings.
355
377
 
356
- #### `Machine.successors`
378
+ ### `Machine.successors`
379
+
357
380
  Returns a hash of states and the states it is valid for them to transition to.
381
+
358
382
  ```ruby
359
383
  Machine.successors
360
384
 
@@ -368,100 +392,129 @@ Machine.successors
368
392
 
369
393
  ## Instance methods
370
394
 
371
- #### `Machine#current_state`
395
+ ### `Machine#current_state`
396
+
372
397
  Returns the current state based on existing transition objects.
373
398
 
374
399
  Takes an optional keyword argument to force a reload of data from the
375
400
  database.
376
401
  e.g `current_state(force_reload: true)`
377
402
 
378
- #### `Machine#in_state?(:state_1, :state_2, ...)`
403
+ ### `Machine#in_state?(:state_1, :state_2, ...)`
404
+
379
405
  Returns true if the machine is in any of the given states.
380
406
 
381
- #### `Machine#history`
407
+ ### `Machine#history`
408
+
382
409
  Returns a sorted array of all transition objects.
383
410
 
384
- #### `Machine#last_transition`
411
+ ### `Machine#last_transition`
412
+
385
413
  Returns the most recent transition object.
386
414
 
387
- #### `Machine#last_transition_to(:state)`
415
+ ### `Machine#last_transition_to(:state)`
416
+
388
417
  Returns the most recent transition object to a given state.
389
418
 
390
- #### `Machine#allowed_transitions`
419
+ ### `Machine#allowed_transitions`
420
+
391
421
  Returns an array of states you can `transition_to` from current state.
392
422
 
393
- #### `Machine#can_transition_to?(:state)`
423
+ ### `Machine#can_transition_to?(:state)`
424
+
394
425
  Returns true if the current state can transition to the passed state and all
395
426
  applicable guards pass.
396
427
 
397
- #### `Machine#transition_to!(:state)`
428
+ ### `Machine#transition_to!(:state)`
429
+
398
430
  Transition to the passed state, returning `true` on success. Raises
399
431
  `Statesman::GuardFailedError` or `Statesman::TransitionFailedError` on failure.
400
432
 
401
- #### `Machine#transition_to(:state)`
433
+ ### `Machine#transition_to(:state)`
434
+
402
435
  Transition to the passed state, returning `true` on success. Swallows all
403
436
  Statesman exceptions and returns false on failure. (NB. if your guard or
404
437
  callback code throws an exception, it will not be caught.)
405
438
 
406
-
407
439
  ## Errors
408
440
 
409
441
  ### Initialization errors
442
+
410
443
  These errors are raised when the Machine and/or Model is initialized. A simple spec like
444
+
411
445
  ```ruby
412
446
  expect { OrderStateMachine.new(Order.new, transition_class: OrderTransition) }.to_not raise_error
413
447
  ```
448
+
414
449
  will expose these errors as part of your test suite
415
450
 
416
451
  #### InvalidStateError
452
+
417
453
  Raised if:
418
- * Attempting to define a transition without a `to` state.
419
- * Attempting to define a transition with a non-existent state.
420
- * Attempting to define multiple states as `initial`.
454
+
455
+ - Attempting to define a transition without a `to` state.
456
+ - Attempting to define a transition with a non-existent state.
457
+ - Attempting to define multiple states as `initial`.
421
458
 
422
459
  #### InvalidTransitionError
460
+
423
461
  Raised if:
424
- * Attempting to define a callback `from` a state that has no valid transitions (A terminal state).
425
- * Attempting to define a callback `to` the `initial` state if that state has no transitions to it.
426
- * Attempting to define a callback with `from` and `to` where any of the pairs have no transition between them.
462
+
463
+ - Attempting to define a callback `from` a state that has no valid transitions (A terminal state).
464
+ - Attempting to define a callback `to` the `initial` state if that state has no transitions to it.
465
+ - Attempting to define a callback with `from` and `to` where any of the pairs have no transition between them.
427
466
 
428
467
  #### InvalidCallbackError
468
+
429
469
  Raised if:
430
- * Attempting to define a callback without a block.
470
+
471
+ - Attempting to define a callback without a block.
431
472
 
432
473
  #### UnserializedMetadataError
474
+
433
475
  Raised if:
434
- * ActiveRecord is configured to not serialize the `metadata` attribute into
435
- to Database column backing it. See the `Using PostgreSQL JSON column` section.
476
+
477
+ - ActiveRecord is configured to not serialize the `metadata` attribute into
478
+ to Database column backing it. See the `Using PostgreSQL JSON column` section.
436
479
 
437
480
  #### IncompatibleSerializationError
481
+
438
482
  Raised if:
439
- * There is a mismatch between the column type of the `metadata` in the
440
- Database and the model. See the `Using PostgreSQL JSON column` section.
483
+
484
+ - There is a mismatch between the column type of the `metadata` in the
485
+ Database and the model. See the `Using PostgreSQL JSON column` section.
441
486
 
442
487
  #### MissingTransitionAssociation
488
+
443
489
  Raised if:
444
- * The model that `Statesman::Adapters::ActiveRecordQueries` is included in
445
- does not have a `has_many` association to the `transition_class`.
490
+
491
+ - The model that `Statesman::Adapters::ActiveRecordQueries` is included in
492
+ does not have a `has_many` association to the `transition_class`.
446
493
 
447
494
  ### Runtime errors
495
+
448
496
  These errors are raised by `transition_to!`. Using `transition_to` will
449
497
  supress `GuardFailedError` and `TransitionFailedError` and return `false` instead.
450
498
 
451
499
  #### GuardFailedError
500
+
452
501
  Raised if:
453
- * A guard callback between `from` and `to` state returned a falsey value.
502
+
503
+ - A guard callback between `from` and `to` state returned a falsey value.
454
504
 
455
505
  #### TransitionFailedError
506
+
456
507
  Raised if:
457
- * A transition is attempted but `current_state -> new_state` is not a valid pair.
508
+
509
+ - A transition is attempted but `current_state -> new_state` is not a valid pair.
458
510
 
459
511
  #### TransitionConflictError
512
+
460
513
  Raised if:
461
- * A database conflict affecting the `sort_key` or `most_recent` columns occurs
462
- when attempting a transition.
463
- Retried automatically if it occurs wrapped in `retry_conflicts`.
464
514
 
515
+ - A database conflict affecting the `sort_key` or `most_recent` columns occurs
516
+ when attempting a transition.
517
+ Retried automatically if it occurs wrapped in `retry_conflicts`.
465
518
 
466
519
  ## Model scopes
467
520
 
@@ -498,14 +551,16 @@ class Order < ActiveRecord::Base
498
551
  end
499
552
  ```
500
553
 
501
- #### `Model.in_state(:state_1, :state_2, etc)`
554
+ ### `Model.in_state(:state_1, :state_2, etc)`
555
+
502
556
  Returns all models currently in any of the supplied states.
503
557
 
504
- #### `Model.not_in_state(:state_1, :state_2, etc)`
558
+ ### `Model.not_in_state(:state_1, :state_2, etc)`
559
+
505
560
  Returns all models not currently in any of the supplied states.
506
561
 
562
+ ### `Model.most_recent_transition_join`
507
563
 
508
- #### `Model.most_recent_transition_join`
509
564
  This joins the model to its most recent transition whatever that may be.
510
565
  We expose this method to ease use of ActiveRecord's `or` e.g
511
566
 
@@ -517,7 +572,7 @@ Model.in_state(:state_1).or(
517
572
 
518
573
  ## Frequently Asked Questions
519
574
 
520
- #### Storing the state on the model object
575
+ ### Storing the state on the model object
521
576
 
522
577
  If you wish to store the model state on the model directly, you can keep it up
523
578
  to date using an `after_transition` hook.
@@ -533,7 +588,7 @@ end
533
588
 
534
589
  You could also use a calculated column or view in your database.
535
590
 
536
- #### Accessing metadata from the last transition
591
+ ### Accessing metadata from the last transition
537
592
 
538
593
  Given a field `foo` that was stored in the metadata, you can access it like so:
539
594
 
@@ -541,7 +596,7 @@ Given a field `foo` that was stored in the metadata, you can access it like so:
541
596
  model_instance.state_machine.last_transition.metadata["foo"]
542
597
  ```
543
598
 
544
- #### Events
599
+ ### Events
545
600
 
546
601
  Used to using a state machine with "events"? Support for events is provided by
547
602
  the [statesman-events](https://github.com/gocardless/statesman-events) gem. Once
@@ -557,31 +612,34 @@ class OrderStateMachine
557
612
  end
558
613
  ```
559
614
 
560
- #### Deleting records.
615
+ ### Deleting records
561
616
 
562
617
  If you need to delete the Parent model regularly you will need to change
563
618
  either the association deletion behaviour or add a `DELETE CASCADE` condition
564
619
  to foreign key in your database.
565
620
 
566
621
  E.g
567
- ```
622
+
623
+ ```ruby
568
624
  has_many :order_transitions, autosave: false, dependent: :destroy
569
625
  ```
626
+
570
627
  or when migrating the transition model
571
- ```
628
+
629
+ ```ruby
572
630
  add_foreign_key :order_transitions, :orders, on_delete: :cascade
573
631
  ```
574
632
 
575
-
576
633
  ## Testing Statesman Implementations
577
634
 
578
635
  This answer was abstracted from [this issue](https://github.com/gocardless/statesman/issues/77).
579
636
 
580
637
  At GoCardless we focus on testing that:
638
+
581
639
  - guards correctly prevent / allow transitions
582
640
  - callbacks execute when expected and perform the expected actions
583
641
 
584
- #### Testing Guards
642
+ ### Testing Guards
585
643
 
586
644
  Guards can be tested by asserting that `transition_to!` does or does not raise a `Statesman::GuardFailedError`:
587
645
 
@@ -597,7 +655,7 @@ describe "guards" do
597
655
  end
598
656
  ```
599
657
 
600
- #### Testing Callbacks
658
+ ### Testing Callbacks
601
659
 
602
660
  Callbacks are tested by asserting that the action they perform occurs:
603
661
 
@@ -4,11 +4,11 @@ Our goal as Statesman maintainers is for the library to be compatible with all s
4
4
 
5
5
  Specifically, any CRuby/MRI version that has not received an End of Life notice ([e.g. this notice for Ruby 2.1](https://www.ruby-lang.org/en/news/2017/04/01/support-of-ruby-2-1-has-ended/)) is supported. Similarly, any version of Rails listed as currently supported on [this page](http://guides.rubyonrails.org/maintenance_policy.html) is one we aim to support in Statesman.
6
6
 
7
- To that end, [our build matrix](../.circleci/config.yml) includes all these versions.
7
+ To that end, [our build matrix](../.github/workflows/tests.yml) includes all these versions.
8
8
 
9
9
  Any time Statesman doesn't work on a supported combination of Ruby and Rails, it's a bug, and can be reported [here](https://github.com/gocardless/statesman/issues).
10
10
 
11
- # Deprecation
11
+ ## Deprecation
12
12
 
13
13
  Whenever a version of Ruby or Rails falls out of support, we will mirror that change in Statesman by updating the build matrix and releasing a new major version.
14
14
 
@@ -39,7 +39,7 @@ module Statesman
39
39
  end
40
40
 
41
41
  def included(base)
42
- ensure_inheritance(base)
42
+ ensure_inheritance(base) if base.respond_to?(:subclasses) && base.subclasses.any?
43
43
 
44
44
  query_builder = QueryBuilder.new(base, **@args)
45
45
 
@@ -40,11 +40,11 @@ module Statesman
40
40
  end
41
41
 
42
42
  def matches_from_state(from, to)
43
- (from == self.from && (to.nil? || self.to.empty?))
43
+ from == self.from && (to.nil? || self.to.empty?)
44
44
  end
45
45
 
46
46
  def matches_to_state(from, to)
47
- ((from.nil? || self.from.nil?) && self.to.include?(to))
47
+ (from.nil? || self.from.nil?) && self.to.include?(to)
48
48
  end
49
49
 
50
50
  def matches_both_states(from, to)
@@ -27,7 +27,7 @@ module Statesman
27
27
  adapter_name = adapter_name(adapter_class)
28
28
  return false unless adapter_name
29
29
 
30
- adapter_name.downcase.start_with?("mysql")
30
+ adapter_name.downcase.start_with?("mysql", "trilogy")
31
31
  end
32
32
 
33
33
  def adapter_name(adapter_class)
@@ -233,12 +233,20 @@ module Statesman
233
233
  def initialize(object,
234
234
  options = {
235
235
  transition_class: Statesman::Adapters::MemoryTransition,
236
+ initial_transition: false,
236
237
  })
237
238
  @object = object
238
239
  @transition_class = options[:transition_class]
239
240
  @storage_adapter = adapter_class(@transition_class).new(
240
241
  @transition_class, object, self, options
241
242
  )
243
+
244
+ if options[:initial_transition]
245
+ if history.empty? && self.class.initial_state
246
+ @storage_adapter.create(nil, self.class.initial_state)
247
+ end
248
+ end
249
+
242
250
  send(:after_initialize) if respond_to? :after_initialize
243
251
  end
244
252
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Statesman
4
- VERSION = "12.0.0"
4
+ VERSION = "12.1.0"
5
5
  end
data/lib/statesman.rb CHANGED
@@ -6,7 +6,7 @@ module Statesman
6
6
  autoload :Callback, "statesman/callback"
7
7
  autoload :Guard, "statesman/guard"
8
8
  autoload :Utils, "statesman/utils"
9
- autoload :Version, "statesman/version"
9
+ autoload :VERSION, "statesman/version"
10
10
  module Adapters
11
11
  autoload :Memory, "statesman/adapters/memory"
12
12
  autoload :ActiveRecord, "statesman/adapters/active_record"
@@ -478,12 +478,83 @@ describe Statesman::Machine do
478
478
  it_behaves_like "a callback store", :after_guard_failure, :after_guard_failure
479
479
  end
480
480
 
481
+ shared_examples "initial transition is not created" do
482
+ it "doesn't call .create on storage adapter" do
483
+ expect_any_instance_of(Statesman.storage_adapter).to_not receive(:create)
484
+ machine.new(my_model, options)
485
+ end
486
+ end
487
+
488
+ shared_examples "initial transition is created" do
489
+ it "calls .create on storage adapter" do
490
+ expect_any_instance_of(Statesman.storage_adapter).to receive(:create).with(nil, "x")
491
+ machine.new(my_model, options)
492
+ end
493
+
494
+ it "creates a new transition object" do
495
+ instance = machine.new(my_model, options)
496
+
497
+ expect(instance.history.count).to eq(1)
498
+ expect(instance.history.first.to_state).to eq("x")
499
+ end
500
+ end
501
+
481
502
  describe "#initialize" do
482
503
  it "accepts an object to manipulate" do
483
504
  machine_instance = machine.new(my_model)
484
505
  expect(machine_instance.object).to be(my_model)
485
506
  end
486
507
 
508
+ context "initial_transition is not provided" do
509
+ let(:options) { {} }
510
+
511
+ it_behaves_like "initial transition is not created"
512
+ end
513
+
514
+ context "initial_transition is provided" do
515
+ context "initial_transition is true" do
516
+ let(:options) do
517
+ { initial_transition: true,
518
+ transition_class: Statesman::Adapters::MemoryTransition }
519
+ end
520
+
521
+ context "history is empty" do
522
+ context "initial state is defined" do
523
+ before { machine.state(:x, initial: true) }
524
+
525
+ it_behaves_like "initial transition is created"
526
+ end
527
+
528
+ context "initial state is not defined" do
529
+ it_behaves_like "initial transition is not created"
530
+ end
531
+ end
532
+
533
+ context "history is not empty" do
534
+ before do
535
+ allow_any_instance_of(Statesman.storage_adapter).to receive(:history).
536
+ and_return([{}])
537
+ end
538
+
539
+ context "initial state is defined" do
540
+ before { machine.state(:x, initial: true) }
541
+
542
+ it_behaves_like "initial transition is not created"
543
+ end
544
+
545
+ context "initial state is not defined" do
546
+ it_behaves_like "initial transition is not created"
547
+ end
548
+ end
549
+ end
550
+
551
+ context "initial_transition is false" do
552
+ let(:options) { { initial_transition: false } }
553
+
554
+ it_behaves_like "initial transition is not created"
555
+ end
556
+ end
557
+
487
558
  context "transition class" do
488
559
  it "sets a default" do
489
560
  expect(Statesman.storage_adapter).to receive(:new).once.