statesman 0.8.3 → 1.0.0.beta1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +111 -3
- data/lib/generators/statesman/templates/create_migration.rb.erb +3 -3
- data/lib/generators/statesman/templates/update_migration.rb.erb +3 -3
- data/lib/statesman/adapters/active_record.rb +5 -1
- data/lib/statesman/adapters/active_record_model.rb +32 -8
- data/lib/statesman/adapters/memory.rb +2 -0
- data/lib/statesman/adapters/mongoid.rb +2 -0
- data/lib/statesman/callback.rb +6 -6
- data/lib/statesman/machine.rb +34 -24
- data/lib/statesman/version.rb +1 -1
- data/spec/generators/statesman/migration_generator_spec.rb +1 -0
- data/spec/statesman/adapters/active_record_model_spec.rb +30 -7
- data/spec/statesman/adapters/active_record_spec.rb +3 -3
- data/spec/statesman/callback_spec.rb +6 -1
- data/spec/statesman/machine_spec.rb +69 -29
- data/spec/support/active_record.rb +16 -0
- data/spec/support/mongoid.rb +2 -0
- data/statesman.gemspec +5 -5
- metadata +15 -15
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 55f5eab4bdf1849056059ca216caa5412679967d
         | 
| 4 | 
            +
              data.tar.gz: 56d537766abea0884d94e4a55a9051896bbfc902
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: b2bead7fc506334da98f4ab620cb6df1fd49012a3a77c3accb8e779e8f09212e3b9926c5ea145ea7d0d3df2f69db13808d05bb65ec55fdfbb39be3ea55d4e915
         | 
| 7 | 
            +
              data.tar.gz: e9c8295a8eca68e22debdb594c4802bdd554c18b6a26b4e7d3940fed0673436bf5aa917b4b3a188191b867f6cb4532d08f9383e378d771752ca4b0e91eab1790
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,17 @@ | |
| 1 | 
            +
            ## v1.0.0.rc1 9 October 2014
         | 
