statesman 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +3 -26
  3. data/.rubocop_todo.yml +7 -8
  4. data/CHANGELOG.md +4 -0
  5. data/Gemfile +5 -4
  6. data/Guardfile +3 -3
  7. data/README.md +1 -1
  8. data/lib/generators/statesman/active_record_transition_generator.rb +1 -1
  9. data/lib/generators/statesman/generator_helpers.rb +1 -1
  10. data/lib/generators/statesman/migration_generator.rb +1 -1
  11. data/lib/generators/statesman/mongoid_transition_generator.rb +1 -1
  12. data/lib/statesman.rb +7 -7
  13. data/lib/statesman/adapters/active_record.rb +18 -12
  14. data/lib/statesman/adapters/memory.rb +4 -1
  15. data/lib/statesman/adapters/mongoid.rb +2 -2
  16. data/lib/statesman/machine.rb +2 -6
  17. data/lib/statesman/version.rb +1 -1
  18. data/lib/tasks/statesman.rake +9 -3
  19. data/spec/fixtures/add_constraints_to_most_recent_for_bacon_transitions_with_partial_index.rb +6 -1
  20. data/spec/fixtures/add_constraints_to_most_recent_for_bacon_transitions_without_partial_index.rb +5 -1
  21. data/spec/generators/statesman/active_record_transition_generator_spec.rb +9 -9
  22. data/spec/generators/statesman/migration_generator_spec.rb +4 -4
  23. data/spec/generators/statesman/mongoid_transition_generator_spec.rb +4 -4
  24. data/spec/statesman/adapters/active_record_queries_spec.rb +1 -1
  25. data/spec/statesman/adapters/active_record_spec.rb +43 -19
  26. data/spec/statesman/adapters/mongoid_spec.rb +30 -0
  27. data/spec/statesman/adapters/shared_examples.rb +0 -1
  28. data/spec/statesman/machine_spec.rb +4 -4
  29. data/spec/support/active_record.rb +7 -7
  30. data/spec/support/generators_shared_examples.rb +5 -5
  31. data/spec/support/mongoid.rb +2 -2
  32. data/statesman.gemspec +6 -5
  33. metadata +19 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 32bb973d8f35e81d00d5f6424d26dfb7af6f101b
4
- data.tar.gz: 2cfacba286b5a45f15f784cf94a91cd2abc34cc7
2
+ SHA256:
3
+ metadata.gz: 4ea9661229864328ca481ff132837554323967d265616d38c295c5fa94ca9103
4
+ data.tar.gz: 8974f12e8e70d925f0b35e3753a9977d1ff94a1a76bf05c0a967535037ce6a24
5
5
  SHA512:
6
- metadata.gz: d2cd17ce3c5cb346912126ff09bb0048647347495bbd21ddb5fd4beaa36225189daae1cd6ca5c6df69bd78f247240e6d03b9c16cb8625b49713b4e4a3b4319f8
7
- data.tar.gz: b8e32db8300886d2db38d7a6e8ef00666f7d7ad018b1a04e89c4bd40a8a0cabb415eed02019b3c7745a4bcc5572d6876f7d932990632bb4c50e21bcfdcfd7599
6
+ metadata.gz: ec7c81ee5c313377acaf35c1196ccf74036769bb945c636d93f56d346deda319534ea9dbeff87bbfe84a2f3d71d7a1a4ae3d435d80c2fa46dc43d2ff3304661a
7
+ data.tar.gz: 78ed181ce45c7517f8617f61e26c20780f0f2c5b3a04236e4766761b985d42172774d05a54d05bcc2d72bc379eb2f6ed3fe68c122ce8989f19d3d5ba16d286af
data/.rubocop.yml CHANGED
@@ -1,30 +1,7 @@
1
1
  inherit_from: .rubocop_todo.yml
2
+
2
3
  inherit_gem:
3
- ruboconfig: rubocop.yml
4
+ gc_ruboconfig: rubocop.yml
4
5
 
5
6
  AllCops:
6
- DisplayCopNames: true
7
- Exclude:
8
- - vendor/**/*
9
- - .*/**
10
- - spec/fixtures/**/*
11
-
12
- StringLiterals:
13
- Enabled: false
14
-
15
- MethodLength:
16
- CountComments: false
17
- Max: 15
18
-
19
- AbcSize:
20
- Max: 18
21
-
22
- LineLength:
23
- Max: 80
24
-
25
- # Allow class and message or instance raises
26
- Style/RaiseArgs:
27
- Enabled: false
28
-
29
- Lint/RescueWithoutErrorClass:
30
- Enabled: false
7
+ TargetRubyVersion: 2.2
data/.rubocop_todo.yml CHANGED
@@ -1,17 +1,16 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2017-08-18 17:09:48 +0100 using RuboCop version 0.49.1.
3
+ # on 2017-12-20 11:33:28 +0000 using RuboCop version 0.52.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 3
10
- # Configuration parameters: CountComments, ExcludedMethods.
11
- Metrics/BlockLength:
12
- Max: 42
9
+ # Offense count: 5
10
+ Metrics/AbcSize:
11
+ Max: 18
13
12
 
