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.
- checksums.yaml +5 -5
- data/.rubocop.yml +3 -26
- data/.rubocop_todo.yml +7 -8
- data/CHANGELOG.md +4 -0
- data/Gemfile +5 -4
- data/Guardfile +3 -3
- data/README.md +1 -1
- data/lib/generators/statesman/active_record_transition_generator.rb +1 -1
- data/lib/generators/statesman/generator_helpers.rb +1 -1
- data/lib/generators/statesman/migration_generator.rb +1 -1
- data/lib/generators/statesman/mongoid_transition_generator.rb +1 -1
- data/lib/statesman.rb +7 -7
- data/lib/statesman/adapters/active_record.rb +18 -12
- data/lib/statesman/adapters/memory.rb +4 -1
- data/lib/statesman/adapters/mongoid.rb +2 -2
- data/lib/statesman/machine.rb +2 -6
- data/lib/statesman/version.rb +1 -1
- data/lib/tasks/statesman.rake +9 -3
- data/spec/fixtures/add_constraints_to_most_recent_for_bacon_transitions_with_partial_index.rb +6 -1
- data/spec/fixtures/add_constraints_to_most_recent_for_bacon_transitions_without_partial_index.rb +5 -1
- data/spec/generators/statesman/active_record_transition_generator_spec.rb +9 -9
- data/spec/generators/statesman/migration_generator_spec.rb +4 -4
- data/spec/generators/statesman/mongoid_transition_generator_spec.rb +4 -4
- data/spec/statesman/adapters/active_record_queries_spec.rb +1 -1
- data/spec/statesman/adapters/active_record_spec.rb +43 -19
- data/spec/statesman/adapters/mongoid_spec.rb +30 -0
- data/spec/statesman/adapters/shared_examples.rb +0 -1
- data/spec/statesman/machine_spec.rb +4 -4
- data/spec/support/active_record.rb +7 -7
- data/spec/support/generators_shared_examples.rb +5 -5
- data/spec/support/mongoid.rb +2 -2
- data/statesman.gemspec +6 -5
- metadata +19 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4ea9661229864328ca481ff132837554323967d265616d38c295c5fa94ca9103
|
4
|
+
data.tar.gz: 8974f12e8e70d925f0b35e3753a9977d1ff94a1a76bf05c0a967535037ce6a24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
4
|
+
gc_ruboconfig: rubocop.yml
|
4
5
|
|
5
6
|
AllCops:
|
6
|
-
|
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-
|
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:
|
10
|
-
|
11
|
-
|
12
|
-
Max: 42
|
9
|
+
# Offense count: 5
|
10
|
+
Metrics/AbcSize:
|
11
|
+
Max: 18
|
13
12
|
|
14
|
-
# Offense count:
|
13
|
+
# Offense count: 4
|
15
14
|
# Configuration parameters: CountComments.
|
16
|
-
Metrics/
|
17
|
-
Max:
|
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
|
-
|
14
|
-
|
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:
|
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(
|
7
|
+
watch("spec/spec_helper.rb") { "spec" }
|
8
8
|
end
|
9
9
|
|
10
|
-
guard :rubocop, all_on_start: true, cli: [
|
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
@@ -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(
|
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)
|
@@ -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(
|
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(
|
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,
|
3
|
-
autoload :Machine,
|
4
|
-
autoload :Callback,
|
5
|
-
autoload :Guard,
|
6
|
-
autoload :Utils,
|
7
|
-
autoload :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
|
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 ==
|
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[
|
22
|
+
column_type = transition_class.columns_hash["metadata"].sql_type
|
23
23
|
if !serialized && !JSON_COLUMN_TYPES.include?(column_type)
|
24
|
-
raise UnserializedMetadataError
|
24
|
+
raise UnserializedMetadataError, transition_class.name
|
25
25
|
elsif serialized && JSON_COLUMN_TYPES.include?(column_type)
|
26
|
-
raise IncompatibleSerializationError
|
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[
|
98
|
-
|
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
|
-
|
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(
|
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?(
|
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?(
|
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
|
|
data/lib/statesman/machine.rb
CHANGED
@@ -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
|
-
|
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)
|
data/lib/statesman/version.rb
CHANGED
data/lib/tasks/statesman.rake
CHANGED
@@ -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).
|
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
|
data/spec/fixtures/add_constraints_to_most_recent_for_bacon_transitions_with_partial_index.rb
CHANGED
@@ -2,7 +2,12 @@ class AddConstraintsToMostRecentForBaconTransitions < ActiveRecord::Migration
|
|
2
2
|
disable_ddl_transaction!
|
3
3
|
|
4
4
|
def up
|
5
|
-
add_index :bacon_transitions,
|
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
|
|
data/spec/fixtures/add_constraints_to_most_recent_for_bacon_transitions_without_partial_index.rb
CHANGED
@@ -2,7 +2,11 @@ class AddConstraintsToMostRecentForBaconTransitions < ActiveRecord::Migration
|
|
2
2
|
disable_ddl_transaction!
|
3
3
|
|
4
4
|
def up
|
5
|
-
add_index :bacon_transitions,
|
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) {
|
7
|
+
let(:migration_name) { "db/migrate/create_bacon_transitions.rb" }
|
8
8
|
end
|
9
9
|
|
10
|
-
describe
|
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(
|
17
|
-
let(:time) {
|
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
|
24
|
+
describe "properly adds class names" do
|
25
25
|
before { run_generator %w[Yummy::Bacon Yummy::BaconTransition] }
|
26
|
-
subject { file(
|
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
|
33
|
+
describe "properly formats without class names" do
|
34
34
|
before { run_generator %w[Bacon BaconTransition] }
|
35
|
-
subject { file(
|
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(
|
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) {
|
7
|
+
let(:migration_name) { "db/migrate/add_statesman_to_bacon_transitions.rb" }
|
8
8
|
end
|
9
9
|
|
10
|
-
describe
|
11
|
-
let(:migration_number) {
|
10
|
+
describe "the model contains the correct words" do
|
11
|
+
let(:migration_number) { "5678309" }
|
12
12
|
|
13
13
|
let(:mock_time) do
|
14
|
-
double(
|
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
|
6
|
+
describe "the model contains the correct words" do
|
7
7
|
before { run_generator %w[Yummy::Bacon Yummy::BaconTransition] }
|
8
|
-
subject { file(
|
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
|
14
|
+
describe "the model contains the correct words" do
|
15
15
|
before { run_generator %w[Bacon BaconTransition] }
|
16
|
-
subject { file(
|
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:
|
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
|
-
{
|
23
|
+
{ "metadata" => metadata_column })
|
23
24
|
if ::ActiveRecord.respond_to?(:gem_version) &&
|
24
|
-
::ActiveRecord.gem_version >= Gem::Version.new(
|
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:
|
46
|
+
allow(metadata_column).to receive_messages(sql_type: "json")
|
46
47
|
allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
|
47
|
-
{
|
48
|
+
{ "metadata" => metadata_column })
|
48
49
|
if ::ActiveRecord.respond_to?(:gem_version) &&
|
49
|
-
::ActiveRecord.gem_version >= Gem::Version.new(
|
50
|
+
::ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
|
50
51
|
serialized_type = ::ActiveRecord::Type::Serialized.new(
|
51
|
-
|
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: {
|
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:
|
74
|
+
allow(metadata_column).to receive_messages(sql_type: "jsonb")
|
74
75
|
allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
|
75
|
-
{
|
76
|
+
{ "metadata" => metadata_column })
|
76
77
|
if ::ActiveRecord.respond_to?(:gem_version) &&
|
77
|
-
::ActiveRecord.gem_version >= Gem::Version.new(
|
78
|
+
::ActiveRecord.gem_version >= Gem::Version.new("4.2.0.a")
|
78
79
|
serialized_type = ::ActiveRecord::Type::Serialized.new(
|
79
|
-
|
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: {
|
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(
|
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
|
240
|
-
|
241
|
-
|
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([
|
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([
|
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,
|
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,
|
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 ==
|
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 ==
|
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 ==
|
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:
|
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(
|
5
|
+
TMP_GENERATOR_PATH = File.expand_path("../generator-tmp", __FILE__)
|
6
6
|
|
7
|
-
shared_examples
|
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
|
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
|
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
|
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
|
data/spec/support/mongoid.rb
CHANGED
data/statesman.gemspec
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
lib = File.expand_path(
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
require
|
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 =
|
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 =
|
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.
|
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.
|
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:
|
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.
|
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.
|
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.
|
282
|
+
rubygems_version: 2.7.3
|
269
283
|
signing_key:
|
270
284
|
specification_version: 4
|
271
285
|
summary: A statesman-like state machine library
|