statesman 3.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
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