14
- # Offense count: 2
13
+ # Offense count: 4
15
14
  # Configuration parameters: CountComments.
16
- Metrics/ModuleLength:
17
- Max: 107
15
+ Metrics/MethodLength:
16
+ Max: 14
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## v3.3.0, 5 January 2018
2
+
3
+ - Touch `updated_at` on transitions when unsetting `most_recent` flag (patch by [@NGMarmaduke](https://github.com/NGMarmaduke))
4
+ - Fix `force_reload` for ActiveRecord models with loaded transitions (patch by [@jacobpgn](https://github.com/))
1
5
 
2
6
  ## v3.2.0, 27 November 2017
3
7
 
data/Gemfile CHANGED
@@ -3,13 +3,14 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  gem "rails", "~> #{ENV['RAILS_VERSION']}" if ENV["RAILS_VERSION"]
6
- gem "ruboconfig", git: "https://github.com/gocardless/ruboconfig", tag: "v1.0.1"
7
6
 
8
7
  group :development do
9
8
  gem "mongoid", ">= 3.1" unless ENV["EXCLUDE_MONGOID"]
10
9
 
11
10
  # test/unit is no longer bundled with Ruby 2.2, but required by Rails
12
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2.0")
13
- gem "test-unit", "~> 3.0"
14
- end
11
+ gem "test-unit", "~> 3.0" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2.0")
12
+ end
13
+
14
+ group :development, :test do
15
+ gem "gc_ruboconfig", "~> 2.1.0"
15
16
  end
data/Guardfile CHANGED
@@ -1,13 +1,13 @@
1
1
  # A sample Guardfile
2
2
  # More info at https://github.com/guard/guard#readme
3
3
 
4
- guard :rspec, all_on_start: true, cmd: 'bundle exec rspec --color' do
4
+ guard :rspec, all_on_start: true, cmd: "bundle exec rspec --color" do
5
5
  watch(%r{^spec/.+_spec\.rb$})
6
6
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
- watch('spec/spec_helper.rb') { "spec" }
7
+ watch("spec/spec_helper.rb") { "spec" }
8
8
  end
9
9
 
10
- guard :rubocop, all_on_start: true, cli: ['--format', 'clang'] do
10
+ guard :rubocop, all_on_start: true, cli: ["--format", "clang"] do
11
11
  watch(/.+\.rb$/)
12
12
  watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
13
13
  watch(%r{(?:.+/)?\rubocop-todo\.yml$}) { |m| File.dirname(m[0]) }
data/README.md CHANGED
@@ -29,7 +29,7 @@ protection.
29
29
  To get started, just add Statesman to your `Gemfile`, and then run `bundle`:
30
30
 
31
31
  ```ruby
32
- gem 'statesman', '~> 3.1.0'
32
+ gem 'statesman', '~> 3.3.0'
33
33
  ```
34
34
 
35
35
  ## Usage
@@ -11,7 +11,7 @@ module Statesman
11
11
  argument :parent, type: :string, desc: "Your parent model name"
12
12
  argument :klass, type: :string, desc: "Your transition model name"
13
13
 
14
- source_root File.expand_path('../templates', __FILE__)
14
+ source_root File.expand_path("../templates", __FILE__)
15
15
 
16
16
  def create_model_file
17
17
  template("create_migration.rb.erb", migration_file_name)
@@ -9,7 +9,7 @@ module Statesman
9
9
  end
10
10
 
11
11
  def migration_class_name
12
- klass.gsub(/::/, '').pluralize
12
+ klass.gsub(/::/, "").pluralize
13
13
  end
14
14
 
15
15
  def next_migration_number
@@ -11,7 +11,7 @@ module Statesman
11
11
  argument :parent, type: :string, desc: "Your parent model name"
12
12
  argument :klass, type: :string, desc: "Your transition model name"
13
13
 
14
- source_root File.expand_path('../templates', __FILE__)
14
+ source_root File.expand_path("../templates", __FILE__)
15
15
 
16
16
  def create_model_file
17
17
  template("update_migration.rb.erb", file_name)
@@ -10,7 +10,7 @@ module Statesman
10
10
  argument :parent, type: :string, desc: "Your parent model name"
11
11
  argument :klass, type: :string, desc: "Your transition model name"
12
12
 
13
- source_root File.expand_path('../templates', __FILE__)
13
+ source_root File.expand_path("../templates", __FILE__)
14
14
 
15
15
  def create_model_file
16
16
  template("mongoid_transition_model.rb.erb", model_file_name)
data/lib/statesman.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  module Statesman
2
- autoload :Config, 'statesman/config'
3
- autoload :Machine, 'statesman/machine'
4
- autoload :Callback, 'statesman/callback'
5
- autoload :Guard, 'statesman/guard'
6
- autoload :Utils, 'statesman/utils'
7
- autoload :Version, 'statesman/version'
2
+ autoload :Config, "statesman/config"
3
+ autoload :Machine, "statesman/machine"
4
+ autoload :Callback, "statesman/callback"
5
+ autoload :Guard, "statesman/guard"
6
+ autoload :Utils, "statesman/utils"
7
+ autoload :Version, "statesman/version"
8
8
  module Adapters
9
9
  autoload :Memory, "statesman/adapters/memory"
10
10
  autoload :ActiveRecord, "statesman/adapters/active_record"
@@ -16,7 +16,7 @@ module Statesman
16
16
  autoload :MongoidTransition,
17
17
  "statesman/adapters/mongoid_transition"
18
18
  end
19
- require 'statesman/railtie' if defined?(::Rails::Railtie)
19
+ require "statesman/railtie" if defined?(::Rails::Railtie)
20
20
 
21
21
  # Example:
22
22
  # Statesman.configure do
@@ -13,17 +13,17 @@ module Statesman
13
13
  if ::ActiveRecord::Base.connection.respond_to?(:supports_partial_index?)
14
14
  ::ActiveRecord::Base.connection.supports_partial_index?
15
15
  else
16
- ::ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
16
+ ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
17
17
  end
18
18
  end
19
19
 
20
20
  def initialize(transition_class, parent_model, observer, options = {})
21
21
  serialized = serialized?(transition_class)
22
- column_type = transition_class.columns_hash['metadata'].sql_type
22
+ column_type = transition_class.columns_hash["metadata"].sql_type
23
23
  if !serialized && !JSON_COLUMN_TYPES.include?(column_type)
24
- raise UnserializedMetadataError.new(transition_class.name)
24
+ raise UnserializedMetadataError, transition_class.name
25
25
  elsif serialized && JSON_COLUMN_TYPES.include?(column_type)
26
- raise IncompatibleSerializationError.new(transition_class.name)
26
+ raise IncompatibleSerializationError, transition_class.name
27
27
  end
28
28
  @transition_class = transition_class
29
29
  @parent_model = parent_model
@@ -41,8 +41,8 @@ module Statesman
41
41
  @last_transition = nil
42
42
  end
43
43
 
44
- def history
45
- if transitions_for_parent.loaded?
44
+ def history(force_reload: false)
45
+ if transitions_for_parent.loaded? && !force_reload
46
46
  # Workaround for Rails bug which causes infinite loop when sorting
47
47
  # already loaded result set. Introduced in rails/rails@b097ebe
48
48
  transitions_for_parent.to_a.sort_by(&:sort_key)
@@ -53,7 +53,7 @@ module Statesman
53
53
 
54
54
  def last(force_reload: false)
55
55
  if force_reload
56
- @last_transition = history.last
56
+ @last_transition = history(force_reload: true).last
57
57
  else
58
58
  @last_transition ||= history.last
59
59
  end
@@ -87,6 +87,12 @@ module Statesman
87
87
  end
88
88
 
89
89
  def unset_old_most_recent
90
+ most_recent = transitions_for_parent.where(most_recent: true)
91
+ updated_at = if ::ActiveRecord::Base.default_timezone == :utc
92
+ Time.now.utc
93
+ else
94
+ Time.now
95
+ end
90
96
  # Check whether the `most_recent` column allows null values. If it
91
97
  # doesn't, set old records to `false`, otherwise, set them to `NULL`.
92
98
  #
@@ -94,10 +100,10 @@ module Statesman
94
100
  # support partial indexes. By doing the conditioning on the column,
95
101
  # rather than Rails' opinion of whether the database supports partial
96
102
  # indexes, we're robust to DBs later adding support for partial indexes.
97
- if transition_class.columns_hash['most_recent'].null == false
98
- transitions_for_parent.update_all(most_recent: false)
103
+ if transition_class.columns_hash["most_recent"].null == false
104
+ most_recent.update_all(most_recent: false, updated_at: updated_at)
99
105
  else
100
- transitions_for_parent.update_all(most_recent: nil)
106
+ most_recent.update_all(most_recent: nil, updated_at: updated_at)
101
107
  end
102
108
  end
103
109
 
@@ -107,7 +113,7 @@ module Statesman
107
113
 
108
114
  def serialized?(transition_class)
109
115
  if ::ActiveRecord.respond_to?(:gem_version) &&
110
- ::ActiveRecord.gem_version >= Gem::Version.new('4.2.0.a')
116
+ ::ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
111
117
  transition_class.type_for_attribute("metadata").
112
118
  is_a?(::ActiveRecord::Type::Serialized)
113
119
  else
@@ -117,7 +123,7 @@ module Statesman
117
123
 
118
124
  def transition_conflict_error?(e)
119
125
  e.message.include?(@transition_class.table_name) &&
120
- (e.message.include?('sort_key') || e.message.include?('most_recent'))
126
+ (e.message.include?("sort_key") || e.message.include?("most_recent"))
121
127
  end
122
128
  end
123
129
  end
@@ -4,7 +4,6 @@ module Statesman
4
4
  module Adapters
5
5
  class Memory
6
6
  attr_reader :transition_class
7
- attr_reader :history
8
7
  attr_reader :parent_model
9
8
 
10
9
  # We only accept mode as a parameter to maintain a consistent interface
@@ -32,6 +31,10 @@ module Statesman
32
31
  @history.sort_by(&:sort_key).last
33
32
  end
34
33
 
34
+ def history(*)
35
+ @history
36
+ end
37
+
35
38
  private
36
39
 
37
40
  def next_sort_key
@@ -10,7 +10,7 @@ module Statesman
10
10
  @transition_class = transition_class
11
11
  @parent_model = parent_model
12
12
  @observer = observer
13
- unless transition_class_hash_fields.include?('statesman_metadata')
13
+ unless transition_class_hash_fields.include?("statesman_metadata")
14
14
  raise UnserializedMetadataError, metadata_field_error_message
15
15
  end
16
16
  end
@@ -32,7 +32,7 @@ module Statesman
32
32
  @last_transition = nil
33
33
  end
34
34
 
35
- def history
35
+ def history(*)
36
36
  transitions_for_parent.asc(:sort_key)
37
37
  end
38
38
 
@@ -138,12 +138,8 @@ module Statesman
138
138
  end
139
139
 
140
140
  def validate_callback_type_and_class(callback_type, callback_class)
141
- if callback_type.nil?
142
- raise ArgumentError.new("missing keyword: callback_type")
143
- end
144
- if callback_class.nil?
145
- raise ArgumentError.new("missing keyword: callback_class")
146
- end
141
+ raise ArgumentError, "missing keyword: callback_type" if callback_type.nil?
142
+ raise ArgumentError, "missing keyword: callback_class" if callback_class.nil?
147
143
  end
148
144
 
149
145
  def validate_state(state)
@@ -1,3 +1,3 @@
1
1
  module Statesman
2
- VERSION = "3.2.0".freeze
2
+ VERSION = "3.3.0".freeze
3
3
  end
@@ -8,6 +8,11 @@ namespace :statesman do
8
8
  parent_class = parent_model_name.constantize
9
9
  transition_class = parent_class.transition_class
10
10
  parent_fk = "#{parent_model_name.demodulize.underscore}_id"
11
+ updated_at = if ActiveRecord::Base.default_timezone == :utc
12
+ Time.now.utc
13
+ else
14
+ Time.now
15
+ end
11
16
 
12
17
  total_models = parent_class.count
13
18
  done_models = 0
@@ -18,10 +23,10 @@ namespace :statesman do
18
23
  if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
19
24
  # Set all transitions' most_recent to FALSE
20
25
  transition_class.where(parent_fk => models.map(&:id)).
21
- update_all(most_recent: false)
26
+ update_all(most_recent: false, updated_at: updated_at)
22
27
  else
23
28
  transition_class.where(parent_fk => models.map(&:id)).
24
- update_all(most_recent: nil)
29
+ update_all(most_recent: nil, updated_at: updated_at)
25
30
  end
26
31
 
27
32
  # Set current transition's most_recent to TRUE
@@ -44,7 +49,8 @@ namespace :statesman do
44
49
  latest_ids = transition_class.find_by_sql(latest_ids_query).
45
50
  to_a.collect(&:id)
46
51
 
47
- transition_class.where(id: latest_ids).update_all(most_recent: true)
52
+ transition_class.where(id: latest_ids).
53
+ update_all(most_recent: true, updated_at: updated_at)
48
54
  end
49
55
 
50
56
  done_models += batch_size
@@ -2,7 +2,12 @@ class AddConstraintsToMostRecentForBaconTransitions < ActiveRecord::Migration
2
2
  disable_ddl_transaction!
3
3
 
4
4
  def up
5
- add_index :bacon_transitions, [:bacon_id, :most_recent], unique: true, where: "most_recent", name: "index_bacon_transitions_parent_most_recent", algorithm: :concurrently
5
+ add_index :bacon_transitions,
6
+ %i[bacon_id most_recent],
7
+ unique: true,
8
+ where: "most_recent",
9
+ name: "index_bacon_transitions_parent_most_recent",
10
+ algorithm: :concurrently
6
11
  change_column_null :bacon_transitions, :most_recent, false
7
12
  end
8
13
 
@@ -2,7 +2,11 @@ class AddConstraintsToMostRecentForBaconTransitions < ActiveRecord::Migration
2
2
  disable_ddl_transaction!
3
3
 
4
4
  def up
5
- add_index :bacon_transitions, [:bacon_id, :most_recent], unique: true, name: "index_bacon_transitions_parent_most_recent", algorithm: :concurrently
5
+ add_index :bacon_transitions,
6
+ %i[bacon_id most_recent],
7
+ unique: true,
8
+ name: "index_bacon_transitions_parent_most_recent",
9
+ algorithm: :concurrently
6
10
  end
7
11
 
8
12
  def down
@@ -4,35 +4,35 @@ require "generators/statesman/active_record_transition_generator"
4
4
 
5
5
  describe Statesman::ActiveRecordTransitionGenerator, type: :generator do
6
6
  it_behaves_like "a generator" do
7
- let(:migration_name) { 'db/migrate/create_bacon_transitions.rb' }
7
+ let(:migration_name) { "db/migrate/create_bacon_transitions.rb" }
8
8
  end
9
9
 
10
- describe 'creates a migration' do
10
+ describe "creates a migration" do
11
11
  subject { file("db/migrate/#{time}_create_bacon_transitions.rb") }
12
12
 
13
13
  before { allow(Time).to receive(:now).and_return(mock_time) }
14
14
  before { run_generator %w[Yummy::Bacon Yummy::BaconTransition] }
15
15
 
16
- let(:mock_time) { double('Time', utc: double('UTCTime', strftime: time)) }
17
- let(:time) { '5678309' }
16
+ let(:mock_time) { double("Time", utc: double("UTCTime", strftime: time)) }
17
+ let(:time) { "5678309" }
18
18
 
19
19
  it "includes a foreign key" do
20
20
  expect(subject).to contain("add_foreign_key :bacon_transitions, :bacons")
21
21
  end
22
22
  end
23
23
 
24
- describe 'properly adds class names' do
24
+ describe "properly adds class names" do
25
25
  before { run_generator %w[Yummy::Bacon Yummy::BaconTransition] }
26
- subject { file('app/models/yummy/bacon_transition.rb') }
26
+ subject { file("app/models/yummy/bacon_transition.rb") }
27
27
 
28
28
  it { is_expected.to contain(/:bacon_transition/) }
29
29
  it { is_expected.not_to contain(%r{:yummy/bacon}) }
30
30
  it { is_expected.to contain(/class_name: 'Yummy::Bacon'/) }
31
31
  end
32
32
 
33
- describe 'properly formats without class names' do
33
+ describe "properly formats without class names" do
34
34
  before { run_generator %w[Bacon BaconTransition] }
35
- subject { file('app/models/bacon_transition.rb') }
35
+ subject { file("app/models/bacon_transition.rb") }
36
36
 
37
37
  it { is_expected.not_to contain(/class_name:/) }
38
38
  it { is_expected.to contain(/class BaconTransition/) }
@@ -40,7 +40,7 @@ describe Statesman::ActiveRecordTransitionGenerator, type: :generator do
40
40
 
41
41
  describe "it doesn't create any double-spacing" do
42
42
  before { run_generator %w[Yummy::Bacon Yummy::BaconTransition] }
43
- subject { file('app/models/yummy/bacon_transition.rb') }
43
+ subject { file("app/models/yummy/bacon_transition.rb") }
44
44
 
45
45
  it { is_expected.to_not contain(/\n\n\n/) }
46
46
  end
@@ -4,14 +4,14 @@ require "generators/statesman/migration_generator"
4
4
 
5
5
  describe Statesman::MigrationGenerator, type: :generator do
6
6
  it_behaves_like "a generator" do
7
- let(:migration_name) { 'db/migrate/add_statesman_to_bacon_transitions.rb' }
7
+ let(:migration_name) { "db/migrate/add_statesman_to_bacon_transitions.rb" }
8
8
  end
9
9
 
10
- describe 'the model contains the correct words' do
11
- let(:migration_number) { '5678309' }
10
+ describe "the model contains the correct words" do
11
+ let(:migration_number) { "5678309" }
12
12
 
13
13
  let(:mock_time) do
14
- double('Time', utc: double('UTCTime', strftime: migration_number))
14
+ double("Time", utc: double("UTCTime", strftime: migration_number))
15
15
  end
16
16
 
17
17
  subject do
@@ -3,17 +3,17 @@ require "support/generators_shared_examples"
3
3
  require "generators/statesman/mongoid_transition_generator"
4
4
 
5
5
  describe Statesman::MongoidTransitionGenerator, type: :generator do
6
- describe 'the model contains the correct words' do
6
+ describe "the model contains the correct words" do
7
7
  before { run_generator %w[Yummy::Bacon Yummy::BaconTransition] }
8
- subject { file('app/models/yummy/bacon_transition.rb') }
8
+ subject { file("app/models/yummy/bacon_transition.rb") }
9
9
 
10
10
  it { is_expected.not_to contain(%r{:yummy/bacon}) }
11
11
  it { is_expected.to contain(/class_name: 'Yummy::Bacon'/) }
12
12
  end
13
13
 
14
- describe 'the model contains the correct words' do
14
+ describe "the model contains the correct words" do
15
15
  before { run_generator %w[Bacon BaconTransition] }
16
- subject { file('app/models/bacon_transition.rb') }
16
+ subject { file("app/models/bacon_transition.rb") }
17
17
 
18
18
  it { is_expected.not_to contain(/class_name:/) }
19
19
  it { is_expected.not_to contain(/CreateYummy::Bacon/) }
@@ -132,7 +132,7 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
132
132
  # this spec.
133
133
  MyActiveRecordModel.send(:has_many,
134
134
  :custom_name,
135
- class_name: 'OtherActiveRecordModelTransition')
135
+ class_name: "OtherActiveRecordModelTransition")
136
136
 
137
137
  MyActiveRecordModel.class_eval do
138
138
  def self.transition_class
@@ -1,4 +1,5 @@
1
1
  require "spec_helper"
2
+ require "timecop"
2
3
  require "statesman/adapters/shared_examples"
3
4
  require "statesman/exceptions"
4
5
 
@@ -17,11 +18,11 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
17
18
  context "with unserialized metadata and non json column type" do
18
19
  before do
19
20
  metadata_column = double
20
- allow(metadata_column).to receive_messages(sql_type: '')
21
+ allow(metadata_column).to receive_messages(sql_type: "")
21
22
  allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
22
- { 'metadata' => metadata_column })
23
+ { "metadata" => metadata_column })
23
24
  if ::ActiveRecord.respond_to?(:gem_version) &&
24
- ::ActiveRecord.gem_version >= Gem::Version.new('4.2.0.a')
25
+ ::ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
25
26
  expect(MyActiveRecordModelTransition).
26
27
  to receive(:type_for_attribute).with("metadata").
27
28
  and_return(ActiveRecord::Type::Value.new)
@@ -42,20 +43,20 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
42
43
  context "with serialized metadata and json column type" do
43
44
  before do
44
45
  metadata_column = double
45
- allow(metadata_column).to receive_messages(sql_type: 'json')
46
+ allow(metadata_column).to receive_messages(sql_type: "json")
46
47
  allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
47
- { 'metadata' => metadata_column })
48
+ { "metadata" => metadata_column })
48
49
  if ::ActiveRecord.respond_to?(:gem_version) &&
