ActiveRecord persistence can ignore validation when trying to save invalid models

This commit is contained in:
Thorsten Böttger 2012-01-16 17:27:15 +01:00
parent c12be51c9c
commit 921cfdad5c
8 changed files with 110 additions and 28 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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={})

View File

@ -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

View File

@ -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

16
spec/models/validator.rb Normal file
View File

@ -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

View File

@ -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

View File

@ -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