| 2 | 
            +
            *Breaking changes*
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            - Classes which include `ActiveRecordModel` must define an `initial_state` class method.
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            *Fixes*
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            - `ActiveRecordModel.in_state` and `ActiveRecordModel.not_in_state` now handle inital states correctly (patch by [@isaacseymour](https://github.com/isaacseymour))
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            *Additions*
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            - Transition tables created by generated migrations have `NOT NULL` constraints on `to_state`, `sort_key` and foreign key columns (patch by [@greysteil](https://github.com/greysteil))
         | 
| 13 | 
            +
            - `before_transition` and `after_transition` allow an array of to states (patch by [@isaacseymour](https://github.com/isaacseymour))
         | 
| 14 | 
            +
             | 
| 1 15 | 
             
            ## v0.8.3 2 September 2014
         | 
| 2 16 | 
             
            *Fixes*
         | 
| 3 17 |  | 
    
        data/README.md
    CHANGED
    
    | @@ -331,7 +331,7 @@ callback code throws an exception, it will not be caught.) | |
| 331 331 |  | 
| 332 332 | 
             
            A mixin is provided for the ActiveRecord adapter which adds scopes to easily
         | 
| 333 333 | 
             
            find all models currently in (or not in) a given state. Include it into your
         | 
| 334 | 
            -
            model and define  | 
| 334 | 
            +
            model and define `transition_class` and `initial_state` class methods:
         | 
| 335 335 |  | 
| 336 336 | 
             
            ```ruby
         | 
| 337 337 | 
             
            class Order < ActiveRecord::Base
         | 
| @@ -342,14 +342,122 @@ class Order < ActiveRecord::Base | |
| 342 342 | 
             
              def self.transition_class
         | 
| 343 343 | 
             
                OrderTransition
         | 
| 344 344 | 
             
              end
         | 
| 345 | 
            +
             | 
| 346 | 
            +
              def self.initial_state
         | 
| 347 | 
            +
                OrderStateMachine.initial_state
         | 
| 348 | 
            +
              end
         | 
| 345 349 | 
             
            end
         | 
| 346 350 | 
             
            ```
         | 
| 347 351 |  | 
| 348 352 | 
             
            #### `Model.in_state(:state_1, :state_2, etc)`
         | 
| 349 | 
            -
            Returns all models currently in any of the supplied states.
         | 
| 353 | 
            +
            Returns all models currently in any of the supplied states. Prior to 1.0 this ignored all models in the initial state, and the `initial_state` class method was not required.
         | 
| 350 354 |  | 
| 351 355 | 
             
            #### `Model.not_in_state(:state_1, :state_2, etc)`
         | 
| 352 | 
            -
            Returns all models not currently in any of the supplied states.
         | 
| 356 | 
            +
            Returns all models not currently in any of the supplied states. Prior to 1.0 this always excluded models in the initial state, and the `initial_state` class method was not required.
         | 
| 357 | 
            +
             | 
| 358 | 
            +
            ## Frequently Asked Questions
         | 
| 359 | 
            +
             | 
| 360 | 
            +
            #### `Model.in_state` does not work before first transition
         | 
| 361 | 
            +
             | 
| 362 | 
            +
            This is expected. State is modelled as a series of tranisitions so initially where there has been no transition, there is no explict state. At GoCardless we get around this by transitioning immediately after creation:
         | 
| 363 | 
            +
             | 
| 364 | 
            +
            ```ruby
         | 
| 365 | 
            +
            after_create do
         | 
| 366 | 
            +
              state_machine.transition_to! "pending"
         | 
| 367 | 
            +
            end
         | 
| 368 | 
            +
            ```
         | 
| 369 | 
            +
             | 
| 370 | 
            +
            #### Storing the state on the model object
         | 
| 371 | 
            +
             | 
| 372 | 
            +
            If you wish to store the model state on the model directly, you can keep it up to date using an `after_transition` hook:
         | 
| 373 | 
            +
             | 
| 374 | 
            +
            ```ruby
         | 
| 375 | 
            +
            after_transition do |model, transition|
         | 
| 376 | 
            +
              model.state = transition.to_state
         | 
| 377 | 
            +
              model.save!
         | 
| 378 | 
            +
            end
         | 
| 379 | 
            +
            ```
         | 
| 380 | 
            +
             | 
| 381 | 
            +
            #### Accessing metadata from the last transition
         | 
| 382 | 
            +
             | 
| 383 | 
            +
            Given a field `foo` that was stored in the metadata, you can access it like so:
         | 
| 384 | 
            +
             | 
| 385 | 
            +
            ```ruby
         | 
| 386 | 
            +
            model_instance.last_transition.metadata["foo"]
         | 
| 387 | 
            +
            ```
         | 
| 388 | 
            +
             | 
| 389 | 
            +
            ## Testing Statesman Implementations
         | 
| 390 | 
            +
             | 
| 391 | 
            +
            This answer was abstracted from [this issue](https://github.com/gocardless/statesman/issues/77).
         | 
| 392 | 
            +
             | 
| 393 | 
            +
            At GoCardless we focus on testing that:
         | 
| 394 | 
            +
            - guards correctly prevent / allow transitions
         | 
| 395 | 
            +
            - callbacks execute when expected and perform the expected actions
         | 
| 396 | 
            +
             | 
| 397 | 
            +
            #### Testing Guards
         | 
| 398 | 
            +
             | 
| 399 | 
            +
            Guards can be tested by asserting that `transition_to!` does or does not raise a `Statesman::GuardFailedError`:
         | 
| 400 | 
            +
             | 
| 401 | 
            +
            ```ruby
         | 
| 402 | 
            +
            describe "guards" do
         | 
| 403 | 
            +
              it "cannot transition from state foo to state bar" do
         | 
| 404 | 
            +
                expect { some_model.transition_to!(:bar) }.to raise_error(Statesman::GuardFailedError)
         | 
| 405 | 
            +
              end
         | 
| 406 | 
            +
             | 
| 407 | 
            +
              it "can transition from state foo to state baz" do
         | 
| 408 | 
            +
                expect { some_model.transition_to!(:baz).to_not raise_error
         | 
| 409 | 
            +
              end
         | 
| 410 | 
            +
            end
         | 
| 411 | 
            +
            ```
         | 
| 412 | 
            +
             | 
| 413 | 
            +
            #### Testing Callbacks
         | 
| 414 | 
            +
             | 
| 415 | 
            +
            Callbacks are tested by asserting that the action they perform occurs:
         | 
| 416 | 
            +
             | 
| 417 | 
            +
            ```ruby
         | 
| 418 | 
            +
            describe "some callback" do
         | 
| 419 | 
            +
              it "adds one to the count property on the model" do
         | 
| 420 | 
            +
                expect { some_model.transition_to!(:some_state) }.
         | 
| 421 | 
            +
                  to change {
         | 
| 422 | 
            +
                    some_model.reload.count
         | 
| 423 | 
            +
                  }.by(1)
         | 
| 424 | 
            +
              end
         | 
| 425 | 
            +
            end
         | 
| 426 | 
            +
            ```
         | 
| 427 | 
            +
             | 
| 428 | 
            +
            #### Creating models in certain states
         | 
| 429 | 
            +
             | 
| 430 | 
            +
            Sometimes you'll want to test a guard/transition from one state to another, where the state you want to go from is not the initial state of the model. In this instance you'll need to construct a model instance in the state required. However, if you have strict guards, this can be a pain. One way to get around this in tests is to directly create the transitions in the database, hence avoiding the guards.
         | 
| 431 | 
            +
             | 
| 432 | 
            +
            We use [FactoryGirl](https://github.com/thoughtbot/factory_girl) for creating our test objects. Given an `Order` model that is backed by Statesman, we can easily set it up to be in a particular state:
         | 
| 433 | 
            +
             | 
| 434 | 
            +
            ```ruby
         | 
| 435 | 
            +
            factory :order do
         | 
| 436 | 
            +
              property "value"
         | 
| 437 | 
            +
              ...
         | 
| 438 | 
            +
             | 
| 439 | 
            +
              trait :shipped do
         | 
| 440 | 
            +
                after(:create) do |order|
         | 
| 441 | 
            +
                  FactoryGirl.create(:order_transition, :shipped, order: order)
         | 
| 442 | 
            +
                end
         | 
| 443 | 
            +
              end
         | 
| 444 | 
            +
            end
         | 
| 445 | 
            +
             | 
| 446 | 
            +
            factory :order_transition do
         | 
| 447 | 
            +
              order
         | 
| 448 | 
            +
              ...
         | 
| 449 | 
            +
             | 
| 450 | 
            +
              trait :shipped do
         | 
| 451 | 
            +
                to_state "shipped"
         | 
| 452 | 
            +
              end
         | 
| 453 | 
            +
            end
         | 
| 454 | 
            +
            ```
         | 
| 455 | 
            +
             | 
| 456 | 
            +
            This means you can easily create an `Order` in the `shipped` state:
         | 
| 457 | 
            +
             | 
| 458 | 
            +
            ```ruby
         | 
| 459 | 
            +
            let(:shipped_order) { FactoryGirl.create(:order, :shipped) }
         | 
| 460 | 
            +
            ```
         | 
| 353 461 |  | 
| 354 462 | 
             
            ---
         | 
| 355 463 |  | 
| @@ -1,10 +1,10 @@ | |
| 1 1 | 
             
            class Create<%= migration_class_name %> < ActiveRecord::Migration
         | 
| 2 2 | 
             
              def change
         | 
| 3 3 | 
             
                create_table :<%= table_name %> do |t|
         | 
| 4 | 
            -
                  t.string :to_state
         | 
| 4 | 
            +
                  t.string :to_state, null: false
         | 
| 5 5 | 
             
                  t.text :metadata<%= ", default: \"{}\"" unless mysql? %>
         | 
| 6 | 
            -
                  t.integer :sort_key
         | 
| 7 | 
            -
                  t.integer :<%= parent_id  | 
| 6 | 
            +
                  t.integer :sort_key, null: false
         | 
| 7 | 
            +
                  t.integer :<%= parent_id %>, null: false
         | 
| 8 8 | 
             
                  t.timestamps
         | 
| 9 9 | 
             
                end
         | 
| 10 10 |  | 
| @@ -1,9 +1,9 @@ | |
| 1 1 | 
             
            class AddStatesmanTo<%= migration_class_name %> < ActiveRecord::Migration
         | 
| 2 2 | 
             
              def change
         | 
| 3 | 
            -
                add_column :<%= table_name %>, :to_state, :string
         | 
| 3 | 
            +
                add_column :<%= table_name %>, :to_state, :string, null: false
         | 
| 4 4 | 
             
                add_column :<%= table_name %>, :metadata, :text<%= ", default: \"{}\"" unless mysql? %>
         | 
| 5 | 
            -
                add_column :<%= table_name %>, :sort_key, :integer
         | 
| 6 | 
            -
                add_column :<%= table_name %>, :<%= parent_id %>, :integer
         | 
| 5 | 
            +
                add_column :<%= table_name %>, :sort_key, :integer, null: false
         | 
| 6 | 
            +
                add_column :<%= table_name %>, :<%= parent_id %>, :integer, null: false
         | 
| 7 7 | 
             
                add_column :<%= table_name %>, :created_at, :datetime
         | 
| 8 8 | 
             
                add_column :<%= table_name %>, :updated_at, :datetime
         | 
| 9 9 |  | 
| @@ -23,6 +23,8 @@ module Statesman | |
| 23 23 | 
             
                  end
         | 
| 24 24 |  | 
| 25 25 | 
             
                  def create(from, to, metadata = {})
         | 
| 26 | 
            +
                    from = from.to_s
         | 
| 27 | 
            +
                    to = to.to_s
         | 
| 26 28 | 
             
                    create_transition(from, to, metadata)
         | 
| 27 29 | 
             
                  rescue ::ActiveRecord::RecordNotUnique => e
         | 
| 28 30 | 
             
                    if e.message.include?('sort_key') &&
         | 
| @@ -36,7 +38,9 @@ module Statesman | |
| 36 38 |  | 
| 37 39 | 
             
                  def history
         | 
| 38 40 | 
             
                    if transitions_for_parent.loaded?
         | 
| 39 | 
            -
                       | 
| 41 | 
            +
                      # Workaround for Rails bug which causes infinite loop when sorting
         | 
| 42 | 
            +
                      # already loaded result set. Introduced in rails/rails@b097ebe
         | 
| 43 | 
            +
                      transitions_for_parent.to_a.sort_by(&:sort_key)
         | 
| 40 44 | 
             
                    else
         | 
| 41 45 | 
             
                      transitions_for_parent.order(:sort_key)
         | 
| 42 46 | 
             
                    end
         | 
| @@ -7,16 +7,20 @@ module Statesman | |
| 7 7 |  | 
| 8 8 | 
             
                  module ClassMethods
         | 
| 9 9 | 
             
                    def in_state(*states)
         | 
| 10 | 
            -
                       | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 10 | 
            +
                      states = states.map(&:to_s)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                      joins(transition1_join)
         | 
| 13 | 
            +
                        .joins(transition2_join)
         | 
| 14 | 
            +
                        .where(state_inclusion_where(states), states)
         | 
| 13 15 | 
             
                        .where("transition2.id" => nil)
         | 
| 14 16 | 
             
                    end
         | 
| 15 17 |  | 
| 16 18 | 
             
                    def not_in_state(*states)
         | 
| 17 | 
            -
                       | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 19 | 
            +
                      states = states.map(&:to_s)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                      joins(transition1_join)
         | 
| 22 | 
            +
                        .joins(transition2_join)
         | 
| 23 | 
            +
                        .where("NOT (#{state_inclusion_where(states)})", states)
         | 
| 20 24 | 
             
                        .where("transition2.id" => nil)
         | 
| 21 25 | 
             
                    end
         | 
| 22 26 |  | 
| @@ -27,6 +31,11 @@ module Statesman | |
| 27 31 | 
             
                                                 "defined on the model"
         | 
| 28 32 | 
             
                    end
         | 
| 29 33 |  | 
| 34 | 
            +
                    def initial_state
         | 
| 35 | 
            +
                      raise NotImplementedError, "An initial_state method should be " \
         | 
| 36 | 
            +
                                                 "defined on the model"
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 30 39 | 
             
                    def transition_name
         | 
| 31 40 | 
             
                      transition_class.table_name.to_sym
         | 
| 32 41 | 
             
                    end
         | 
| @@ -35,10 +44,25 @@ module Statesman | |
| 35 44 | 
             
                      reflections[transition_name].foreign_key
         | 
| 36 45 | 
             
                    end
         | 
| 37 46 |  | 
| 38 | 
            -
                    def  | 
| 47 | 
            +
                    def transition1_join
         | 
| 48 | 
            +
                      "LEFT OUTER JOIN #{transition_name} transition1
         | 
| 49 | 
            +
                         ON transition1.#{model_foreign_key} = #{table_name}.id"
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    def transition2_join
         | 
| 39 53 | 
             
                      "LEFT OUTER JOIN #{transition_name} transition2
         | 
| 40 54 | 
             
                         ON transition2.#{model_foreign_key} = #{table_name}.id
         | 
| 41 | 
            -
                         AND transition2.sort_key >  | 
| 55 | 
            +
                         AND transition2.sort_key > transition1.sort_key"
         | 
| 56 | 
            +
                    end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    def state_inclusion_where(states)
         | 
| 59 | 
            +
                      if initial_state.in?(states)
         | 
| 60 | 
            +
                        'transition1.to_state IN (?) OR ' \
         | 
| 61 | 
            +
                        'transition1.to_state IS NULL'
         | 
| 62 | 
            +
                      else
         | 
| 63 | 
            +
                        'transition1.to_state IN (?) AND ' \
         | 
| 64 | 
            +
                        'transition1.to_state IS NOT NULL'
         | 
| 65 | 
            +
                      end
         | 
| 42 66 | 
             
                    end
         | 
| 43 67 | 
             
                  end
         | 
| 44 68 | 
             
                end
         | 
    
        data/lib/statesman/callback.rb
    CHANGED
    
    | @@ -11,8 +11,8 @@ module Statesman | |
| 11 11 | 
             
                    raise InvalidCallbackError, "No callback passed"
         | 
| 12 12 | 
             
                  end
         | 
| 13 13 |  | 
| 14 | 
            -
                  @from | 
| 15 | 
            -
                  @to | 
| 14 | 
            +
                  @from     = options[:from]
         | 
| 15 | 
            +
                  @to       = Array(options[:to])
         | 
| 16 16 | 
             
                  @callback = options[:callback]
         | 
| 17 17 | 
             
                end
         | 
| 18 18 |  | 
| @@ -34,19 +34,19 @@ module Statesman | |
| 34 34 | 
             
                end
         | 
| 35 35 |  | 
| 36 36 | 
             
                def matches_all_transitions
         | 
| 37 | 
            -
                  from.nil? && to. | 
| 37 | 
            +
                  from.nil? && to.empty?
         | 
| 38 38 | 
             
                end
         | 
| 39 39 |  | 
| 40 40 | 
             
                def matches_from_state(from, to)
         | 
| 41 | 
            -
                  (from == self.from  && (to.nil? || self.to. | 
| 41 | 
            +
                  (from == self.from  && (to.nil? || self.to.empty?))
         | 
| 42 42 | 
             
                end
         | 
| 43 43 |  | 
| 44 44 | 
             
                def matches_to_state(from, to)
         | 
| 45 | 
            -
                  ((from.nil? || self.from.nil?) &&  | 
| 45 | 
            +
                  ((from.nil? || self.from.nil?) && self.to.include?(to))
         | 
| 46 46 | 
             
                end
         | 
| 47 47 |  | 
| 48 48 | 
             
                def matches_both_states(from, to)
         | 
| 49 | 
            -
                  from == self.from &&  | 
| 49 | 
            +
                  from == self.from && self.to.include?(to)
         | 
| 50 50 | 
             
                end
         | 
| 51 51 | 
             
              end
         | 
| 52 52 | 
             
            end
         | 
    
        data/lib/statesman/machine.rb
    CHANGED
    
    | @@ -64,7 +64,7 @@ module Statesman | |
| 64 64 |  | 
| 65 65 | 
             
                  def transition(options = { from: nil, to: nil }, event = nil)
         | 
| 66 66 | 
             
                    from = to_s_or_nil(options[:from])
         | 
| 67 | 
            -
                    to =  | 
| 67 | 
            +
                    to = array_to_s_or_nil(options[:to])
         | 
| 68 68 |  | 
| 69 69 | 
             
                    raise InvalidStateError, "No to states provided." if to.empty?
         | 
| 70 70 |  | 
| @@ -82,44 +82,39 @@ module Statesman | |
| 82 82 | 
             
                  end
         | 
| 83 83 |  | 
| 84 84 | 
             
                  def before_transition(options = { from: nil, to: nil }, &block)
         | 
| 85 | 
            -
                     | 
| 86 | 
            -
             | 
| 85 | 
            +
                    add_callback(
         | 
| 86 | 
            +
                      options.merge(callback_class: Callback, callback_type: :before),
         | 
| 87 | 
            +
                      &block)
         | 
| 88 | 
            +
                  end
         | 
| 87 89 |  | 
| 88 | 
            -
             | 
| 89 | 
            -
                     | 
| 90 | 
            +
                  def guard_transition(options = { from: nil, to: nil }, &block)
         | 
| 91 | 
            +
                    add_callback(
         | 
| 92 | 
            +
                      options.merge(callback_class: Guard, callback_type: :guards),
         | 
| 93 | 
            +
                      &block)
         | 
| 90 94 | 
             
                  end
         | 
| 91 95 |  | 
| 92 96 | 
             
                  def after_transition(options = { from: nil, to: nil,
         | 
| 93 97 | 
             
                                                   after_commit: false }, &block)
         | 
| 94 | 
            -
                     | 
| 95 | 
            -
                    to   = to_s_or_nil(options[:to])
         | 
| 98 | 
            +
                    callback_type = options[:after_commit] ? :after_commit : :after
         | 
| 96 99 |  | 
| 97 | 
            -
                     | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
                  end
         | 
| 101 | 
            -
             | 
| 102 | 
            -
                  def guard_transition(options = { from: nil, to: nil }, &block)
         | 
| 103 | 
            -
                    from = to_s_or_nil(options[:from])
         | 
| 104 | 
            -
                    to   = to_s_or_nil(options[:to])
         | 
| 105 | 
            -
             | 
| 106 | 
            -
                    validate_callback_condition(from: from, to: to)
         | 
| 107 | 
            -
                    callbacks[:guards] << Guard.new(from: from, to: to, callback: block)
         | 
| 100 | 
            +
                    add_callback(
         | 
| 101 | 
            +
                      options.merge(callback_class: Callback, callback_type: callback_type),
         | 
| 102 | 
            +
                      &block)
         | 
| 108 103 | 
             
                  end
         | 
| 109 104 |  | 
| 110 105 | 
             
                  def validate_callback_condition(options = { from: nil, to: nil })
         | 
| 111 106 | 
             
                    from = to_s_or_nil(options[:from])
         | 
| 112 | 
            -
                    to   =  | 
| 107 | 
            +
                    to   = array_to_s_or_nil(options[:to])
         | 
| 113 108 |  | 
| 114 | 
            -
                    [from | 
| 115 | 
            -
                    return if from.nil? && to. | 
| 109 | 
            +
                    ([from] + to).compact.each { |state| validate_state(state) }
         | 
| 110 | 
            +
                    return if from.nil? && to.empty?
         | 
| 116 111 |  | 
| 117 112 | 
             
                    validate_not_from_terminal_state(from)
         | 
| 118 | 
            -
                    validate_not_to_initial_state( | 
| 113 | 
            +
                    to.each { |state| validate_not_to_initial_state(state) }
         | 
| 119 114 |  | 
| 120 | 
            -
                    return if from.nil? || to. | 
| 115 | 
            +
                    return if from.nil? || to.empty?
         | 
| 121 116 |  | 
| 122 | 
            -
                    validate_from_and_to_state(from,  | 
| 117 | 
            +
                    to.each { |state| validate_from_and_to_state(from, state) }
         | 
| 123 118 | 
             
                  end
         | 
| 124 119 |  | 
| 125 120 | 
             
                  # Check that the 'from' state is not terminal
         | 
| @@ -148,6 +143,17 @@ module Statesman | |
| 148 143 |  | 
| 149 144 | 
             
                  private
         | 
| 150 145 |  | 
| 146 | 
            +
                  def add_callback(options, &block)
         | 
| 147 | 
            +
                    from = to_s_or_nil(options[:from])
         | 
| 148 | 
            +
                    to   = array_to_s_or_nil(options[:to])
         | 
| 149 | 
            +
                    callback_klass = options.fetch(:callback_class)
         | 
| 150 | 
            +
                    callback_type = options.fetch(:callback_type)
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                    validate_callback_condition(from: from, to: to)
         | 
| 153 | 
            +
                    callbacks[callback_type] <<
         | 
| 154 | 
            +
                      callback_klass.new(from: from, to: to, callback: block)
         | 
| 155 | 
            +
                  end
         | 
| 156 | 
            +
             | 
| 151 157 | 
             
                  def validate_state(state)
         | 
| 152 158 | 
             
                    unless states.include?(state.to_s)
         | 
| 153 159 | 
             
                      raise InvalidStateError, "Invalid state '#{state}'"
         | 
| @@ -164,6 +170,10 @@ module Statesman | |
| 164 170 | 
             
                  def to_s_or_nil(input)
         | 
| 165 171 | 
             
                    input.nil? ? input : input.to_s
         | 
| 166 172 | 
             
                  end
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                  def array_to_s_or_nil(input)
         | 
| 175 | 
            +
                    Array(input).map { |item| to_s_or_nil(item) }
         | 
| 176 | 
            +
                  end
         | 
| 167 177 | 
             
                end
         | 
| 168 178 |  | 
| 169 179 | 
             
                def initialize(object,
         | 
    
        data/lib/statesman/version.rb
    CHANGED
    
    
| @@ -12,46 +12,69 @@ describe Statesman::Adapters::ActiveRecordModel do | |
| 12 12 | 
             
                  def self.transition_class
         | 
| 13 13 | 
             
                    MyActiveRecordModelTransition
         | 
| 14 14 | 
             
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def self.initial_state
         | 
| 17 | 
            +
                    MyStateMachine.initial_state
         | 
| 18 | 
            +
                  end
         | 
| 15 19 | 
             
                end
         | 
| 16 20 | 
             
              end
         | 
| 17 21 |  | 
| 18 22 | 
             
              let!(:model) do
         | 
| 19 23 | 
             
                model = MyActiveRecordModel.create
         | 
| 20 | 
            -
                model.my_active_record_model_transitions.create(to_state: : | 
| 24 | 
            +
                model.my_active_record_model_transitions.create(to_state: :succeeded)
         | 
| 21 25 | 
             
                model
         | 
| 22 26 | 
             
              end
         | 
| 23 27 |  | 
| 24 28 | 
             
              let!(:other_model) do
         | 
| 25 29 | 
             
                model = MyActiveRecordModel.create
         | 
| 26 | 
            -
                model.my_active_record_model_transitions.create(to_state: : | 
| 30 | 
            +
                model.my_active_record_model_transitions.create(to_state: :failed)
         | 
| 31 | 
            +
                model
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              let!(:initial_state_model) { MyActiveRecordModel.create }
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              let!(:returned_to_initial_model) do
         | 
| 37 | 
            +
                model = MyActiveRecordModel.create
         | 
| 38 | 
            +
                model.my_active_record_model_transitions.create(to_state: :failed)
         | 
| 39 | 
            +
                model.my_active_record_model_transitions.create(to_state: :initial)
         | 
| 27 40 | 
             
                model
         | 
| 28 41 | 
             
              end
         | 
| 29 42 |  | 
| 30 43 | 
             
              describe ".in_state" do
         | 
| 31 44 | 
             
                context "given a single state" do
         | 
| 32 | 
            -
                  subject { MyActiveRecordModel.in_state(: | 
| 45 | 
            +
                  subject { MyActiveRecordModel.in_state(:succeeded) }
         | 
| 33 46 |  | 
| 34 47 | 
             
                  it { is_expected.to include model }
         | 
| 35 48 | 
             
                end
         | 
| 36 49 |  | 
| 37 50 | 
             
                context "given multiple states" do
         | 
| 38 | 
            -
                  subject { MyActiveRecordModel.in_state(: | 
| 51 | 
            +
                  subject { MyActiveRecordModel.in_state(:succeeded, :failed) }
         | 
| 39 52 |  | 
| 40 53 | 
             
                  it { is_expected.to include model }
         | 
| 41 54 | 
             
                  it { is_expected.to include other_model }
         | 
| 42 55 | 
             
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                context "given the initial state" do
         | 
| 58 | 
            +
                  subject { MyActiveRecordModel.in_state(:initial) }
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  it { is_expected.to include initial_state_model }
         | 
| 61 | 
            +
                  it { is_expected.to include returned_to_initial_model }
         | 
| 62 | 
            +
                end
         | 
| 43 63 | 
             
              end
         | 
| 44 64 |  | 
| 45 65 | 
             
              describe ".not_in_state" do
         | 
| 46 66 | 
             
                context "given a single state" do
         | 
| 47 | 
            -
                  subject { MyActiveRecordModel.not_in_state(: | 
| 67 | 
            +
                  subject { MyActiveRecordModel.not_in_state(:failed) }
         | 
| 48 68 | 
             
                  it { is_expected.to include model }
         | 
| 49 69 | 
             
                  it { is_expected.not_to include other_model }
         | 
| 50 70 | 
             
                end
         | 
| 51 71 |  | 
| 52 72 | 
             
                context "given multiple states" do
         | 
| 53 | 
            -
                  subject { MyActiveRecordModel.not_in_state(: | 
| 54 | 
            -
                  it  | 
| 73 | 
            +
                  subject { MyActiveRecordModel.not_in_state(:succeeded, :failed) }
         | 
| 74 | 
            +
                  it do
         | 
| 75 | 
            +
                    is_expected.to match_array([initial_state_model,
         | 
| 76 | 
            +
                                                returned_to_initial_model])
         | 
| 77 | 
            +
                  end
         | 
| 55 78 | 
             
                end
         | 
| 56 79 | 
             
              end
         | 
| 57 80 | 
             
            end
         | 
| @@ -56,7 +56,7 @@ describe Statesman::Adapters::ActiveRecord do | |
| 56 56 | 
             
              end
         | 
| 57 57 |  | 
| 58 58 | 
             
              describe "#create" do
         | 
| 59 | 
            -
                let(:adapter) do
         | 
| 59 | 
            +
                let!(:adapter) do
         | 
| 60 60 | 
             
                  described_class.new(MyActiveRecordModelTransition, model, observer)
         | 
| 61 61 | 
             
                end
         | 
| 62 62 | 
             
                let(:from) { :x }
         | 
| @@ -123,8 +123,8 @@ describe Statesman::Adapters::ActiveRecord do | |
| 123 123 |  | 
| 124 124 | 
             
                context "with a pre-fetched transition history" do
         | 
| 125 125 | 
             
                  before do
         | 
| 126 | 
            -
                     | 
| 127 | 
            -
                    model.my_active_record_model_transitions. | 
| 126 | 
            +
                    adapter.create(:x, :y)
         | 
| 127 | 
            +
                    model.my_active_record_model_transitions.load_target
         | 
| 128 128 | 
             
                  end
         | 
| 129 129 |  | 
| 130 130 | 
             
                  it "doesn't query the database" do
         | 
| @@ -72,7 +72,7 @@ describe Statesman::Callback do | |
| 72 72 |  | 
| 73 73 | 
             
                context "with any from value on the callback" do
         | 
| 74 74 | 
             
                  let(:callback) do
         | 
| 75 | 
            -
                    Statesman::Callback.new(to: :y, callback: cb_lambda)
         | 
| 75 | 
            +
                    Statesman::Callback.new(to: [:y, :z], callback: cb_lambda)
         | 
| 76 76 | 
             
                  end
         | 
| 77 77 | 
             
                  let(:from) { :x }
         | 
| 78 78 |  | 
| @@ -81,6 +81,11 @@ describe Statesman::Callback do | |
| 81 81 | 
             
                    it { is_expected.to be_truthy }
         | 
| 82 82 | 
             
                  end
         | 
| 83 83 |  | 
| 84 | 
            +
                  context "and another allowed to value" do
         | 
| 85 | 
            +
                    let(:to) { :z }
         | 
| 86 | 
            +
                    it { is_expected.to be_truthy }
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
             | 
| 84 89 | 
             
                  context "and a disallowed to value" do
         | 
| 85 90 | 
             
                    let(:to) { :a }
         | 
| 86 91 | 
             
                    it { is_expected.to be_falsey }
         | 
| @@ -218,54 +218,94 @@ describe Statesman::Machine do | |
| 218 218 | 
             
                  machine.class_eval do
         | 
| 219 219 | 
             
                    state :x, initial: true
         | 
| 220 220 | 
             
                    state :y
         | 
| 221 | 
            -
                     | 
| 221 | 
            +
                    state :z
         | 
| 222 | 
            +
                    transition from: :x, to: [:y, :z]
         | 
| 222 223 | 
             
                  end
         | 
| 223 224 | 
             
                end
         | 
| 224 225 |  | 
| 225 | 
            -
                 | 
| 226 | 
            -
             | 
| 227 | 
            -
             | 
| 228 | 
            -
             | 
| 226 | 
            +
                let(:options) { { from: nil, to: [] } }
         | 
| 227 | 
            +
                let(:set_callback) { machine.send(assignment_method, options) {} }
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                shared_examples "fails" do |error_type|
         | 
| 230 | 
            +
                  it "raises an exception" do
         | 
| 231 | 
            +
                    expect { set_callback }.to raise_error(error_type)
         | 
| 232 | 
            +
                  end
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                  it "does not add a callback" do
         | 
| 235 | 
            +
                    expect do
         | 
| 236 | 
            +
                      begin
         | 
| 237 | 
            +
                        set_callback
         | 
| 238 | 
            +
                      rescue error_type
         | 
| 239 | 
            +
                        nil
         | 
| 240 | 
            +
                      end
         | 
| 241 | 
            +
                    end.to_not change(machine.callbacks[callback_store], :count)
         | 
| 242 | 
            +
                  end
         | 
| 229 243 | 
             
                end
         | 
| 230 244 |  | 
| 231 | 
            -
                 | 
| 232 | 
            -
                   | 
| 233 | 
            -
             | 
| 234 | 
            -
             | 
| 245 | 
            +
                shared_examples "adds callback" do
         | 
| 246 | 
            +
                  it "does not raise" do
         | 
| 247 | 
            +
                    expect { set_callback }.to_not raise_error
         | 
| 248 | 
            +
                  end
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                  it "stores callbacks" do
         | 
| 251 | 
            +
                    expect { set_callback }.to change(
         | 
| 252 | 
            +
                      machine.callbacks[callback_store], :count).by(1)
         | 
| 253 | 
            +
                  end
         | 
| 254 | 
            +
             | 
| 255 | 
            +
                  it "stores callback instances" do
         | 
| 256 | 
            +
                    set_callback
         | 
| 257 | 
            +
                    machine.callbacks[callback_store].each do |callback|
         | 
| 258 | 
            +
                      expect(callback).to be_a(Statesman::Callback)
         | 
| 259 | 
            +
                    end
         | 
| 235 260 | 
             
                  end
         | 
| 236 261 | 
             
                end
         | 
| 237 262 |  | 
| 238 263 | 
             
                context "with invalid states" do
         | 
| 239 | 
            -
                   | 
| 240 | 
            -
                     | 
| 241 | 
            -
             | 
| 242 | 
            -
                    end.to raise_error(Statesman::InvalidStateError)
         | 
| 264 | 
            +
                  context "when both are invalid" do
         | 
| 265 | 
            +
                    let(:options) { { from: :foo, to: :bar } }
         | 
| 266 | 
            +
                    it_behaves_like "fails", Statesman::InvalidStateError
         | 
| 243 267 | 
             
                  end
         | 
| 244 268 |  | 
| 245 | 
            -
                   | 
| 246 | 
            -
                     | 
| 247 | 
            -
             | 
| 248 | 
            -
                    end.to raise_error(Statesman::InvalidTransitionError)
         | 
| 269 | 
            +
                  context "from a terminal state to anything" do
         | 
| 270 | 
            +
                    let(:options) { { from: :y, to: [] } }
         | 
| 271 | 
            +
                    it_behaves_like "fails", Statesman::InvalidTransitionError
         | 
| 249 272 | 
             
                  end
         | 
| 250 273 |  | 
| 251 | 
            -
                   | 
| 252 | 
            -
                     | 
| 253 | 
            -
             | 
| 254 | 
            -
             | 
| 274 | 
            +
                  context "to an initial state and from anything" do
         | 
| 275 | 
            +
                    let(:options) { { from: nil, to: :x } }
         | 
| 276 | 
            +
                    it_behaves_like "fails", Statesman::InvalidTransitionError
         | 
| 277 | 
            +
                  end
         | 
| 278 | 
            +
             | 
| 279 | 
            +
                  context "from a terminal state and to multiple states" do
         | 
| 280 | 
            +
                    let(:options) { { from: :y, to: [:x, :z] } }
         | 
| 281 | 
            +
                    it_behaves_like "fails", Statesman::InvalidTransitionError
         | 
| 282 | 
            +
                  end
         | 
| 283 | 
            +
             | 
| 284 | 
            +
                  context "to an initial state and other states" do
         | 
| 285 | 
            +
                    let(:options) { { from: nil, to: [:y, :x, :z] } }
         | 
| 286 | 
            +
                    it_behaves_like "fails", Statesman::InvalidTransitionError
         | 
| 255 287 | 
             
                  end
         | 
| 256 288 | 
             
                end
         | 
| 257 289 |  | 
| 258 290 | 
             
                context "with validate_states" do
         | 
| 259 | 
            -
                   | 
| 260 | 
            -
                     | 
| 261 | 
            -
             | 
| 262 | 
            -
                    end.to_not raise_error
         | 
| 291 | 
            +
                  context "from anything" do
         | 
| 292 | 
            +
                    let(:options) { { from: nil, to: :y } }
         | 
| 293 | 
            +
                    it_behaves_like "adds callback"
         | 
| 263 294 | 
             
                  end
         | 
| 264 295 |  | 
| 265 | 
            -
                   | 
| 266 | 
            -
                     | 
| 267 | 
            -
             | 
| 268 | 
            -
             | 
| 296 | 
            +
                  context "to anything" do
         | 
| 297 | 
            +
                    let(:options) { { from: :x, to: [] } }
         | 
| 298 | 
            +
                    it_behaves_like "adds callback"
         | 
| 299 | 
            +
                  end
         | 
| 300 | 
            +
             | 
| 301 | 
            +
                  context "to several" do
         | 
| 302 | 
            +
                    let(:options) { { from: :x, to: [:y, :z] } }
         | 
| 303 | 
            +
                    it_behaves_like "adds callback"
         | 
| 304 | 
            +
                  end
         | 
| 305 | 
            +
             | 
| 306 | 
            +
                  context "from any to several" do
         | 
| 307 | 
            +
                    let(:options) { { from: nil, to: [:y, :z] } }
         | 
| 308 | 
            +
                    it_behaves_like "adds callback"
         | 
| 269 309 | 
             
                  end
         | 
| 270 310 | 
             
                end
         | 
| 271 311 | 
             
              end
         | 
| @@ -3,8 +3,24 @@ require "json" | |
| 3 3 |  | 
| 4 4 | 
             
            DB = Pathname.new("test.sqlite3")
         | 
| 5 5 |  | 
| 6 | 
            +
            class MyStateMachine
         | 
| 7 | 
            +
              include Statesman::Machine
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              state :initial, initial: true
         | 
| 10 | 
            +
              state :succeeded
         | 
| 11 | 
            +
              state :failed
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              transition from: :initial, to: [:succeeded, :failed]
         | 
| 14 | 
            +
              transition from: :failed,  to: :initial
         | 
| 15 | 
            +
            end
         | 
| 16 | 
            +
             | 
| 6 17 | 
             
            class MyActiveRecordModel < ActiveRecord::Base
         | 
| 7 18 | 
             
              has_many :my_active_record_model_transitions
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              def state_machine
         | 
| 21 | 
            +
                @state_machine ||= MyStateMachine
         | 
| 22 | 
            +
                  .new(self, transition_class: MyActiveRecordModelTransition)
         | 
| 23 | 
            +
              end
         | 
| 8 24 | 
             
            end
         | 
| 9 25 |  | 
| 10 26 | 
             
            class MyActiveRecordModelTransition < ActiveRecord::Base
         | 
    
        data/spec/support/mongoid.rb
    CHANGED
    
    
    
        data/statesman.gemspec
    CHANGED
    
    | @@ -20,13 +20,13 @@ Gem::Specification.new do |spec| | |
| 20 20 |  | 
| 21 21 | 
             
              spec.add_development_dependency "bundler",       "~> 1.3"
         | 
| 22 22 | 
             
              spec.add_development_dependency "rake"
         | 
| 23 | 
            -
              spec.add_development_dependency "rspec",         "~> 3. | 
| 23 | 
            +
              spec.add_development_dependency "rspec",         "~> 3.1"
         | 
| 24 24 | 
             
              spec.add_development_dependency "rspec-its",     "~> 1.0"
         | 
| 25 25 | 
             
              spec.add_development_dependency "guard-rspec",   "~> 4.3"
         | 
| 26 | 
            -
              spec.add_development_dependency "rubocop",       "~> 0. | 
| 26 | 
            +
              spec.add_development_dependency "rubocop",       "~> 0.26"
         | 
| 27 27 | 
             
              spec.add_development_dependency "guard-rubocop", "~> 1.1"
         | 
| 28 | 
            -
              spec.add_development_dependency "sqlite3",       "~> 1.3 | 
| 29 | 
            -
              spec.add_development_dependency "mongoid",       "~>  | 
| 30 | 
            -
              spec.add_development_dependency " | 
| 28 | 
            +
              spec.add_development_dependency "sqlite3",       "~> 1.3"
         | 
| 29 | 
            +
              spec.add_development_dependency "mongoid",       "~> 4.0"
         | 
| 30 | 
            +
              spec.add_development_dependency "activerecord",  "~> 4.1"
         | 
| 31 31 | 
             
              spec.add_development_dependency "ammeter",       "~> 1.1"
         | 
| 32 32 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: statesman
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 1.0.0.beta1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Harry Marr
         | 
| @@ -9,7 +9,7 @@ authors: | |
| 9 9 | 
             
            autorequire: 
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date: 2014-09 | 
| 12 | 
            +
            date: 2014-10-09 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: bundler
         | 
| @@ -45,14 +45,14 @@ dependencies: | |
| 45 45 | 
             
                requirements:
         | 
| 46 46 | 
             
                - - ~>
         | 
| 47 47 | 
             
                  - !ruby/object:Gem::Version
         | 
| 48 | 
            -
                    version: '3. | 
| 48 | 
            +
                    version: '3.1'
         | 
| 49 49 | 
             
              type: :development
         | 
| 50 50 | 
             
              prerelease: false
         | 
| 51 51 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 52 52 | 
             
                requirements:
         | 
| 53 53 | 
             
                - - ~>
         | 
| 54 54 | 
             
                  - !ruby/object:Gem::Version
         | 
| 55 | 
            -
                    version: '3. | 
| 55 | 
            +
                    version: '3.1'
         | 
| 56 56 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 57 57 | 
             
              name: rspec-its
         | 
| 58 58 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -87,14 +87,14 @@ dependencies: | |
| 87 87 | 
             
                requirements:
         | 
| 88 88 | 
             
                - - ~>
         | 
| 89 89 | 
             
                  - !ruby/object:Gem::Version
         | 
| 90 | 
            -
                    version: 0. | 
| 90 | 
            +
                    version: '0.26'
         | 
| 91 91 | 
             
              type: :development
         | 
| 92 92 | 
             
              prerelease: false
         | 
| 93 93 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 94 94 | 
             
                requirements:
         | 
| 95 95 | 
             
                - - ~>
         | 
| 96 96 | 
             
                  - !ruby/object:Gem::Version
         | 
| 97 | 
            -
                    version: 0. | 
| 97 | 
            +
                    version: '0.26'
         | 
| 98 98 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 99 99 | 
             
              name: guard-rubocop
         | 
| 100 100 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -115,42 +115,42 @@ dependencies: | |
| 115 115 | 
             
                requirements:
         | 
| 116 116 | 
             
                - - ~>
         | 
| 117 117 | 
             
                  - !ruby/object:Gem::Version
         | 
| 118 | 
            -
                    version: 1.3 | 
| 118 | 
            +
                    version: '1.3'
         | 
| 119 119 | 
             
              type: :development
         | 
| 120 120 | 
             
              prerelease: false
         | 
| 121 121 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 122 122 | 
             
                requirements:
         | 
| 123 123 | 
             
                - - ~>
         | 
| 124 124 | 
             
                  - !ruby/object:Gem::Version
         | 
| 125 | 
            -
                    version: 1.3 | 
| 125 | 
            +
                    version: '1.3'
         | 
| 126 126 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 127 127 | 
             
              name: mongoid
         | 
| 128 128 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 129 129 | 
             
                requirements:
         | 
| 130 130 | 
             
                - - ~>
         | 
| 131 131 | 
             
                  - !ruby/object:Gem::Version
         | 
| 132 | 
            -
                    version:  | 
| 132 | 
            +
                    version: '4.0'
         | 
| 133 133 | 
             
              type: :development
         | 
| 134 134 | 
             
              prerelease: false
         | 
| 135 135 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 136 136 | 
             
                requirements:
         | 
| 137 137 | 
             
                - - ~>
         | 
| 138 138 | 
             
                  - !ruby/object:Gem::Version
         | 
| 139 | 
            -
                    version:  | 
| 139 | 
            +
                    version: '4.0'
         | 
| 140 140 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 141 | 
            -
              name:  | 
| 141 | 
            +
              name: activerecord
         | 
| 142 142 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 143 143 | 
             
                requirements:
         | 
| 144 144 | 
             
                - - ~>
         | 
| 145 145 | 
             
                  - !ruby/object:Gem::Version
         | 
| 146 | 
            -
                    version: ' | 
| 146 | 
            +
                    version: '4.1'
         | 
| 147 147 | 
             
              type: :development
         | 
| 148 148 | 
             
              prerelease: false
         | 
| 149 149 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 150 150 | 
             
                requirements:
         | 
| 151 151 | 
             
                - - ~>
         | 
| 152 152 | 
             
                  - !ruby/object:Gem::Version
         | 
| 153 | 
            -
                    version: ' | 
| 153 | 
            +
                    version: '4.1'
         | 
| 154 154 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 155 155 | 
             
              name: ammeter
         | 
| 156 156 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -240,9 +240,9 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 240 240 | 
             
                  version: '0'
         | 
| 241 241 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 242 242 | 
             
              requirements:
         | 
| 243 | 
            -
              - - ' | 
| 243 | 
            +
              - - '>'
         | 
| 244 244 | 
             
                - !ruby/object:Gem::Version
         | 
| 245 | 
            -
                  version:  | 
| 245 | 
            +
                  version: 1.3.1
         | 
| 246 246 | 
             
            requirements: []
         | 
| 247 247 | 
             
            rubyforge_project: 
         | 
| 248 248 | 
             
            rubygems_version: 2.4.1
         |