49
- ::ActiveRecord.gem_version >= Gem::Version.new('4.2.0.a')
50
+ ::ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
50
51
  serialized_type = ::ActiveRecord::Type::Serialized.new(
51
- '', ::ActiveRecord::Coders::JSON
52
+ "", ::ActiveRecord::Coders::JSON
52
53
  )
53
54
  expect(MyActiveRecordModelTransition).
54
55
  to receive(:type_for_attribute).with("metadata").
55
56
  and_return(serialized_type)
56
57
  else
57
58
  expect(MyActiveRecordModelTransition).
58
- to receive_messages(serialized_attributes: { 'metadata' => '' })
59
+ to receive_messages(serialized_attributes: { "metadata" => "" })
59
60
  end
60
61
  end
61
62
 
@@ -70,20 +71,20 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
70
71
  context "with serialized metadata and jsonb column type" do
71
72
  before do
72
73
  metadata_column = double
73
- allow(metadata_column).to receive_messages(sql_type: 'jsonb')
74
+ allow(metadata_column).to receive_messages(sql_type: "jsonb")
74
75
  allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
75
- { 'metadata' => metadata_column })
76
+ { "metadata" => metadata_column })
76
77
  if ::ActiveRecord.respond_to?(:gem_version) &&
77
- ::ActiveRecord.gem_version >= Gem::Version.new('4.2.0.a')
78
+ ::ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
78
79
  serialized_type = ::ActiveRecord::Type::Serialized.new(
79
- '', ::ActiveRecord::Coders::JSON
80
+ "", ::ActiveRecord::Coders::JSON
80
81
  )
