I am trying to migrate some activerecord code to sequel, and this makes
aasm work for sequel out of the box. This is not yet feature complete
as for activerecord, missing some scope related features. However this
could be a start, and to be honest, since we're not using them, this is
far good enough for us.

I have tried my best to make this aligned with activerecord and mongoid,
and I've overridden `aasm_read_state` for sequel, because there's no
`new_record?` but only `new?` in sequel, and there's no `blank?`,
either. (which I believe is provided in activesupport, which sequel
does not depend on, and I don't want to force people to depend on that.)

Thanks for considering.
This commit is contained in:
Lin Jen-Shin 2014-04-22 22:28:08 +08:00
parent 421b60fb1f
commit c1461fbf6c
3 changed files with 214 additions and 0 deletions

View File

@ -12,6 +12,9 @@ module AASM
elsif hierarchy.include?("Mongoid::Document")
require_files_for(:mongoid)
base.send(:include, AASM::Persistence::MongoidPersistence)
elsif hierarchy.include?("Sequel::Model")
require_files_for(:sequel)
base.send(:include, AASM::Persistence::SequelPersistence)
end
end

View File

@ -0,0 +1,108 @@
module AASM
module Persistence
module SequelPersistence
def self.included(base)
base.send(:include, AASM::Persistence::Base)
base.send(:include, AASM::Persistence::SequelPersistence::InstanceMethods)
end
module InstanceMethods
def before_validation
aasm_ensure_initial_state
super
end
def before_create
aasm_ensure_initial_state
super
end
# Returns the value of the aasm_column - called from <tt>aasm.current_state</tt>
#
# If it's a new record, and the aasm state column is blank it returns the initial state
#
# class Foo < Sequel::Model
# include AASM
# aasm :column => :status do
# state :opened
# state :closed
# end
# end
#
# foo = Foo.new
# foo.current_state # => :opened
# foo.close
# foo.current_state # => :closed
#
# foo = Foo[1]
# foo.current_state # => :opened
# foo.aasm_state = nil
# foo.current_state # => nil
#
# NOTE: intended to be called from an event
#
# This allows for nil aasm states - be sure to add validation to your model
def aasm_read_state
state = send(self.class.aasm_column)
if new? && state.to_s.strip.empty?
aasm.determine_state_name(self.class.aasm.initial_state)
elsif state.nil?
nil
else
state.to_sym
end
end
# Ensures that if the aasm_state column is nil and the record is new
# that the initial state gets populated before validation on create
#
# foo = Foo.new
# foo.aasm_state # => nil
# foo.valid?
# foo.aasm_state # => "open" (where :open is the initial state)
#
#
# foo = Foo.find(:first)
# foo.aasm_state # => 1
# foo.aasm_state = nil
# foo.valid?
# foo.aasm_state # => nil
#
def aasm_ensure_initial_state
aasm.enter_initial_state if
send(self.class.aasm_column).to_s.strip.empty?
end
# Writes <tt>state</tt> to the state column and persists it to the database
#
# foo = Foo[1]
# foo.aasm.current_state # => :opened
# foo.close!
# foo.aasm.current_state # => :closed
# Foo[1].aasm.current_state # => :closed
#
# NOTE: intended to be called from an event
def aasm_write_state state
aasm_column = self.class.aasm_column
update_ony({aasm_column => state.to_s}, aasm_column)
end
# Writes <tt>state</tt> to the state column, but does not persist it to the database
#
# foo = Foo[1]
# foo.aasm.current_state # => :opened
# foo.close
# foo.aasm.current_state # => :closed
# Foo[1].aasm.current_state # => :opened
# foo.save
# foo.aasm.current_state # => :closed
# Foo[1].aasm.current_state # => :closed
#
# NOTE: intended to be called from an event
def aasm_write_state_without_persistence state
send("#{self.class.aasm_column}=", state.to_s)
end
end
end
end
end

View File

@ -0,0 +1,103 @@
describe 'sequel' do
begin
require 'sequel'
require 'logger'
require 'spec_helper'
before(:all) do
db = Sequel.sqlite
# if you want to see the statements while running the spec enable the following line
# db.loggers << Logger.new($stderr)
db.create_table(:models) do
primary_key :id
String :status
end
@model = Class.new(Sequel::Model(db)) do
set_dataset(:models)
attr_accessor :default
include AASM
aasm :column => :status
aasm do
state :alpha, :initial => true
state :beta
state :gamma
event :release do
transitions :from => [:alpha, :beta, :gamma], :to => :beta
end
end
end
end
describe "instance methods" do
let(:model) {@model.new}
it "should respond to aasm persistence methods" do
expect(model).to respond_to(:aasm_read_state)
expect(model).to respond_to(:aasm_write_state)
expect(model).to respond_to(:aasm_write_state_without_persistence)
end
it "should return the initial state when new and the aasm field is nil" do
expect(model.aasm.current_state).to eq(:alpha)
end
it "should return the aasm column when new and the aasm field is not nil" do
model.status = "beta"
expect(model.aasm.current_state).to eq(:beta)
end
it "should return the aasm column when not new and the aasm_column is not nil" do
allow(model).to receive(:new?).and_return(false)
model.status = "gamma"
expect(model.aasm.current_state).to eq(:gamma)
end
it "should allow a nil state" do
allow(model).to receive(:new?).and_return(false)
model.status = nil
expect(model.aasm.current_state).to be_nil
end
it "should call aasm_ensure_initial_state on validation before create" do
expect(model).to receive(:aasm_ensure_initial_state).and_return(true)
model.valid?
end
it "should call aasm_ensure_initial_state before create, even if skipping validations" do
expect(model).to receive(:aasm_ensure_initial_state).and_return(true)
model.save(:validate => false)
end
end
describe 'subclasses' do
it "should have the same states as its parent class" do
expect(Class.new(@model).aasm.states).to eq(@model.aasm.states)
end
it "should have the same events as its parent class" do
expect(Class.new(@model).aasm.events).to eq(@model.aasm.events)
end
it "should have the same column as its parent even for the new dsl" do
expect(@model.aasm_column).to eq(:status)
expect(Class.new(@model).aasm_column).to eq(:status)
end
end
describe 'initial states' do
it 'should support conditions' do
@model.aasm do
initial_state lambda{ |m| m.default }
end
expect(@model.new(:default => :beta).aasm.current_state).to eq(:beta)
expect(@model.new(:default => :gamma).aasm.current_state).to eq(:gamma)
end
end
rescue LoadError
puts "Not running Sequel specs because sequel gem if not installed!!!"
end
end