Skip to content

Commit

Permalink
Add i18n support for ActiveRecord validation errors
Browse files Browse the repository at this point in the history
  • Loading branch information
obrie committed Mar 7, 2009
1 parent 257cfb3 commit d0c9991
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rdoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
== master

* Add i18n support for ActiveRecord validation errors
* Add a validation error when failing to transition for ActiveRecord / DataMapper / Sequel integrations

== 0.6.0 / 2009-03-03
Expand Down
2 changes: 2 additions & 0 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Some brief, high-level features include:
* State values of any data type
* Dynamically-generated state values
* Inheritance
* Internationalization
* GraphViz visualization creator

Examples of the usage patterns for some of the above features are shown below.
Expand Down Expand Up @@ -446,3 +447,4 @@ dependencies are listed below.
* ActiveRecord[http://rubyonrails.org] integration: 2.1.0 or later
* DataMapper[http://datamapper.org] integration: 0.9.0 or later
* Sequel[http://sequel.rubyforge.org] integration: 2.8.0 or later
* i18n[http://github.com/svenfuchs/i18n] support (when used outside of ActiveRecord): 0.1.1 or later
11 changes: 10 additions & 1 deletion lib/state_machine/integrations/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,21 @@ def self.matches?(klass)
# Loads additional files specific to ActiveRecord
def self.extended(base) #:nodoc:
require 'state_machine/integrations/active_record/observer'
I18n.load_path << "#{File.dirname(__FILE__)}/active_record/locale.rb" if Object.const_defined?(:I18n)
end

# Adds a validation error to the given object after failing to fire a
# specific event
def invalidate(object, event)
object.errors.add(attribute, invalid_message(object, event))
if Object.const_defined?(:I18n)
object.errors.add(attribute, :invalid_transition,
:event => event.name,
:value => state_for(object).name,
:default => @invalid_message
)
else
object.errors.add(attribute, invalid_message(object, event))
end
end

# Resets an errors previously added when invalidating the given object
Expand Down
9 changes: 9 additions & 0 deletions lib/state_machine/integrations/active_record/locale.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{:en => {
:activerecord => {
:errors => {
:messages => {
:invalid_transition => StateMachine::Machine.default_invalid_message % ['{{event}}', '{{value}}']
}
}
}
}}
4 changes: 2 additions & 2 deletions lib/state_machine/machine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def initialize(owner_class, *args, &block)
@states = StateCollection.new
@callbacks = {:before => [], :after => []}
@namespace = options[:namespace]
@invalid_message = options[:invalid_message] || self.class.default_invalid_message
@invalid_message = options[:invalid_message]
self.owner_class = owner_class
self.initial_state = options[:initial]

Expand Down Expand Up @@ -1102,7 +1102,7 @@ def add_states(new_states)
# Generates the message to use when invalidating the given object after
# failing to transition on a specific event
def invalid_message(object, event)
@invalid_message % [event.name, state_for(object).name]
(@invalid_message || self.class.default_invalid_message) % [event.name, state_for(object).name]
end
end
end
70 changes: 70 additions & 0 deletions test/unit/integrations/active_record_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ def test_should_not_rollback_transaction_if_true
end

def test_should_invalidate_using_errors
I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:I18n)

record = @model.new
record.state = 'parked'

Expand Down Expand Up @@ -737,6 +739,74 @@ def test_should_invoke_callbacks_in_specific_order
assert_equal expected, @notifications
end
end

if Object.const_defined?(:I18n)
class MachineWithInternationalizationTest < ActiveRecord::TestCase
def setup
I18n.backend = I18n::Backend::Simple.new

# Initialize the backend
I18n.backend.translate(:en, 'activerecord.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling')

@model = new_model
end

def test_should_invalidate_using_i18n_default
I18n.backend.store_translations(:en, {
:activerecord => {
:errors => {
:messages => {
:invalid_transition => 'cannot {{event}} when {{value}}'
}
}
}
})

machine = StateMachine::Machine.new(@model)
machine.state :parked, :idling
event = StateMachine::Event.new(machine, :ignite)

record = @model.new(:state => 'idling')

machine.invalidate(record, event)
assert_equal 'cannot ignite when idling', record.errors.on(:state)
end

def test_should_invalidate_using_customized_i18n_key_if_specified
I18n.backend.store_translations(:en, {
:activerecord => {
:errors => {
:messages => {
:bad_transition => 'cannot {{event}} when {{value}}'
}
}
}
})

machine = StateMachine::Machine.new(@model, :invalid_message => :bad_transition)
machine.state :parked, :idling
event = StateMachine::Event.new(machine, :ignite)

record = @model.new(:state => 'idling')

machine.invalidate(record, event)
assert_equal 'cannot ignite when idling', record.errors.on(:state)
end
end

def test_should_invalidate_using_customized_i18n_string_if_specified
machine = StateMachine::Machine.new(@model, :invalid_message => 'cannot {{event}} when {{value}}')
machine.state :parked, :idling
event = StateMachine::Event.new(machine, :ignite)

record = @model.new(:state => 'idling')

machine.invalidate(record, event)
assert_equal 'cannot ignite when idling', record.errors.on(:state)
end
else
$stderr.puts 'Skipping ActiveRecord I18n tests. `gem install active_record` and try again.'
end
end
rescue LoadError
$stderr.puts 'Skipping ActiveRecord tests. `gem install active_record` and try again.'
Expand Down

0 comments on commit d0c9991

Please sign in to comment.