81
82
  expect(MyActiveRecordModelTransition).
82
83
  to receive(:type_for_attribute).with("metadata").
83
84
  and_return(serialized_type)
84
85
  else
85
86
  expect(MyActiveRecordModelTransition).
86
- to receive_messages(serialized_attributes: { 'metadata' => '' })
87
+ to receive_messages(serialized_attributes: { "metadata" => "" })
87
88
  end
88
89
  end
89
90
 
@@ -125,7 +126,7 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
125
126
  context "ActiveRecord::RecordNotUnique unrelated to this transition" do
126
127
  let(:error) do
127
128
  if ::ActiveRecord.respond_to?(:gem_version) &&
128
- ::ActiveRecord.gem_version >= Gem::Version.new('4.0.0')
129
+ ::ActiveRecord.gem_version >= Gem::Version.new("4.0.0")
129
130
  ActiveRecord::RecordNotUnique.new("unrelated")
130
131
  else
131
132
  ActiveRecord::RecordNotUnique.new("unrelated", nil)
@@ -157,6 +158,11 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
157
158
  from(true).to be_falsey
158
159
  end
159
160
 
161
+ it "touches the previous transition's updated_at timestamp" do
162
+ expect { Timecop.freeze(Time.now + 5.seconds) { create } }.
163
+ to(change { previous_transition.reload.updated_at })
164
+ end
165
+
160
166
  context "and a query on the parent model's state is made" do
