From 8e8c6baeafb46c1ed321697462f63d6722a86cd0 Mon Sep 17 00:00:00 2001 From: Victor Shcherbakov Date: Tue, 11 Jul 2017 13:42:49 +0300 Subject: [PATCH] Update Redis support Fix #480 --- Appraisals | 2 + README.md | 6 +- gemfiles/rails_4.2.gemfile | 1 + gemfiles/rails_5.0.gemfile | 1 + lib/aasm/persistence/redis_persistence.rb | 27 +++--- spec/models/redis/complex_redis_example.rb | 40 +++++++++ spec/models/redis/redis_multiple.rb | 20 +++++ spec/models/redis/redis_simple.rb | 20 +++++ spec/spec_helpers/redis.rb | 8 ++ .../redis_persistence_multiple_spec.rb | 88 +++++++++++++++++++ .../persistence/redis_persistence_spec.rb | 30 ++----- 11 files changed, 205 insertions(+), 38 deletions(-) create mode 100644 spec/models/redis/complex_redis_example.rb create mode 100644 spec/models/redis/redis_multiple.rb create mode 100644 spec/models/redis/redis_simple.rb create mode 100644 spec/unit/persistence/redis_persistence_multiple_spec.rb diff --git a/Appraisals b/Appraisals index fea49a6..b8a606d 100644 --- a/Appraisals +++ b/Appraisals @@ -24,6 +24,7 @@ appraise 'rails_4.2' do gem 'sequel' gem 'dynamoid', '~> 1', :platforms => :ruby gem 'aws-sdk', '~>2', :platforms => :ruby + gem 'redis-objects' end appraise 'rails_4.2_mongoid_5' do @@ -41,4 +42,5 @@ appraise 'rails_5.0' do # gem 'dynamoid', '~> 1', :platforms => :ruby gem 'aws-sdk', '~>2', :platforms => :ruby + gem 'redis-objects' end diff --git a/README.md b/README.md index 9887b77..c86b96a 100644 --- a/README.md +++ b/README.md @@ -720,8 +720,10 @@ end ### Redis -AASM also supports persistence in Redis. -Make sure to include Redis::Objects before you include AASM. +AASM also supports persistence in Redis via +[Redis::Objects](https://github.com/nateware/redis-objects). +Make sure to include Redis::Objects before you include AASM. Note that non-bang +events will work as bang events, persisting the changes on every call. ```ruby class User diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile index 05363a7..b39eac4 100644 --- a/gemfiles/rails_4.2.gemfile +++ b/gemfiles/rails_4.2.gemfile @@ -11,5 +11,6 @@ gem "mongoid", "~>4.0" gem "sequel" gem "dynamoid", "~> 1", :platforms => :ruby gem "aws-sdk", "~>2", :platforms => :ruby +gem "redis-objects" gemspec :path => "../" diff --git a/gemfiles/rails_5.0.gemfile b/gemfiles/rails_5.0.gemfile index 3683eb7..8845ed3 100644 --- a/gemfiles/rails_5.0.gemfile +++ b/gemfiles/rails_5.0.gemfile @@ -8,5 +8,6 @@ gem "rails", "5.0.0" gem "mongoid", "~>6.0" gem "sequel" gem "aws-sdk", "~>2", :platforms => :ruby +gem "redis-objects" gemspec :path => "../" diff --git a/lib/aasm/persistence/redis_persistence.rb b/lib/aasm/persistence/redis_persistence.rb index ced7c2f..5e711b7 100644 --- a/lib/aasm/persistence/redis_persistence.rb +++ b/lib/aasm/persistence/redis_persistence.rb @@ -8,13 +8,12 @@ module AASM end module InstanceMethods - # Add the inital value to intiializer + # Initialize with default values # - # redis-objects removed the key from redis when set to nil + # Redis::Objects removes 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) + aasm_ensure_initial_state end # Returns the value of the aasm.attribute_name - called from aasm.current_state # @@ -68,8 +67,10 @@ module AASM # foo.aasm_state # => nil # def aasm_ensure_initial_state - aasm.enter_initial_state if - send(self.class.aasm.attribute_name).to_s.strip.empty? + AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |name| + aasm_column = self.class.aasm(name).attribute_name + aasm(name).enter_initial_state if send(aasm_column).value.blank? + end end # Writes state to the state column and persists it to the database @@ -81,12 +82,16 @@ module AASM # 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) + def aasm_write_state(state, name=:default) + aasm_column = self.class.aasm(name).attribute_name + send("#{aasm_column}").value = state end # Writes state to the state column, but does not persist it to the database + # (but actually it still does) + # + # With Redis::Objects it's not possible to skip persisting - it's not an ORM, + # it does not operate like an AR model and does not know how to postpone changes. # # foo = Foo[1] # foo.aasm.current_state # => :opened @@ -98,8 +103,8 @@ module AASM # 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) + def aasm_write_state_without_persistence(state, name=:default) + aasm_write_state(state, name) end end end diff --git a/spec/models/redis/complex_redis_example.rb b/spec/models/redis/complex_redis_example.rb new file mode 100644 index 0000000..4490032 --- /dev/null +++ b/spec/models/redis/complex_redis_example.rb @@ -0,0 +1,40 @@ +class RedisComplexExample + include Redis::Objects + include AASM + + value :left + value :right + + def id + 1 + end + + aasm :left, :column => 'left' do + state :one, :initial => true + state :two + state :three + + event :increment do + transitions :from => :one, :to => :two + transitions :from => :two, :to => :three + end + event :reset do + transitions :from => :three, :to => :one + end + end + + aasm :right, :column => 'right' do + state :alpha, :initial => true + state :beta + state :gamma + + event :level_up do + transitions :from => :alpha, :to => :beta + transitions :from => :beta, :to => :gamma + end + event :level_down do + transitions :from => :gamma, :to => :beta + transitions :from => :beta, :to => :alpha + end + end +end diff --git a/spec/models/redis/redis_multiple.rb b/spec/models/redis/redis_multiple.rb new file mode 100644 index 0000000..665c008 --- /dev/null +++ b/spec/models/redis/redis_multiple.rb @@ -0,0 +1,20 @@ +class RedisMultiple + include Redis::Objects + include AASM + + value :status + + def id + 1 + end + + aasm :left, :column => :status + aasm :left do + state :alpha, :initial => true + state :beta + state :gamma + event :release do + transitions :from => [:alpha, :beta, :gamma], :to => :beta + end + end +end diff --git a/spec/models/redis/redis_simple.rb b/spec/models/redis/redis_simple.rb new file mode 100644 index 0000000..54801ce --- /dev/null +++ b/spec/models/redis/redis_simple.rb @@ -0,0 +1,20 @@ +class RedisSimple + 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 diff --git a/spec/spec_helpers/redis.rb b/spec/spec_helpers/redis.rb index 98573c8..2e1ab1a 100644 --- a/spec/spec_helpers/redis.rb +++ b/spec/spec_helpers/redis.rb @@ -2,6 +2,14 @@ begin require 'redis-objects' puts "redis-objects gem found, running Redis specs \e[32m#{'✔'}\e[0m" + + Redis.current = Redis.new(host: '127.0.0.1', port: 6379) + + RSpec.configure do |c| + c.before(:each) do + Redis.current.keys('redis_*').each { |k| Redis.current.del k } + end + end rescue LoadError puts "redis-objects gem not found, not running Redis specs \e[31m#{'✖'}\e[0m" end diff --git a/spec/unit/persistence/redis_persistence_multiple_spec.rb b/spec/unit/persistence/redis_persistence_multiple_spec.rb new file mode 100644 index 0000000..94955a8 --- /dev/null +++ b/spec/unit/persistence/redis_persistence_multiple_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +if defined?(Redis) + describe 'redis' do + + Dir[File.dirname(__FILE__) + "/../../models/redis/*.rb"].sort.each do |f| + require File.expand_path(f) + end + + before(:all) do + @model = RedisMultiple + 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(:left).current_state).to eq(:alpha) + end + + it "should save the initial state" do + expect(model.status).to eq("alpha") + end + + it "should return the aasm column the aasm field is not nil" do + model.status = "beta" + expect(model.aasm(:left).current_state).to eq(:beta) + end + + it "should allow a nil state" do + model.status = nil + expect(model.aasm(:left).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(:left).states).to eq(@model.aasm(:left).states) + end + + it "should have the same events as its parent class" do + expect(Class.new(@model).aasm(:left).events).to eq(@model.aasm(:left).events) + end + + it "should have the same column as its parent even for the new dsl" do + expect(@model.aasm(:left).attribute_name).to eq(:status) + expect(Class.new(@model).aasm(:left).attribute_name).to eq(:status) + end + end + + describe "complex example" do + it "works" do + record = RedisComplexExample.new + + expect(record.aasm(:left).current_state).to eql :one + expect(record.aasm(:right).current_state).to eql :alpha + + expect_aasm_states record, :one, :alpha + + record.increment! + expect_aasm_states record, :two, :alpha + + record.level_up! + expect_aasm_states record, :two, :beta + + record.increment! + expect { record.increment! }.to raise_error(AASM::InvalidTransition) + expect_aasm_states record, :three, :beta + + record.level_up! + expect_aasm_states record, :three, :gamma + end + + def expect_aasm_states(record, left_state, right_state) + expect(record.aasm(:left).current_state).to eql left_state.to_sym + expect(record.left.value.to_s).to eql left_state.to_s + expect(record.aasm(:right).current_state).to eql right_state.to_sym + expect(record.right.value.to_s).to eql right_state.to_s + end + end + end +end diff --git a/spec/unit/persistence/redis_persistence_spec.rb b/spec/unit/persistence/redis_persistence_spec.rb index 5c66311..0ac4908 100644 --- a/spec/unit/persistence/redis_persistence_spec.rb +++ b/spec/unit/persistence/redis_persistence_spec.rb @@ -3,31 +3,12 @@ require 'spec_helper' if defined?(Redis::Objects) describe 'redis' do + Dir[File.dirname(__FILE__) + "/../../models/redis/*.rb"].sort.each do |f| + require File.expand_path(f) + end + 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 + @model = RedisSimple end describe "instance methods" do @@ -68,6 +49,5 @@ if defined?(Redis::Objects) expect(Class.new(@model).aasm.attribute_name).to eq(:status) end end - end end