Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sequel adapter #152

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions lib/statesman.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ module Statesman
autoload :Guard, 'statesman/guard'
autoload :Version, 'statesman/version'
module Adapters
autoload :Memory, "statesman/adapters/memory"
autoload :ActiveRecord, "statesman/adapters/active_record"
autoload :Memory, "statesman/adapters/memory"
autoload :ActiveRecord, "statesman/adapters/active_record"
autoload :ActiveRecordTransition,
"statesman/adapters/active_record_transition"
autoload :ActiveRecordQueries,
"statesman/adapters/active_record_queries"
autoload :Mongoid, "statesman/adapters/mongoid"
autoload :MongoidTransition,
"statesman/adapters/mongoid_transition"
autoload :ActiveRecordQueries, "statesman/adapters/active_record_queries"
autoload :Sequel, "statesman/adapters/sequel"
autoload :SequelTransistion, "statesman/adapters/sequel_transition"
autoload :Mongoid, "statesman/adapters/mongoid"
autoload :MongoidTransition, "statesman/adapters/mongoid_transition"
end

# Example:
Expand Down
81 changes: 81 additions & 0 deletions lib/statesman/adapters/sequel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
require_relative "../exceptions"

module Statesman
module Adapters
class Sequel
attr_reader :transition_class, :parent_model, :observer

::Sequel.extension(:inflector)

def initialize(transition_class, parent_model, observer)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method now requires an additional options={} argument to work on the latest version.

@transition_class = transition_class
@parent_model = parent_model
@observer = observer
end

def create(from, to, metadata = {})
create_transition(from.to_s, to.to_s, metadata)
ensure
@last_transition = nil
end

def history
history_dataset.all
end

def last
Copy link

@badosu badosu Apr 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method now accepts a force_reload keyword argument:

def last(force_reload: false)
  @last_transition = nil if force_reload
  @last_transition ||= history_dataset.last
end

this code can be refactored to support Ruby < 2.0

@last_transition ||= history_dataset.last
end

private

def history_dataset
transitions_for_parent.order(:sort_key)
end

def transitions_for_parent
@parent_model.send("#{transition_table_name}_dataset")
end

def next_sort_key
(last && last.sort_key + 10) || 0
end

def create_transition(from, to, metadata)
transition = transition_class.new(
to_state: to,
sort_key: next_sort_key,
metadata: metadata,
parent_model_foreign_key => @parent_model.pk
)

parent_model_class.db.transaction do
@observer.execute(:before, from, to, transition)
transition.save
@last_transition = transition
@observer.execute(:after, from, to, transition)
end

@observer.execute(:after_commit, from, to, transition)

transition
end

def transition_table_name
@transition_table_name ||= @transition_class.table_name
end

def parent_model_class
@parent_model_class ||= @parent_model.class
end

def parent_association_name
parent_model_class.table_name.to_s.singularize.to_sym
end

def parent_model_foreign_key
@transition_class.association_reflection(parent_association_name)[:key]
end
end
end
end
12 changes: 12 additions & 0 deletions lib/statesman/adapters/sequel_transition.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
require "json"
require "sequel"

module Statesman
module Adapters
module SequelTransition
def self.included(base)
base.send(:plugin, :serialization, :json, :metadata)
end
end
end
end
18 changes: 17 additions & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
require "active_record"
require "support/active_record"
require "mongoid"
require 'rspec/its'
require "sequel"
require "rspec/its"

RSpec.configure do |config|
config.raise_errors_for_deprecations!
Expand Down Expand Up @@ -38,6 +39,13 @@
puts "Running with database adapter '#{db_adapter}'"
end

if config.exclusion_filter[:sequel]
puts "Skipping Sequel tests"
else
Sequel::Model.db = Sequel.sqlite
require "support/sequel"
end

config.before(:each, active_record: true) do
tables = %w(my_active_record_models my_active_record_model_transitions)
tables.each do |table_name|
Expand All @@ -57,4 +65,12 @@ def prepare_transitions_table
end
end
end

config.before(:each, sequel: true) do
SequelMigrator.new(Sequel::Model.db).up
end

config.after(:each, sequel: true) do
SequelMigrator.new(Sequel::Model.db).down
end
end
26 changes: 6 additions & 20 deletions spec/statesman/adapters/active_record_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "spec_helper"
require "statesman/adapters/shared_examples"
require "statesman/adapters/adapter_behaviour"
require "statesman/adapters/sql_adapter_behaviour"
require "statesman/exceptions"

describe Statesman::Adapters::ActiveRecord, active_record: true do
Expand All @@ -12,6 +13,10 @@
let(:observer) { double(Statesman::Machine, execute: nil) }
let(:model) { MyActiveRecordModel.create(current_state: :pending) }
it_behaves_like "an adapter", described_class, MyActiveRecordModelTransition
it_behaves_like "a SQL adapter",
described_class, MyActiveRecordModelTransition do
let(:association_name) { :my_active_record_model_transitions }
end

describe "#initialize" do
context "with unserialized metadata and non json column type" do
Expand Down Expand Up @@ -109,25 +114,6 @@
described_class.new(MyActiveRecordModelTransition, model, observer)
end

before { adapter.create(:x, :y) }

context "with a previously looked up transition" do
before { adapter.last }