161
167
  context "in a before action" do
162
168
  it "still has the old state" do
@@ -228,17 +234,35 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
228
234
  described_class.new(MyActiveRecordModelTransition, model, observer)
229
235
  end
230
236
 
231
- before { alternate_adapter.create(:y, :z, []) }
232
-
233
237
  it "still returns the cached value" do
238
+ alternate_adapter.create(:y, :z, [])
239
+
234
240
  expect_any_instance_of(MyActiveRecordModel).
235
241
  to receive(:my_active_record_model_transitions).never
236
242
  expect(adapter.last.to_state).to eq("y")
237
243
  end
238
244
 
239
- context "when explitly not using the cache" do
240
- it "still returns the cached value" do
241
- expect(adapter.last(force_reload: true).to_state).to eq("z")
245
+ context "when explicitly not using the cache" do
246
+ context "when the transitions are in memory" do
247
+ before do
248
+ model.my_active_record_model_transitions.load
249
+ alternate_adapter.create(:y, :z, [])
250
+ end
251
+
252
+ it "reloads the value" do
253
+ expect(adapter.last(force_reload: true).to_state).to eq("z")
254
+ end
255
+ end
256
+
257
+ context "when the transitions are not in memory" do
258
+ before do
259
+ model.my_active_record_model_transitions.reset
260
+ alternate_adapter.create(:y, :z, [])
261
+ end
262
+
263
+ it "reloads the value" do
264
+ expect(adapter.last(force_reload: true).to_state).to eq("z")
265
+ end
242
266
  end
