diff --git a/.travis.yml b/.travis.yml
index 91b2ae6..c241a09 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,7 +14,9 @@ rvm:
- jruby-9.0.4.0
- rbx-2.5.8
-services: mongodb
+services:
+ - mongodb
+ - redis-server
gemfile:
- gemfiles/rails_3.2_stable.gemfile
diff --git a/README.md b/README.md
index e704241..6ed77bc 100644
--- a/README.md
+++ b/README.md
@@ -663,6 +663,21 @@ class Job
end
```
+### Redis
+
+AASM also supports persistence in Redis.
+Make sure to include Redis::Objects before you include AASM.
+
+```ruby
+class User
+ include Redis::Objects
+ include AASM
+
+ aasm do
+ end
+end
+```
+
### Automatic Scopes
AASM will automatically create scope methods for each state in the model.
diff --git a/gemfiles/rails_4.0.gemfile b/gemfiles/rails_4.0.gemfile
index 3209bf9..13fcc50 100644
--- a/gemfiles/rails_4.0.gemfile
+++ b/gemfiles/rails_4.0.gemfile
@@ -10,5 +10,6 @@ gem 'mongoid', '~>4.0' if Gem::Version.create(RUBY_VERSION.dup) >= Gem::Version.
gem 'sequel'
gem 'dynamoid', '~> 1', :platforms => :ruby
gem 'aws-sdk', '~>2', :platforms => :ruby
+gem "redis-objects"
gemspec :path => "../"
diff --git a/gemfiles/rails_4.1.gemfile b/gemfiles/rails_4.1.gemfile
index 1a00497..f407c32 100644
--- a/gemfiles/rails_4.1.gemfile
+++ b/gemfiles/rails_4.1.gemfile
@@ -10,5 +10,6 @@ gem 'mongoid', '~>4.0' if Gem::Version.create(RUBY_VERSION.dup) >= Gem::Version.
gem 'sequel'
gem 'dynamoid', '~> 1', :platforms => :ruby
gem 'aws-sdk', '~>2', :platforms => :ruby
+gem "redis-objects"
gemspec :path => "../"
diff --git a/gemfiles/rails_4.2_mongo_mapper.gemfile b/gemfiles/rails_4.2_mongo_mapper.gemfile
index 8156a55..604a7ee 100644
--- a/gemfiles/rails_4.2_mongo_mapper.gemfile
+++ b/gemfiles/rails_4.2_mongo_mapper.gemfile
@@ -12,5 +12,6 @@ gem 'mongo_mapper'
gem 'bson_ext', :platforms => :ruby
gem 'dynamoid', '~> 1', :platforms => :ruby
gem 'aws-sdk', '~>2', :platforms => :ruby
+gem "redis-objects"
gemspec :path => "../"
diff --git a/lib/aasm/persistence.rb b/lib/aasm/persistence.rb
index 21bfd0a..8adbbef 100644
--- a/lib/aasm/persistence.rb
+++ b/lib/aasm/persistence.rb
@@ -18,6 +18,8 @@ module AASM
include_persistence base, :dynamoid
elsif hierarchy.include?("CDQManagedObject")
include_persistence base, :core_data_query
+ elsif hierarchy.include?("Redis::Objects")
+ include_persistence base, :redis
else
include_persistence base, :plain
end
diff --git a/lib/aasm/persistence/redis_persistence.rb b/lib/aasm/persistence/redis_persistence.rb
new file mode 100644
index 0000000..bdb0a19
--- /dev/null
+++ b/lib/aasm/persistence/redis_persistence.rb
@@ -0,0 +1,109 @@
+require_relative "base"
+
+module AASM
+ module Persistence
+ module RedisPersistence
+
+ def self.included(base)
+ base.send(:include, AASM::Persistence::Base)
+ base.send(:include, AASM::Persistence::RedisPersistence::InstanceMethods)
+ end
+
+ module InstanceMethods
+ # Add the inital value to intiializer
+ #
+ # redis-objects removed the key from redis when set to nil
+ def initialize(*args)
+ super
+ state = send(self.class.aasm.attribute_name)
+ state.value = aasm.determine_state_name(self.class.aasm.initial_state)
+ end
+ # Returns the value of the aasm.attribute_name - called from aasm.current_state
+ #
+ # If it's a new record, and the aasm state column is blank it returns the initial state
+ #
+ # class Foo
+ # include Redis::Objects
+ # 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.attribute_name)
+
+ if state.value.nil?
+ nil
+ else
+ state.value.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.attribute_name).to_s.strip.empty?
+ end
+
+ # Writes state 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.attribute_name
+ self.send("#{aasm_column}=", state)
+ end
+
+ # Writes state 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.attribute_name}=", state)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/persistence/redis_persistence_spec.rb b/spec/unit/persistence/redis_persistence_spec.rb
new file mode 100644
index 0000000..2448c4d
--- /dev/null
+++ b/spec/unit/persistence/redis_persistence_spec.rb
@@ -0,0 +1,77 @@
+
+describe 'redis' do
+ begin
+ require 'redis-objects'
+ require 'logger'
+ require 'spec_helper'
+
+ before(:all) do
+ Redis.current = Redis.new(host: '127.0.0.1', port: 6379)
+
+ @model = Class.new do
+ attr_accessor :default
+
+ include Redis::Objects
+ include AASM
+
+ value :status
+
+ def id
+ 1
+ end
+
+ 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 allow a nil state" do
+ model.status = nil
+ expect(model.aasm.current_state).to be_nil
+ 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.attribute_name).to eq(:status)
+ expect(Class.new(@model).aasm.attribute_name).to eq(:status)
+ end
+ end
+
+ rescue LoadError
+ puts "Not running Sequel specs because sequel gem is not installed!!!"
+ end
+end