it "caches the transition" do
expect_any_instance_of(MyActiveRecordModel).
to receive(:my_active_record_model_transitions).never
adapter.last
end

context "and a new transition" do
before { adapter.create(:y, :z, []) }
it "retrieves the new transition from the database" do
expect(adapter.last.to_state).to eq("z")
end
end
end

context "with a pre-fetched transition history" do
before { adapter.create(:x, :y) }
before { model.my_active_record_model_transitions.load_target }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
# initialize: Accepts a transition class, parent model and state_attr.
# transition_class: Returns the transition class object passed to initialize.
# parent_model: Returns the model class object passed to initialize.
# state_attr: Returns the state attribute to set on the parent.
# create: Accepts to_state, before callbacks, after callbacks and
# optional metadata. Creates a new transition class
# instance and saves metadata to it.
Expand All @@ -29,7 +28,7 @@
let(:create) { adapter.create(from, to) }
subject { -> { create } }

it { is_expected.to change(adapter.history, :count).by(1) }
it { is_expected.to change { adapter.history.count }.from(0).to(1) }

context "the new transition" do
subject { create }
Expand Down
2 changes: 1 addition & 1 deletion spec/statesman/adapters/memory_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
require "spec_helper"
require "statesman/adapters/shared_examples"
require "statesman/adapters/adapter_behaviour"
require "statesman/adapters/memory_transition"

describe Statesman::Adapters::Memory do
Expand Down
2 changes: 1 addition & 1 deletion spec/statesman/adapters/mongoid_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
require "spec_helper"
require "statesman/adapters/shared_examples"
require "statesman/adapters/adapter_behaviour"
require "statesman/exceptions"
require "support/mongoid"
require "mongoid"
Expand Down
18 changes: 18 additions & 0 deletions spec/statesman/adapters/sequel_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require "spec_helper"
require "statesman/adapters/adapter_behaviour"
require "statesman/adapters/sql_adapter_behaviour"

describe Statesman::Adapters::Sequel, sequel: true do
before do
MySequelModel.dataset = MySequelModel.dataset
MySequelModelTransition.dataset = MySequelModelTransition.dataset
end

let(:observer) { double(Statesman::Machine, execute: nil) }
let(:model) { MySequelModel.create(current_state: :pending) }

it_behaves_like "an adapter", described_class, MySequelModelTransition
it_behaves_like "a SQL adapter", described_class, MySequelModelTransition do
let(:association_name) { :my_sequel_model_transitions_dataset }
end
end
25 changes: 25 additions & 0 deletions spec/statesman/adapters/sql_adapter_behaviour.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require "spec_helper"

shared_examples_for "a SQL adapter" do |adapter_class, transition_class|
describe "#last" do
let(:adapter) { adapter_class.new(transition_class, model, observer) }

before { adapter.create(:x, :y) }

context "with a previously looked up transition" do
before { adapter.last }

it "caches the transition" do
expect_any_instance_of(model.class).to receive(association_name).never
adapter.last
end

context "and a new transition" do
before { adapter.create(:y, :z, []) }
it "retrieves the new transition from the database" do
expect(adapter.last.to_state).to eq("z")
end
end
end
end
end
29 changes: 29 additions & 0 deletions spec/support/sequel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require "json"
require "sequel"
require_relative "./sequel_migrations"
require_relative "../../lib/statesman/adapters/sequel_transition"

class SequelStateMachine
include Statesman::Machine

state :initial, initial: true
state :succeeded
state :failed

transition from: :initial, to: [:succeeded, :failed]
transition from: :failed, to: :initial
end

class MySequelModel < Sequel::Model
one_to_many :my_sequel_model_transitions

def state_machine
@state_machine ||= SequelStateMachine.new(
self, transition_class: MySequelModelTransition)
end
end

class MySequelModelTransition < Sequel::Model
include Statesman::Adapters::SequelTransition
many_to_one :my_sequel_model
end
41 changes: 41 additions & 0 deletions spec/support/sequel_migrations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
require "sequel"

class SequelMigrator
MODEL_TABLE = "my_sequel_models"
TRANSITION_TABLE = "my_sequel_model_transitions"

def initialize(db)
@db = db
end

def up
down
create_model_table
create_transition_table
end

def down
[TRANSITION_TABLE, MODEL_TABLE].each do |table_name|
@db.execute("DROP TABLE IF EXISTS #{table_name};")
end
end

private

def create_model_table
@db.create_table(:my_sequel_models) do
primary_key :id, type: Bignum
String :current_state
end
end

def create_transition_table
@db.create_table(:my_sequel_model_transitions) do
primary_key :id, type: Bignum
String :to_state
foreign_key :my_sequel_model_id, :my_sequel_models
Bignum :sort_key, index: true, unique: true
String :metadata
end
end
end
1 change: 1 addition & 0 deletions statesman.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "sqlite3", "~> 1.3"
spec.add_development_dependency "mongoid", ">= 3.1"
spec.add_development_dependency "activerecord", ">= 3.2"
spec.add_development_dependency "sequel", "~> 4.20.0"
spec.add_development_dependency "pg", "~> 0.18"
spec.add_development_dependency "mysql2", "~> 0.3"
spec.add_development_dependency "ammeter", "~> 1.1"
Expand Down