243
267
  end
244
268
  end
@@ -48,5 +48,35 @@ describe Statesman::Adapters::Mongoid, mongo: true do
48
48
  end
49
49
  end
50
50
  end
51
+
52
+ context "when a new transition has been created elsewhere" do
53
+ let(:alternate_adapter) do
54
+ described_class.new(MyMongoidModelTransition, model, observer)
55
+ end
56
+
57
+ context "when explicitly not using the cache" do
58
+ context "when the transitions are in memory" do
59
+ before do
60
+ model.my_mongoid_model_transitions.entries
61
+ alternate_adapter.create(:y, :z)
62
+ end
63
+
64
+ it "reloads the value" do
65
+ expect(adapter.last(force_reload: true).to_state).to eq("z")
66
+ end
67
+ end
68
+
69
+ context "when the transitions are not in memory" do
70
+ before do
71
+ model.my_mongoid_model_transitions.reset
72
+ alternate_adapter.create(:y, :z)
73
+ end
74
+
75
+ it "reloads the value" do
76
+ expect(adapter.last(force_reload: true).to_state).to eq("z")
77
+ end
78
+ end
79
+ end
80
+ end
51
81
  end
52
82
  end
@@ -11,7 +11,6 @@ require "spec_helper"
11
11
  # history: Returns the full transition history
