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 # CHANGELOG
## 3.0.x
* ActiveRecord persistence can ignore validation when trying to save invalid models
## 3.0.1 ## 3.0.1
* added support for Mongoid (Thanks, Michał Taberski) * added support for Mongoid (Thanks, Michał Taberski)

View File

@ -57,7 +57,9 @@ gem 'aasm'
% sudo gem install pkg/aasm-x.y.z.gem % sudo gem install pkg/aasm-x.y.z.gem
``` ```
## Simple Example ## ## Examples ##
### Simple Example ###
Here's a quick example highlighting some of the features. Here's a quick example highlighting some of the features.
@ -82,7 +84,7 @@ class Conversation
end end
``` ```
## A Slightly More Complex Example ## ### A Slightly More Complex Example ###
This example uses a few of the more complex features available. 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 end
``` ```
## Callbacks around events ### Callbacks around events ###
```ruby ```ruby
class Relationship class Relationship
include AASM include AASM
@ -135,6 +137,27 @@ This example uses a few of the more complex features available.
end 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 ## ## Changelog ##
Look at the [CHANGELOG](https://github.com/rubyist/aasm/blob/master/CHANGELOG.md) for details. 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 @clazz = clazz
sm = AASM::StateMachine[@clazz] sm = AASM::StateMachine[@clazz]
sm.config.column = options[:column].to_sym if options[:column] sm.config.column = options[:column].to_sym if options[:column]
if options.key?(:whiny_transitions) if options.key?(:whiny_transitions)
sm.config.whiny_transitions = options[:whiny_transitions] sm.config.whiny_transitions = options[:whiny_transitions]
else else
sm.config.whiny_transitions = true # this is the default, so let's cry sm.config.whiny_transitions = true # this is the default, so let's cry
end 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 end
def state(name, options={}) def state(name, options={})

View File

@ -196,7 +196,12 @@ module AASM
old_value = read_attribute(self.class.aasm_column) old_value = read_attribute(self.class.aasm_column)
write_attribute(self.class.aasm_column, state.to_s) 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) write_attribute(self.class.aasm_column, old_value)
return false return false
end 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" t.string "status"
end end
create_table "invalid_persistors", :force => true do |t|
t.string "name"
t.string "status"
end
end end

View File

@ -68,21 +68,6 @@ class Thief < ActiveRecord::Base
attr_accessor :skilled, :aasm_state attr_accessor :skilled, :aasm_state
end 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 shared_examples_for "aasm model" do
it "should include AASM::Persistence::ActiveRecordPersistence" do it "should include AASM::Persistence::ActiveRecordPersistence" do
@klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence) @klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence)
@ -264,19 +249,19 @@ end
describe 'transitions with persistence' do 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 = Validator.create(:name => 'name')
validator.should be_valid validator.should be_valid
validator.should be_sleeping validator.should be_sleeping
# validator.name = nil validator.name = nil
# validator.should_not be_valid validator.should_not be_valid
# validator.run!.should be_false validator.run!.should be_false
# validator.should be_running validator.should be_sleeping
#
# validator.reload validator.reload
# validator.should_not be_running validator.should_not be_running
# validator.should be_sleeping validator.should be_sleeping
validator.name = 'another name' validator.name = 'another name'
validator.should be_valid validator.should be_valid
@ -288,4 +273,25 @@ describe 'transitions with persistence' do
validator.should_not be_sleeping validator.should_not be_sleeping
end 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 end