diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c1a3eb..be3619d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 3.0.x + + * ActiveRecord persistence can ignore validation when trying to save invalid models + ## 3.0.1 * added support for Mongoid (Thanks, MichaƂ Taberski) diff --git a/README.md b/README.md index 96a9f58..d8a2494 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,9 @@ gem 'aasm' % sudo gem install pkg/aasm-x.y.z.gem ``` -## Simple Example ## +## Examples ## + +### Simple Example ### Here's a quick example highlighting some of the features. @@ -82,7 +84,7 @@ class Conversation end ``` -## A Slightly More Complex Example ## +### A Slightly More Complex Example ### This example uses a few of the more complex features available. @@ -117,7 +119,7 @@ This example uses a few of the more complex features available. end ``` -## Callbacks around events +### Callbacks around events ### ```ruby class Relationship include AASM @@ -135,6 +137,27 @@ This example uses a few of the more complex features available. end ``` +### Persistence example ### +```ruby + class InvalidPersistor < ActiveRecord::Base + include AASM + aasm :column => :status, :skip_validation_on_save => true do + state :sleeping, :initial => true + state :running + event :run do + transitions :to => :running, :from => :sleeping + end + event :sleep do + transitions :to => :sleeping, :from => :running + end + end + validates_presence_of :name + end +``` +This model can change AASM states which are stored into the database, even if the model itself is invalid! + + + ## Changelog ## Look at the [CHANGELOG](https://github.com/rubyist/aasm/blob/master/CHANGELOG.md) for details. diff --git a/lib/aasm/base.rb b/lib/aasm/base.rb index 3decc13..493dd76 100644 --- a/lib/aasm/base.rb +++ b/lib/aasm/base.rb @@ -4,11 +4,18 @@ module AASM @clazz = clazz sm = AASM::StateMachine[@clazz] sm.config.column = options[:column].to_sym if options[:column] + if options.key?(:whiny_transitions) sm.config.whiny_transitions = options[:whiny_transitions] else sm.config.whiny_transitions = true # this is the default, so let's cry end + + if options.key?(:skip_validation_on_save) + sm.config.skip_validation_on_save = options[:skip_validation_on_save] + else + sm.config.skip_validation_on_save = false # this is the default, so don't store any new state if the model is invalid + end end def state(name, options={}) diff --git a/lib/aasm/persistence/active_record_persistence.rb b/lib/aasm/persistence/active_record_persistence.rb index 3aaff9b..031f072 100644 --- a/lib/aasm/persistence/active_record_persistence.rb +++ b/lib/aasm/persistence/active_record_persistence.rb @@ -196,7 +196,12 @@ module AASM old_value = read_attribute(self.class.aasm_column) write_attribute(self.class.aasm_column, state.to_s) - unless self.save + success = if AASM::StateMachine[self.class].config.skip_validation_on_save && !self.valid? + self.class.update_all({ self.class.aasm_column => state.to_s }, self.class.primary_key => self.id) == 1 + else + self.save + end + unless success write_attribute(self.class.aasm_column, old_value) return false end diff --git a/spec/models/invalid_persistor.rb b/spec/models/invalid_persistor.rb new file mode 100644 index 0000000..2888ac1 --- /dev/null +++ b/spec/models/invalid_persistor.rb @@ -0,0 +1,16 @@ +require 'active_record' + +class InvalidPersistor < ActiveRecord::Base + include AASM + aasm :column => :status, :skip_validation_on_save => true do + state :sleeping, :initial => true + state :running + event :run do + transitions :to => :running, :from => :sleeping + end + event :sleep do + transitions :to => :sleeping, :from => :running + end + end + validates_presence_of :name +end diff --git a/spec/models/validator.rb b/spec/models/validator.rb new file mode 100644 index 0000000..02ad7ef --- /dev/null +++ b/spec/models/validator.rb @@ -0,0 +1,16 @@ +require 'active_record' + +class Validator < ActiveRecord::Base + include AASM + aasm :column => :status do + state :sleeping, :initial => true + state :running + event :run do + transitions :to => :running, :from => :sleeping + end + event :sleep do + transitions :to => :sleeping, :from => :running + end + end + validates_presence_of :name +end diff --git a/spec/schema.rb b/spec/schema.rb index 30e781d..f211832 100644 --- a/spec/schema.rb +++ b/spec/schema.rb @@ -9,4 +9,9 @@ ActiveRecord::Schema.define(:version => 0) do t.string "status" end + create_table "invalid_persistors", :force => true do |t| + t.string "name" + t.string "status" + end + end diff --git a/spec/unit/active_record_persistence_spec.rb b/spec/unit/active_record_persistence_spec.rb index 437fcf2..17259c6 100644 --- a/spec/unit/active_record_persistence_spec.rb +++ b/spec/unit/active_record_persistence_spec.rb @@ -68,21 +68,6 @@ class Thief < ActiveRecord::Base attr_accessor :skilled, :aasm_state end -class Validator < ActiveRecord::Base - include AASM - aasm :column => :status do - state :sleeping, :initial => true - state :running - event :run do - transitions :to => :running, :from => :sleeping - end - event :sleep do - transitions :to => :sleeping, :from => :running - end - end - validates_presence_of :name -end - shared_examples_for "aasm model" do it "should include AASM::Persistence::ActiveRecordPersistence" do @klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence) @@ -264,19 +249,19 @@ end describe 'transitions with persistence' do - it 'should succeed and store the new state' do + it 'should not store states for invalid models' do validator = Validator.create(:name => 'name') validator.should be_valid validator.should be_sleeping - # validator.name = nil - # validator.should_not be_valid - # validator.run!.should be_false - # validator.should be_running - # - # validator.reload - # validator.should_not be_running - # validator.should be_sleeping + validator.name = nil + validator.should_not be_valid + validator.run!.should be_false + validator.should be_sleeping + + validator.reload + validator.should_not be_running + validator.should be_sleeping validator.name = 'another name' validator.should be_valid @@ -288,4 +273,25 @@ describe 'transitions with persistence' do validator.should_not be_sleeping end + it 'should store states for invalid models if configured' do + persistor = InvalidPersistor.create(:name => 'name') + persistor.should be_valid + persistor.should be_sleeping + + persistor.name = nil + persistor.should_not be_valid + persistor.run!.should be_true + persistor.should be_running + + persistor = InvalidPersistor.find(persistor.id) + persistor.valid? + persistor.should be_valid + persistor.should be_running + persistor.should_not be_sleeping + + persistor.reload + persistor.should be_running + persistor.should_not be_sleeping + end + end