12
12
  # last: Returns the latest transition history item
13
13
  #
14
- # rubocop:disable Metrics/LineLength
15
14
  # NOTE This line cannot reasonably be shortened.
16
15
  shared_examples_for "an adapter" do |adapter_class, transition_class, options = {}|
17
16
  # rubocop:enable Metrics/LineLength
@@ -450,7 +450,7 @@ describe Statesman::Machine do
450
450
 
451
451
  context "with one possible state" do
452
452
  before { instance.transition_to!(:y) }
453
- it { is_expected.to eq(['z']) }
453
+ it { is_expected.to eq(["z"]) }
454
454
 
455
455
  context "guarded using metadata" do
456
456
  before do
@@ -459,7 +459,7 @@ describe Statesman::Machine do
459
459
  end
460
460
  end
461
461
 
462
- it { is_expected.to eq(['z']) }
462
+ it { is_expected.to eq(["z"]) }
463
463
  end
464
464
 
465
465
  context "excluded by guard using metadata" do
@@ -666,12 +666,12 @@ describe Statesman::Machine do
666
666
  context "when a non statesman exception is raised" do
667
667
  before do
668
668
  allow(instance).to receive(:transition_to!).
669
- and_raise(RuntimeError, 'user defined exception')
669
+ and_raise(RuntimeError, "user defined exception")
670
670
  end
671
671
 
672
672
  it "should not rescue the exception" do
673
673
  expect { instance.transition_to(:some_state, metadata) }.
674
- to raise_error(RuntimeError, 'user defined exception')
674
+ to raise_error(RuntimeError, "user defined exception")
675
675
  end
676
676
  end
677
677
  end
@@ -59,10 +59,10 @@ class CreateMyActiveRecordModelTransitionMigration < MIGRATION_CLASS
59
59
  t.integer :sort_key
60
60
 
61
61
  # MySQL doesn't allow default values on text fields
62
- if ActiveRecord::Base.connection.adapter_name == 'Mysql2'
62
+ if ActiveRecord::Base.connection.adapter_name == "Mysql2"
63
63
  t.text :metadata
64
64
  else
65
- t.text :metadata, default: '{}'
65
+ t.text :metadata, default: "{}"
66
66
  end
67
67
 
68
68
  if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
@@ -137,10 +137,10 @@ class CreateOtherActiveRecordModelTransitionMigration < MIGRATION_CLASS
137
137
  t.integer :sort_key
138
138
 
139
139
  # MySQL doesn't allow default values on text fields
140
- if ActiveRecord::Base.connection.adapter_name == 'Mysql2'
140
+ if ActiveRecord::Base.connection.adapter_name == "Mysql2"
141
141
  t.text :metadata
142
142
  else
143
- t.text :metadata, default: '{}'
143
+ t.text :metadata, default: "{}"
144
144
  end
145
145
 
146
146
  if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
@@ -234,10 +234,10 @@ class CreateNamespacedARModelTransitionMigration < MIGRATION_CLASS
234
234
  t.integer :sort_key
235
235
 
236
236
  # MySQL doesn't allow default values on text fields
237
- if ActiveRecord::Base.connection.adapter_name == 'Mysql2'
237
+ if ActiveRecord::Base.connection.adapter_name == "Mysql2"
238
238
  t.text :metadata
239
239
  else
240
- t.text :metadata, default: '{}'
240
+ t.text :metadata, default: "{}"
241
241
  end
242
242
 
243
243
  if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
@@ -250,7 +250,7 @@ class CreateNamespacedARModelTransitionMigration < MIGRATION_CLASS
250
250
  end
251
251
 
252
252
  add_index :my_namespace_my_active_record_model_transitions, :sort_key,
253
- unique: true, name: 'my_namespaced_key'
253
+ unique: true, name: "my_namespaced_key"
254
254
 
255
255
  if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes?
256
256
  add_index :my_namespace_my_active_record_model_transitions,
@@ -2,22 +2,22 @@ require "rails/version"
2
2
  require "rspec/rails"
3
3
  require "ammeter/init"
4
4
 
5
- TMP_GENERATOR_PATH = File.expand_path('../generator-tmp', __FILE__)
5
+ TMP_GENERATOR_PATH = File.expand_path("../generator-tmp", __FILE__)
6
6
 
7
- shared_examples 'a generator' do
7
+ shared_examples "a generator" do
8
8
  destination TMP_GENERATOR_PATH
9
9
  before { prepare_destination }
10
10
  let(:gen) { generator %w[Yummy::Bacon Yummy::BaconTransition] }
11
11
 
12
- it 'invokes create_model_file method' do
12
+ it "invokes create_model_file method" do
13
13
  expect(gen).to receive(:create_model_file)
14
14
  gen.invoke_all
15
15
  end
16
16
 
17
- describe 'it runs the generator and check things out' do
17
+ describe "it runs the generator and check things out" do
18
18
  before { run_generator %w[Yummy::Bacon Yummy::BaconTransition] }
19
19
 
20
- describe 'it generates a correctly named file' do
20
+ describe "it generates a correctly named file" do
21
21
  subject { file(migration_name) }
22
22
  it { is_expected.to be_a_migration }
23
23
  end
@@ -1,7 +1,7 @@
1
- require 'mongoid'
1
+ require "mongoid"
2
2
 
3
3
  Mongoid.configure do |config|
4
- config.connect_to('statesman_test')
4
+ config.connect_to("statesman_test")
5
5
  end
6
6
 
7
7
  class MyMongoidModel
data/statesman.gemspec CHANGED
@@ -1,13 +1,13 @@
1
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path("../lib", __FILE__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require 'statesman/version'
3
+ require "statesman/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "statesman"
7
7
  spec.version = Statesman::VERSION
8
8
  spec.authors = ["GoCardless"]
9
9
  spec.email = ["developers@gocardless.com"]
10
- spec.description = 'A statesman-like state machine library'
10
+ spec.description = "A statesman-like state machine library"
11
11
  spec.summary = spec.description
12
12
  spec.homepage = "https://github.com/gocardless/statesman"
13
13
  spec.license = "MIT"
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
19
 
20
- spec.required_ruby_version = '>= 2.2'
20
+ spec.required_ruby_version = ">= 2.2"
21
21
 
22
22
  spec.add_development_dependency "ammeter", "~> 1.1"
23
23
  spec.add_development_dependency "bundler", "~> 1.3"
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.add_development_dependency "rspec-its", "~> 1.1"
30
30
  spec.add_development_dependency "rspec-rails", "~> 3.1"
31
31
  spec.add_development_dependency "rspec_junit_formatter", "~> 0.3.0"
32
- spec.add_development_dependency "rubocop", "~> 0.51.0"
32
+ spec.add_development_dependency "rubocop", "~> 0.52.0"
33
33
  spec.add_development_dependency "sqlite3", "~> 1.3"
34
+ spec.add_development_dependency "timecop", "~> 0.9.1"
34
35
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statesman
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GoCardless
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-28 00:00:00.000000000 Z
11
+ date: 2018-01-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ammeter
@@ -156,14 +156,14 @@ dependencies:
156
156
  requirements:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: 0.51.0
159
+ version: 0.52.0
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: 0.51.0
166
+ version: 0.52.0
167
167
  - !ruby/object:Gem::Dependency
168
168
  name: sqlite3
169
169
  requirement: !ruby/object:Gem::Requirement
@@ -178,6 +178,20 @@ dependencies:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
180
  version: '1.3'
181
+ - !ruby/object:Gem::Dependency
182
+ name: timecop
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 0.9.1
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 0.9.1
181
195
  description: A statesman-like state machine library
182
196
  email:
183
197
  - developers@gocardless.com
@@ -265,7 +279,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
265
279
  version: '0'
266
280
  requirements: []
267
281
  rubyforge_project:
268
- rubygems_version: 2.6.13
282
+ rubygems_version: 2.7.3
269
283
  signing_key:
270
284
  specification_version: 4
271
285
  summary: A statesman-like state machine library