mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge branch 'state_machine'
Some big changes: * Added some redundant requires so active_support/inflecto can be loaded without the rest of ActiveSupport. * Disabled callbacks and validations until they are added and tested. * Converted specs back to tests, using ActiveSupport::TestCase and the new #test helper. * As an experiment, I imported Scott Barron's awesome AASM gem into ActiveModel. I added multiple state machine support and vastly improved the API (no more aasm_* prefixes). All the old tests pass. If this bothers people, I have no problems removing this and contributing the changes back to AASM. I just feel like AMo is a better spot for all these 'modelish' features.
This commit is contained in:
commit
01db5ded54
22 changed files with 1078 additions and 167 deletions
|
@ -1,11 +1,19 @@
|
|||
#!/usr/bin/env ruby
|
||||
$LOAD_PATH << File.join(File.dirname(__FILE__), 'vendor', 'rspec', 'lib')
|
||||
require 'rake'
|
||||
require 'spec/rake/spectask'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
task :default => :test
|
||||
|
||||
Rake::TestTask.new do |t|
|
||||
t.libs << "test"
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
t.warning = true
|
||||
end
|
||||
|
||||
# Generate the RDoc documentation
|
||||
Rake::RDocTask.new { |rdoc|
|
||||
Rake::RDocTask.new do |rdoc|
|
||||
rdoc.rdoc_dir = 'doc'
|
||||
rdoc.title = "Active Model"
|
||||
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
||||
|
@ -13,4 +21,4 @@ Rake::RDocTask.new { |rdoc|
|
|||
rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
|
||||
rdoc.rdoc_files.include('README', 'CHANGES')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
}
|
||||
end
|
||||
|
|
|
@ -1,17 +1,5 @@
|
|||
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..', 'activesupport', 'lib')
|
||||
|
||||
# premature optimization?
|
||||
require 'active_support/inflector'
|
||||
require 'active_support/core_ext/string/inflections'
|
||||
String.send :include, ActiveSupport::CoreExtensions::String::Inflections
|
||||
|
||||
require 'active_model/base'
|
||||
require 'active_model/observing'
|
||||
require 'active_model/callbacks'
|
||||
require 'active_model/validations'
|
||||
|
||||
ActiveModel::Base.class_eval do
|
||||
include ActiveModel::Observing
|
||||
include ActiveModel::Callbacks
|
||||
include ActiveModel::Validations
|
||||
end
|
||||
# disabled until they're tested
|
||||
# require 'active_model/callbacks'
|
||||
# require 'active_model/validations'
|
||||
require 'active_model/base'
|
|
@ -1,4 +1,8 @@
|
|||
module ActiveModel
|
||||
class Base
|
||||
include Observing
|
||||
# disabled, until they're tested
|
||||
# include Callbacks
|
||||
# include Validations
|
||||
end
|
||||
end
|
|
@ -1,3 +1,5 @@
|
|||
require 'active_model/core'
|
||||
|
||||
module ActiveModel
|
||||
module Callbacks
|
||||
|
||||
|
|
7
activemodel/lib/active_model/core.rb
Normal file
7
activemodel/lib/active_model/core.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# This file is required by each major ActiveModel component for the core requirements. This allows you to
|
||||
# load individual pieces of ActiveModel as needed.
|
||||
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..', '..', 'activesupport', 'lib')
|
||||
|
||||
# premature optimization?
|
||||
# So far, we only need the string inflections and not the rest of ActiveSupport.
|
||||
require 'active_support/inflector'
|
|
@ -1,4 +1,6 @@
|
|||
require 'observer'
|
||||
require 'singleton'
|
||||
require 'active_model/core'
|
||||
|
||||
module ActiveModel
|
||||
module Observing
|
||||
|
@ -73,7 +75,7 @@ module ActiveModel
|
|||
# Start observing the declared classes and their subclasses.
|
||||
def initialize
|
||||
self.observed_classes = self.class.models if self.class.models
|
||||
observed_classes.each { |klass| add_observer! klass }
|
||||
observed_classes.each { |klass| klass.add_observer(self) }
|
||||
end
|
||||
|
||||
# Send observed_method(object) if the method exists.
|
||||
|
@ -85,16 +87,12 @@ module ActiveModel
|
|||
# Passes the new subclass.
|
||||
def observed_class_inherited(subclass) #:nodoc:
|
||||
self.class.observe(observed_classes + [subclass])
|
||||
add_observer!(subclass)
|
||||
subclass.add_observer(self)
|
||||
end
|
||||
|
||||
protected
|
||||
def observed_classes
|
||||
@observed_classes ||= [self.class.observed_class]
|
||||
end
|
||||
|
||||
def add_observer!(klass)
|
||||
klass.add_observer(self)
|
||||
end
|
||||
protected
|
||||
def observed_classes
|
||||
@observed_classes ||= [self.class.observed_class]
|
||||
end
|
||||
end
|
||||
end
|
66
activemodel/lib/active_model/state_machine.rb
Normal file
66
activemodel/lib/active_model/state_machine.rb
Normal file
|
@ -0,0 +1,66 @@
|
|||
Dir[File.dirname(__FILE__) + "/state_machine/*.rb"].sort.each do |path|
|
||||
filename = File.basename(path)
|
||||
require "active_model/state_machine/#{filename}"
|
||||
end
|
||||
|
||||
module ActiveModel
|
||||
module StateMachine
|
||||
class InvalidTransition < Exception
|
||||
end
|
||||
|
||||
def self.included(base)
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def inherited(klass)
|
||||
super
|
||||
klass.state_machines = state_machines
|
||||
end
|
||||
|
||||
def state_machines
|
||||
@state_machines ||= {}
|
||||
end
|
||||
|
||||
def state_machines=(value)
|
||||
@state_machines = value ? value.dup : nil
|
||||
end
|
||||
|
||||
def state_machine(name = nil, options = {}, &block)
|
||||
if name.is_a?(Hash)
|
||||
options = name
|
||||
name = nil
|
||||
end
|
||||
name ||= :default
|
||||
state_machines[name] ||= Machine.new(self, name)
|
||||
block ? state_machines[name].update(options, &block) : state_machines[name]
|
||||
end
|
||||
end
|
||||
|
||||
def current_state(name = nil, new_state = nil, persist = false)
|
||||
sm = self.class.state_machine(name)
|
||||
ivar = sm.current_state_variable
|
||||
if name && new_state
|
||||
if persist && respond_to?(:write_state)
|
||||
write_state(sm, new_state)
|
||||
end
|
||||
|
||||
if respond_to?(:write_state_without_persistence)
|
||||
write_state_without_persistence(sm, new_state)
|
||||
end
|
||||
|
||||
instance_variable_set(ivar, new_state)
|
||||
else
|
||||
instance_variable_set(ivar, nil) unless instance_variable_defined?(ivar)
|
||||
value = instance_variable_get(ivar)
|
||||
return value if value
|
||||
|
||||
if respond_to?(:read_state)
|
||||
value = instance_variable_set(ivar, read_state(sm))
|
||||
end
|
||||
|
||||
value || sm.initial_state
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
62
activemodel/lib/active_model/state_machine/event.rb
Normal file
62
activemodel/lib/active_model/state_machine/event.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
module ActiveModel
|
||||
module StateMachine
|
||||
class Event
|
||||
attr_reader :name, :success
|
||||
|
||||
def initialize(machine, name, options = {}, &block)
|
||||
@machine, @name, @transitions = machine, name, []
|
||||
if machine
|
||||
machine.klass.send(:define_method, "#{name.to_s}!") do |*args|
|
||||
machine.fire_event(name, self, true, *args)
|
||||
end
|
||||
|
||||
machine.klass.send(:define_method, "#{name.to_s}") do |*args|
|
||||
machine.fire_event(name, self, false, *args)
|
||||
end
|
||||
end
|
||||
update(options, &block)
|
||||
end
|
||||
|
||||
def fire(obj, to_state = nil, *args)
|
||||
transitions = @transitions.select { |t| t.from == obj.current_state(@machine ? @machine.name : nil) }
|
||||
raise InvalidTransition if transitions.size == 0
|
||||
|
||||
next_state = nil
|
||||
transitions.each do |transition|
|
||||
next if to_state && !Array(transition.to).include?(to_state)
|
||||
if transition.perform(obj)
|
||||
next_state = to_state || Array(transition.to).first
|
||||
transition.execute(obj, *args)
|
||||
break
|
||||
end
|
||||
end
|
||||
next_state
|
||||
end
|
||||
|
||||
def transitions_from_state?(state)
|
||||
@transitions.any? { |t| t.from? state }
|
||||
end
|
||||
|
||||
def ==(event)
|
||||
if event.is_a? Symbol
|
||||
name == event
|
||||
else
|
||||
name == event.name
|
||||
end
|
||||
end
|
||||
|
||||
def update(options = {}, &block)
|
||||
if options.key?(:success) then @success = options[:success] end
|
||||
if block then instance_eval(&block) end
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
def transitions(trans_opts)
|
||||
Array(trans_opts[:from]).each do |s|
|
||||
@transitions << StateTransition.new(trans_opts.merge({:from => s.to_sym}))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
74
activemodel/lib/active_model/state_machine/machine.rb
Normal file
74
activemodel/lib/active_model/state_machine/machine.rb
Normal file
|
@ -0,0 +1,74 @@
|
|||
module ActiveModel
|
||||
module StateMachine
|
||||
class Machine
|
||||
attr_accessor :initial_state, :states, :events, :state_index
|
||||
attr_reader :klass, :name
|
||||
|
||||
def initialize(klass, name, options = {}, &block)
|
||||
@klass, @name, @states, @state_index, @events = klass, name, [], {}, {}
|
||||
update(options, &block)
|
||||
end
|
||||
|
||||
def initial_state
|
||||
@initial_state ||= (states.first ? states.first.name : nil)
|
||||
end
|
||||
|
||||
def update(options = {}, &block)
|
||||
if options.key?(:initial) then @initial_state = options[:initial] end
|
||||
if block then instance_eval(&block) end
|
||||
self
|
||||
end
|
||||
|
||||
def fire_event(event, record, persist, *args)
|
||||
state_index[record.current_state(@name)].call_action(:exit, record)
|
||||
if new_state = @events[event].fire(record, *args)
|
||||
state_index[new_state].call_action(:enter, record)
|
||||
|
||||
if record.respond_to?(event_fired_callback)
|
||||
record.send(event_fired_callback, record.current_state, new_state)
|
||||
end
|
||||
|
||||
record.current_state(@name, new_state, persist)
|
||||
record.send(@events[event].success) if @events[event].success
|
||||
true
|
||||
else
|
||||
if record.respond_to?(event_failed_callback)
|
||||
record.send(event_failed_callback, event)
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def states_for_select
|
||||
states.map { |st| [st.display_name, st.name.to_s] }
|
||||
end
|
||||
|
||||
def events_for(state)
|
||||
events = @events.values.select { |event| event.transitions_from_state?(state) }
|
||||
events.map! { |event| event.name }
|
||||
end
|
||||
|
||||
def current_state_variable
|
||||
"@#{@name}_current_state"
|
||||
end
|
||||
|
||||
private
|
||||
def state(name, options = {})
|
||||
@states << (state_index[name] ||= State.new(name, :machine => self)).update(options)
|
||||
end
|
||||
|
||||
def event(name, options = {}, &block)
|
||||
(@events[name] ||= Event.new(self, name)).update(options, &block)
|
||||
end
|
||||
|
||||
def event_fired_callback
|
||||
@event_fired_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_fired'
|
||||
end
|
||||
|
||||
def event_failed_callback
|
||||
@event_failed_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_failed'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
50
activemodel/lib/active_model/state_machine/state.rb
Normal file
50
activemodel/lib/active_model/state_machine/state.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
module ActiveModel
|
||||
module StateMachine
|
||||
class State
|
||||
attr_reader :name, :options
|
||||
|
||||
def initialize(name, options = {})
|
||||
@name = name
|
||||
machine = options.delete(:machine)
|
||||
if machine
|
||||
machine.klass.send(:define_method, "#{name}?") do
|
||||
current_state.to_s == name.to_s
|
||||
end
|
||||
end
|
||||
update(options)
|
||||
end
|
||||
|
||||
def ==(state)
|
||||
if state.is_a? Symbol
|
||||
name == state
|
||||
else
|
||||
name == state.name
|
||||
end
|
||||
end
|
||||
|
||||
def call_action(action, record)
|
||||
action = @options[action]
|
||||
case action
|
||||
when Symbol, String
|
||||
record.send(action)
|
||||
when Proc
|
||||
action.call(record)
|
||||
end
|
||||
end
|
||||
|
||||
def display_name
|
||||
@display_name ||= name.to_s.gsub(/_/, ' ').capitalize
|
||||
end
|
||||
|
||||
def for_select
|
||||
[display_name, name.to_s]
|
||||
end
|
||||
|
||||
def update(options = {})
|
||||
if options.key?(:display) then @display_name = options.delete(:display) end
|
||||
@options = options
|
||||
self
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,40 @@
|
|||
module ActiveModel
|
||||
module StateMachine
|
||||
class StateTransition
|
||||
attr_reader :from, :to, :options
|
||||
|
||||
def initialize(opts)
|
||||
@from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
|
||||
@options = opts
|
||||
end
|
||||
|
||||
def perform(obj)
|
||||
case @guard
|
||||
when Symbol, String
|
||||
obj.send(@guard)
|
||||
when Proc
|
||||
@guard.call(obj)
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def execute(obj, *args)
|
||||
case @on_transition
|
||||
when Symbol, String
|
||||
obj.send(@on_transition, *args)
|
||||
when Proc
|
||||
@on_transition.call(obj, *args)
|
||||
end
|
||||
end
|
||||
|
||||
def ==(obj)
|
||||
@from == obj.from && @to == obj.to
|
||||
end
|
||||
|
||||
def from?(value)
|
||||
@from == value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +1,5 @@
|
|||
require 'active_model/core'
|
||||
|
||||
module ActiveModel
|
||||
module Validations
|
||||
def self.included(base) # :nodoc:
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
require File.join(File.dirname(__FILE__), 'spec_helper')
|
||||
|
||||
class ObservedModel < ActiveModel::Base
|
||||
class Observer
|
||||
end
|
||||
end
|
||||
|
||||
class FooObserver < ActiveModel::Observer
|
||||
class << self
|
||||
public :new
|
||||
end
|
||||
|
||||
attr_accessor :stub
|
||||
|
||||
def on_spec(record)
|
||||
stub.event_with(record) if stub
|
||||
end
|
||||
end
|
||||
|
||||
class Foo < ActiveModel::Base
|
||||
end
|
||||
|
||||
module ActiveModel
|
||||
describe Observing do
|
||||
before do
|
||||
ObservedModel.observers.clear
|
||||
end
|
||||
|
||||
it "initializes model with no cached observers" do
|
||||
ObservedModel.observers.should be_empty
|
||||
end
|
||||
|
||||
it "stores cached observers in an array" do
|
||||
ObservedModel.observers << :foo
|
||||
ObservedModel.observers.should include(:foo)
|
||||
end
|
||||
|
||||
it "flattens array of assigned cached observers" do
|
||||
ObservedModel.observers = [[:foo], :bar]
|
||||
ObservedModel.observers.should include(:foo)
|
||||
ObservedModel.observers.should include(:bar)
|
||||
end
|
||||
|
||||
it "instantiates observer names passed as strings" do
|
||||
ObservedModel.observers << 'foo_observer'
|
||||
FooObserver.should_receive(:instance)
|
||||
ObservedModel.instantiate_observers
|
||||
end
|
||||
|
||||
it "instantiates observer names passed as symbols" do
|
||||
ObservedModel.observers << :foo_observer
|
||||
FooObserver.should_receive(:instance)
|
||||
ObservedModel.instantiate_observers
|
||||
end
|
||||
|
||||
it "instantiates observer classes" do
|
||||
ObservedModel.observers << ObservedModel::Observer
|
||||
ObservedModel::Observer.should_receive(:instance)
|
||||
ObservedModel.instantiate_observers
|
||||
end
|
||||
|
||||
it "should pass observers to subclasses" do
|
||||
FooObserver.instance
|
||||
bar = Class.new(Foo)
|
||||
bar.count_observers.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe Observer do
|
||||
before do
|
||||
ObservedModel.observers = :foo_observer
|
||||
FooObserver.models = nil
|
||||
end
|
||||
|
||||
it "guesses implicit observable model name" do
|
||||
FooObserver.observed_class_name.should == 'Foo'
|
||||
end
|
||||
|
||||
it "tracks implicit observable models" do
|
||||
instance = FooObserver.new
|
||||
instance.send(:observed_classes).should include(Foo)
|
||||
instance.send(:observed_classes).should_not include(ObservedModel)
|
||||
end
|
||||
|
||||
it "tracks explicit observed model class" do
|
||||
FooObserver.new.send(:observed_classes).should_not include(ObservedModel)
|
||||
FooObserver.observe ObservedModel
|
||||
instance = FooObserver.new
|
||||
instance.send(:observed_classes).should include(ObservedModel)
|
||||
end
|
||||
|
||||
it "tracks explicit observed model as string" do
|
||||
FooObserver.new.send(:observed_classes).should_not include(ObservedModel)
|
||||
FooObserver.observe 'observed_model'
|
||||
instance = FooObserver.new
|
||||
instance.send(:observed_classes).should include(ObservedModel)
|
||||
end
|
||||
|
||||
it "tracks explicit observed model as symbol" do
|
||||
FooObserver.new.send(:observed_classes).should_not include(ObservedModel)
|
||||
FooObserver.observe :observed_model
|
||||
instance = FooObserver.new
|
||||
instance.send(:observed_classes).should include(ObservedModel)
|
||||
end
|
||||
|
||||
it "calls existing observer event" do
|
||||
foo = Foo.new
|
||||
FooObserver.instance.stub = stub!(:stub)
|
||||
FooObserver.instance.stub.should_receive(:event_with).with(foo)
|
||||
Foo.send(:changed)
|
||||
Foo.send(:notify_observers, :on_spec, foo)
|
||||
end
|
||||
|
||||
it "skips nonexistent observer event" do
|
||||
foo = Foo.new
|
||||
Foo.send(:changed)
|
||||
Foo.send(:notify_observers, :whatever, foo)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,17 +0,0 @@
|
|||
ENV['LOG_NAME'] = 'spec'
|
||||
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'vendor', 'rspec', 'lib')
|
||||
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
|
||||
require 'active_model'
|
||||
begin
|
||||
require 'spec'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
require 'spec'
|
||||
end
|
||||
|
||||
begin
|
||||
require 'ruby-debug'
|
||||
Debugger.start
|
||||
rescue LoadError
|
||||
# you do not know the ways of ruby-debug yet, what a shame
|
||||
end
|
123
activemodel/test/observing_test.rb
Normal file
123
activemodel/test/observing_test.rb
Normal file
|
@ -0,0 +1,123 @@
|
|||
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
||||
|
||||
class ObservedModel < ActiveModel::Base
|
||||
class Observer
|
||||
end
|
||||
end
|
||||
|
||||
class FooObserver < ActiveModel::Observer
|
||||
class << self
|
||||
public :new
|
||||
end
|
||||
|
||||
attr_accessor :stub
|
||||
|
||||
def on_spec(record)
|
||||
stub.event_with(record) if stub
|
||||
end
|
||||
end
|
||||
|
||||
class Foo < ActiveModel::Base
|
||||
end
|
||||
|
||||
class ObservingTest < ActiveModel::TestCase
|
||||
def setup
|
||||
ObservedModel.observers.clear
|
||||
end
|
||||
|
||||
test "initializes model with no cached observers" do
|
||||
assert ObservedModel.observers.empty?, "Not empty: #{ObservedModel.observers.inspect}"
|
||||
end
|
||||
|
||||
test "stores cached observers in an array" do
|
||||
ObservedModel.observers << :foo
|
||||
assert ObservedModel.observers.include?(:foo), ":foo not in #{ObservedModel.observers.inspect}"
|
||||
end
|
||||
|
||||
test "flattens array of assigned cached observers" do
|
||||
ObservedModel.observers = [[:foo], :bar]
|
||||
assert ObservedModel.observers.include?(:foo), ":foo not in #{ObservedModel.observers.inspect}"
|
||||
assert ObservedModel.observers.include?(:bar), ":bar not in #{ObservedModel.observers.inspect}"
|
||||
end
|
||||
|
||||
uses_mocha "observer instantiation" do
|
||||
test "instantiates observer names passed as strings" do
|
||||
ObservedModel.observers << 'foo_observer'
|
||||
FooObserver.expects(:instance)
|
||||
ObservedModel.instantiate_observers
|
||||
end
|
||||
|
||||
test "instantiates observer names passed as symbols" do
|
||||
ObservedModel.observers << :foo_observer
|
||||
FooObserver.expects(:instance)
|
||||
ObservedModel.instantiate_observers
|
||||
end
|
||||
|
||||
test "instantiates observer classes" do
|
||||
ObservedModel.observers << ObservedModel::Observer
|
||||
ObservedModel::Observer.expects(:instance)
|
||||
ObservedModel.instantiate_observers
|
||||
end
|
||||
end
|
||||
|
||||
test "passes observers to subclasses" do
|
||||
FooObserver.instance
|
||||
bar = Class.new(Foo)
|
||||
assert_equal Foo.count_observers, bar.count_observers
|
||||
end
|
||||
end
|
||||
|
||||
class ObserverTest < ActiveModel::TestCase
|
||||
def setup
|
||||
ObservedModel.observers = :foo_observer
|
||||
FooObserver.models = nil
|
||||
end
|
||||
|
||||
test "guesses implicit observable model name" do
|
||||
assert_equal 'Foo', FooObserver.observed_class_name
|
||||
end
|
||||
|
||||
test "tracks implicit observable models" do
|
||||
instance = FooObserver.new
|
||||
assert instance.send(:observed_classes).include?(Foo), "Foo not in #{instance.send(:observed_classes).inspect}"
|
||||
assert !instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{instance.send(:observed_classes).inspect}"
|
||||
end
|
||||
|
||||
test "tracks explicit observed model class" do
|
||||
old_instance = FooObserver.new
|
||||
assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
|
||||
FooObserver.observe ObservedModel
|
||||
instance = FooObserver.new
|
||||
assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
|
||||
end
|
||||
|
||||
test "tracks explicit observed model as string" do
|
||||
old_instance = FooObserver.new
|
||||
assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
|
||||
FooObserver.observe 'observed_model'
|
||||
instance = FooObserver.new
|
||||
assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
|
||||
end
|
||||
|
||||
test "tracks explicit observed model as symbol" do
|
||||
old_instance = FooObserver.new
|
||||
assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
|
||||
FooObserver.observe :observed_model
|
||||
instance = FooObserver.new
|
||||
assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
|
||||
end
|
||||
|
||||
test "calls existing observer event" do
|
||||
foo = Foo.new
|
||||
FooObserver.instance.stub = stub
|
||||
FooObserver.instance.stub.expects(:event_with).with(foo)
|
||||
Foo.send(:changed)
|
||||
Foo.send(:notify_observers, :on_spec, foo)
|
||||
end
|
||||
|
||||
test "skips nonexistent observer event" do
|
||||
foo = Foo.new
|
||||
Foo.send(:changed)
|
||||
Foo.send(:notify_observers, :whatever, foo)
|
||||
end
|
||||
end
|
51
activemodel/test/state_machine/event_test.rb
Normal file
51
activemodel/test/state_machine/event_test.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
|
||||
|
||||
class EventTest < ActiveModel::TestCase
|
||||
def setup
|
||||
@name = :close_order
|
||||
@success = :success_callback
|
||||
end
|
||||
|
||||
def new_event
|
||||
@event = ActiveModel::StateMachine::Event.new(nil, @name, {:success => @success}) do
|
||||
transitions :to => :closed, :from => [:open, :received]
|
||||
end
|
||||
end
|
||||
|
||||
test 'should set the name' do
|
||||
assert_equal @name, new_event.name
|
||||
end
|
||||
|
||||
test 'should set the success option' do
|
||||
assert_equal @success, new_event.success
|
||||
end
|
||||
|
||||
uses_mocha 'StateTransition creation' do
|
||||
test 'should create StateTransitions' do
|
||||
ActiveModel::StateMachine::StateTransition.expects(:new).with(:to => :closed, :from => :open)
|
||||
ActiveModel::StateMachine::StateTransition.expects(:new).with(:to => :closed, :from => :received)
|
||||
new_event
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class EventBeingFiredTest < ActiveModel::TestCase
|
||||
test 'should raise an AASM::InvalidTransition error if the transitions are empty' do
|
||||
event = ActiveModel::StateMachine::Event.new(nil, :event)
|
||||
|
||||
assert_raises ActiveModel::StateMachine::InvalidTransition do
|
||||
event.fire(nil)
|
||||
end
|
||||
end
|
||||
|
||||
test 'should return the state of the first matching transition it finds' do
|
||||
event = ActiveModel::StateMachine::Event.new(nil, :event) do
|
||||
transitions :to => :closed, :from => [:open, :received]
|
||||
end
|
||||
|
||||
obj = stub
|
||||
obj.stubs(:current_state).returns(:open)
|
||||
|
||||
assert_equal :closed, event.fire(obj)
|
||||
end
|
||||
end
|
43
activemodel/test/state_machine/machine_test.rb
Normal file
43
activemodel/test/state_machine/machine_test.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
|
||||
|
||||
class MachineTestSubject
|
||||
include ActiveModel::StateMachine
|
||||
|
||||
state_machine do
|
||||
state :open
|
||||
state :closed
|
||||
end
|
||||
|
||||
state_machine :initial => :foo do
|
||||
event :shutdown do
|
||||
transitions :from => :open, :to => :closed
|
||||
end
|
||||
|
||||
event :timeout do
|
||||
transitions :from => :open, :to => :closed
|
||||
end
|
||||
end
|
||||
|
||||
state_machine :extra, :initial => :bar do
|
||||
end
|
||||
end
|
||||
|
||||
class StateMachineMachineTest < ActiveModel::TestCase
|
||||
test "allows reuse of existing machines" do
|
||||
assert_equal 2, MachineTestSubject.state_machines.size
|
||||
end
|
||||
|
||||
test "sets #initial_state from :initial option" do
|
||||
assert_equal :bar, MachineTestSubject.state_machine(:extra).initial_state
|
||||
end
|
||||
|
||||
test "accesses non-default state machine" do
|
||||
assert_kind_of ActiveModel::StateMachine::Machine, MachineTestSubject.state_machine(:extra)
|
||||
end
|
||||
|
||||
test "finds events for given state" do
|
||||
events = MachineTestSubject.state_machine.events_for(:open)
|
||||
assert events.include?(:shutdown)
|
||||
assert events.include?(:timeout)
|
||||
end
|
||||
end
|
74
activemodel/test/state_machine/state_test.rb
Normal file
74
activemodel/test/state_machine/state_test.rb
Normal file
|
@ -0,0 +1,74 @@
|
|||
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
|
||||
|
||||
class StateTestSubject
|
||||
include ActiveModel::StateMachine
|
||||
|
||||
state_machine do
|
||||
end
|
||||
end
|
||||
|
||||
class StateTest < ActiveModel::TestCase
|
||||
def setup
|
||||
@name = :astate
|
||||
@machine = StateTestSubject.state_machine
|
||||
@options = { :crazy_custom_key => 'key', :machine => @machine }
|
||||
end
|
||||
|
||||
def new_state(options={})
|
||||
ActiveModel::StateMachine::State.new(@name, @options.merge(options))
|
||||
end
|
||||
|
||||
test 'sets the name' do
|
||||
assert_equal :astate, new_state.name
|
||||
end
|
||||
|
||||
test 'sets the display_name from name' do
|
||||
assert_equal "Astate", new_state.display_name
|
||||
end
|
||||
|
||||
test 'sets the display_name from options' do
|
||||
assert_equal "A State", new_state(:display => "A State").display_name
|
||||
end
|
||||
|
||||
test 'sets the options and expose them as options' do
|
||||
@options.delete(:machine)
|
||||
assert_equal @options, new_state.options
|
||||
end
|
||||
|
||||
test 'equals a symbol of the same name' do
|
||||
assert_equal new_state, :astate
|
||||
end
|
||||
|
||||
test 'equals a State of the same name' do
|
||||
assert_equal new_state, new_state
|
||||
end
|
||||
|
||||
uses_mocha 'state actions' do
|
||||
test 'should send a message to the record for an action if the action is present as a symbol' do
|
||||
state = new_state(:entering => :foo)
|
||||
|
||||
record = stub
|
||||
record.expects(:foo)
|
||||
|
||||
state.call_action(:entering, record)
|
||||
end
|
||||
|
||||
test 'should send a message to the record for an action if the action is present as a string' do
|
||||
state = new_state(:entering => 'foo')
|
||||
|
||||
record = stub
|
||||
record.expects(:foo)
|
||||
|
||||
state.call_action(:entering, record)
|
||||
end
|
||||
|
||||
test 'should call a proc, passing in the record for an action if the action is present' do
|
||||
state = new_state(:entering => Proc.new {|r| r.foobar})
|
||||
|
||||
record = stub
|
||||
record.expects(:foobar)
|
||||
|
||||
state.call_action(:entering, record)
|
||||
end
|
||||
end
|
||||
end
|
88
activemodel/test/state_machine/state_transition_test.rb
Normal file
88
activemodel/test/state_machine/state_transition_test.rb
Normal file
|
@ -0,0 +1,88 @@
|
|||
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
|
||||
|
||||
class StateTransitionTest < ActiveModel::TestCase
|
||||
test 'should set from, to, and opts attr readers' do
|
||||
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
|
||||
st = ActiveModel::StateMachine::StateTransition.new(opts)
|
||||
|
||||
assert_equal opts[:from], st.from
|
||||
assert_equal opts[:to], st.to
|
||||
assert_equal opts, st.options
|
||||
end
|
||||
|
||||
uses_mocha 'checking ActiveModel StateMachine transitions' do
|
||||
test 'should pass equality check if from and to are the same' do
|
||||
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
|
||||
st = ActiveModel::StateMachine::StateTransition.new(opts)
|
||||
|
||||
obj = stub
|
||||
obj.stubs(:from).returns(opts[:from])
|
||||
obj.stubs(:to).returns(opts[:to])
|
||||
|
||||
assert_equal st, obj
|
||||
end
|
||||
|
||||
test 'should fail equality check if from are not the same' do
|
||||
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
|
||||
st = ActiveModel::StateMachine::StateTransition.new(opts)
|
||||
|
||||
obj = stub
|
||||
obj.stubs(:from).returns('blah')
|
||||
obj.stubs(:to).returns(opts[:to])
|
||||
|
||||
assert_not_equal st, obj
|
||||
end
|
||||
|
||||
test 'should fail equality check if to are not the same' do
|
||||
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
|
||||
st = ActiveModel::StateMachine::StateTransition.new(opts)
|
||||
|
||||
obj = stub
|
||||
obj.stubs(:from).returns(opts[:from])
|
||||
obj.stubs(:to).returns('blah')
|
||||
|
||||
assert_not_equal st, obj
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class StateTransitionGuardCheckTest < ActiveModel::TestCase
|
||||
test 'should return true of there is no guard' do
|
||||
opts = {:from => 'foo', :to => 'bar'}
|
||||
st = ActiveModel::StateMachine::StateTransition.new(opts)
|
||||
|
||||
assert st.perform(nil)
|
||||
end
|
||||
|
||||
uses_mocha 'checking ActiveModel StateMachine transition guard checks' do
|
||||
test 'should call the method on the object if guard is a symbol' do
|
||||
opts = {:from => 'foo', :to => 'bar', :guard => :test_guard}
|
||||
st = ActiveModel::StateMachine::StateTransition.new(opts)
|
||||
|
||||
obj = stub
|
||||
obj.expects(:test_guard)
|
||||
|
||||
st.perform(obj)
|
||||
end
|
||||
|
||||
test 'should call the method on the object if guard is a string' do
|
||||
opts = {:from => 'foo', :to => 'bar', :guard => 'test_guard'}
|
||||
st = ActiveModel::StateMachine::StateTransition.new(opts)
|
||||
|
||||
obj = stub
|
||||
obj.expects(:test_guard)
|
||||
|
||||
st.perform(obj)
|
||||
end
|
||||
|
||||
test 'should call the proc passing the object if the guard is a proc' do
|
||||
opts = {:from => 'foo', :to => 'bar', :guard => Proc.new {|o| o.test_guard}}
|
||||
st = ActiveModel::StateMachine::StateTransition.new(opts)
|
||||
|
||||
obj = stub
|
||||
obj.expects(:test_guard)
|
||||
|
||||
st.perform(obj)
|
||||
end
|
||||
end
|
||||
end
|
324
activemodel/test/state_machine_test.rb
Normal file
324
activemodel/test/state_machine_test.rb
Normal file
|
@ -0,0 +1,324 @@
|
|||
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
||||
|
||||
class StateMachineSubject
|
||||
include ActiveModel::StateMachine
|
||||
|
||||
state_machine do
|
||||
state :open, :exit => :exit
|
||||
state :closed, :enter => :enter
|
||||
|
||||
event :close, :success => :success_callback do
|
||||
transitions :to => :closed, :from => [:open]
|
||||
end
|
||||
|
||||
event :null do
|
||||
transitions :to => :closed, :from => [:open], :guard => :always_false
|
||||
end
|
||||
end
|
||||
|
||||
state_machine :bar do
|
||||
state :read
|
||||
state :ended
|
||||
|
||||
event :foo do
|
||||
transitions :to => :ended, :from => [:read]
|
||||
end
|
||||
end
|
||||
|
||||
def always_false
|
||||
false
|
||||
end
|
||||
|
||||
def success_callback
|
||||
end
|
||||
|
||||
def enter
|
||||
end
|
||||
def exit
|
||||
end
|
||||
end
|
||||
|
||||
class StateMachineSubjectSubclass < StateMachineSubject
|
||||
end
|
||||
|
||||
class StateMachineClassLevelTest < ActiveModel::TestCase
|
||||
test 'defines a class level #state_machine method on its including class' do
|
||||
assert StateMachineSubject.respond_to?(:state_machine)
|
||||
end
|
||||
|
||||
test 'defines a class level #state_machines method on its including class' do
|
||||
assert StateMachineSubject.respond_to?(:state_machines)
|
||||
end
|
||||
|
||||
test 'class level #state_machine returns machine instance' do
|
||||
assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machine
|
||||
end
|
||||
|
||||
test 'class level #state_machine returns machine instance with given name' do
|
||||
assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machine(:default)
|
||||
end
|
||||
|
||||
test 'class level #state_machines returns hash of machine instances' do
|
||||
assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machines[:default]
|
||||
end
|
||||
|
||||
test "should return a select friendly array of states in the form of [['Friendly name', 'state_name']]" do
|
||||
assert_equal [['Open', 'open'], ['Closed', 'closed']], StateMachineSubject.state_machine.states_for_select
|
||||
end
|
||||
end
|
||||
|
||||
class StateMachineInstanceLevelTest < ActiveModel::TestCase
|
||||
def setup
|
||||
@foo = StateMachineSubject.new
|
||||
end
|
||||
|
||||
test 'defines an accessor for the current state' do
|
||||
assert @foo.respond_to?(:current_state)
|
||||
end
|
||||
|
||||
test 'defines a state querying instance method on including class' do
|
||||
assert @foo.respond_to?(:open?)
|
||||
end
|
||||
|
||||
test 'defines an event! instance method' do
|
||||
assert @foo.respond_to?(:close!)
|
||||
end
|
||||
|
||||
test 'defines an event instance method' do
|
||||
assert @foo.respond_to?(:close)
|
||||
end
|
||||
end
|
||||
|
||||
class StateMachineInitialStatesTest < ActiveModel::TestCase
|
||||
def setup
|
||||
@foo = StateMachineSubject.new
|
||||
end
|
||||
|
||||
test 'sets the initial state' do
|
||||
assert_equal :open, @foo.current_state
|
||||
end
|
||||
|
||||
test '#open? should be initially true' do
|
||||
assert @foo.open?
|
||||
end
|
||||
|
||||
test '#closed? should be initially false' do
|
||||
assert !@foo.closed?
|
||||
end
|
||||
|
||||
test 'uses the first state defined if no initial state is given' do
|
||||
assert_equal :read, @foo.current_state(:bar)
|
||||
end
|
||||
end
|
||||
|
||||
class StateMachineEventFiringWithPersistenceTest < ActiveModel::TestCase
|
||||
def setup
|
||||
@subj = StateMachineSubject.new
|
||||
end
|
||||
|
||||
test 'updates the current state' do
|
||||
@subj.close!
|
||||
|
||||
assert_equal :closed, @subj.current_state
|
||||
end
|
||||
|
||||
uses_mocha "StateMachineEventFiringWithPersistenceTest with callbacks" do
|
||||
test 'fires the Event' do
|
||||
@subj.class.state_machine.events[:close].expects(:fire).with(@subj)
|
||||
@subj.close!
|
||||
end
|
||||
|
||||
test 'calls the success callback if one was provided' do
|
||||
@subj.expects(:success_callback)
|
||||
@subj.close!
|
||||
end
|
||||
|
||||
test 'attempts to persist if write_state is defined' do
|
||||
def @subj.write_state
|
||||
end
|
||||
|
||||
@subj.expects(:write_state)
|
||||
@subj.close!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class StateMachineEventFiringWithoutPersistence < ActiveModel::TestCase
|
||||
test 'updates the current state' do
|
||||
subj = StateMachineSubject.new
|
||||
assert_equal :open, subj.current_state
|
||||
subj.close
|
||||
assert_equal :closed, subj.current_state
|
||||
end
|
||||
|
||||
uses_mocha 'StateMachineEventFiringWithoutPersistence' do
|
||||
test 'fires the Event' do
|
||||
subj = StateMachineSubject.new
|
||||
|
||||
StateMachineSubject.state_machine.events[:close].expects(:fire).with(subj)
|
||||
subj.close
|
||||
end
|
||||
|
||||
test 'attempts to persist if write_state is defined' do
|
||||
subj = StateMachineSubject.new
|
||||
|
||||
def subj.write_state
|
||||
end
|
||||
|
||||
subj.expects(:write_state_without_persistence)
|
||||
|
||||
subj.close
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
uses_mocha 'StateMachinePersistenceTest' do
|
||||
class StateMachinePersistenceTest < ActiveModel::TestCase
|
||||
test 'reads the state if it has not been set and read_state is defined' do
|
||||
subj = StateMachineSubject.new
|
||||
def subj.read_state
|
||||
end
|
||||
|
||||
subj.expects(:read_state).with(StateMachineSubject.state_machine)
|
||||
|
||||
subj.current_state
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
uses_mocha 'StateMachineEventCallbacksTest' do
|
||||
class StateMachineEventCallbacksTest < ActiveModel::TestCase
|
||||
test 'should call aasm_event_fired if defined and successful for bang fire' do
|
||||
subj = StateMachineSubject.new
|
||||
def subj.aasm_event_fired(from, to)
|
||||
end
|
||||
|
||||
subj.expects(:event_fired)
|
||||
|
||||
subj.close!
|
||||
end
|
||||
|
||||
test 'should call aasm_event_fired if defined and successful for non-bang fire' do
|
||||
subj = StateMachineSubject.new
|
||||
def subj.aasm_event_fired(from, to)
|
||||
end
|
||||
|
||||
subj.expects(:event_fired)
|
||||
|
||||
subj.close
|
||||
end
|
||||
|
||||
test 'should call aasm_event_failed if defined and transition failed for bang fire' do
|
||||
subj = StateMachineSubject.new
|
||||
def subj.event_failed(event)
|
||||
end
|
||||
|
||||
subj.expects(:event_failed)
|
||||
|
||||
subj.null!
|
||||
end
|
||||
|
||||
test 'should call aasm_event_failed if defined and transition failed for non-bang fire' do
|
||||
subj = StateMachineSubject.new
|
||||
def subj.aasm_event_failed(event)
|
||||
end
|
||||
|
||||
subj.expects(:event_failed)
|
||||
|
||||
subj.null
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
uses_mocha 'StateMachineStateActionsTest' do
|
||||
class StateMachineStateActionsTest < ActiveModel::TestCase
|
||||
test "calls enter when entering state" do
|
||||
subj = StateMachineSubject.new
|
||||
subj.expects(:enter)
|
||||
subj.close
|
||||
end
|
||||
|
||||
test "calls exit when exiting state" do
|
||||
subj = StateMachineSubject.new
|
||||
subj.expects(:exit)
|
||||
subj.close
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class StateMachineInheritanceTest < ActiveModel::TestCase
|
||||
test "has the same states as its parent" do
|
||||
assert_equal StateMachineSubject.state_machine.states, StateMachineSubjectSubclass.state_machine.states
|
||||
end
|
||||
|
||||
test "has the same events as its parent" do
|
||||
assert_equal StateMachineSubject.state_machine.events, StateMachineSubjectSubclass.state_machine.events
|
||||
end
|
||||
end
|
||||
|
||||
class StateMachineSubject
|
||||
state_machine :chetan_patil, :initial => :sleeping do
|
||||
state :sleeping
|
||||
state :showering
|
||||
state :working
|
||||
state :dating
|
||||
|
||||
event :wakeup do
|
||||
transitions :from => :sleeping, :to => [:showering, :working]
|
||||
end
|
||||
|
||||
event :dress do
|
||||
transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes
|
||||
transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) }
|
||||
end
|
||||
end
|
||||
|
||||
def wear_clothes(shirt_color, trouser_type)
|
||||
end
|
||||
end
|
||||
|
||||
class StateMachineWithComplexTransitionsTest < ActiveModel::TestCase
|
||||
def setup
|
||||
@subj = StateMachineSubject.new
|
||||
end
|
||||
|
||||
test 'transitions to specified next state (sleeping to showering)' do
|
||||
@subj.wakeup! :showering
|
||||
|
||||
assert_equal :showering, @subj.current_state(:chetan_patil)
|
||||
end
|
||||
|
||||
test 'transitions to specified next state (sleeping to working)' do
|
||||
@subj.wakeup! :working
|
||||
|
||||
assert_equal :working, @subj.current_state(:chetan_patil)
|
||||
end
|
||||
|
||||
test 'transitions to default (first or showering) state' do
|
||||
@subj.wakeup!
|
||||
|
||||
assert_equal :showering, @subj.current_state(:chetan_patil)
|
||||
end
|
||||
|
||||
test 'transitions to default state when on_transition invoked' do
|
||||
@subj.dress!(nil, 'purple', 'dressy')
|
||||
|
||||
assert_equal :working, @subj.current_state(:chetan_patil)
|
||||
end
|
||||
|
||||
uses_mocha "StateMachineWithComplexTransitionsTest on_transition tests" do
|
||||
test 'calls on_transition method with args' do
|
||||
@subj.wakeup! :showering
|
||||
|
||||
@subj.expects(:wear_clothes).with('blue', 'jeans')
|
||||
@subj.dress! :working, 'blue', 'jeans'
|
||||
end
|
||||
|
||||
test 'calls on_transition proc' do
|
||||
@subj.wakeup! :showering
|
||||
|
||||
@subj.expects(:wear_clothes).with('purple', 'slacks')
|
||||
@subj.dress!(:dating, 'purple', 'slacks')
|
||||
end
|
||||
end
|
||||
end
|
39
activemodel/test/test_helper.rb
Normal file
39
activemodel/test/test_helper.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
$:.unshift "#{File.dirname(__FILE__)}/../lib"
|
||||
$:.unshift File.dirname(__FILE__)
|
||||
|
||||
require 'test/unit'
|
||||
require 'active_model'
|
||||
require 'active_model/state_machine'
|
||||
require 'active_support/callbacks' # needed by ActiveModel::TestCase
|
||||
require 'active_support/test_case'
|
||||
|
||||
def uses_gem(gem_name, test_name, version = '> 0')
|
||||
require 'rubygems'
|
||||
gem gem_name.to_s, version
|
||||
require gem_name.to_s
|
||||
yield
|
||||
rescue LoadError
|
||||
$stderr.puts "Skipping #{test_name} tests. `gem install #{gem_name}` and try again."
|
||||
end
|
||||
|
||||
# Wrap tests that use Mocha and skip if unavailable.
|
||||
unless defined? uses_mocha
|
||||
def uses_mocha(test_name, &block)
|
||||
uses_gem('mocha', test_name, '>= 0.5.5', &block)
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
require 'rubygems'
|
||||
require 'ruby-debug'
|
||||
Debugger.start
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
ActiveSupport::TestCase.send :include, ActiveSupport::Testing::Default
|
||||
|
||||
module ActiveModel
|
||||
class TestCase < ActiveSupport::TestCase
|
||||
include ActiveSupport::Testing::Default
|
||||
end
|
||||
end
|
|
@ -307,4 +307,9 @@ module ActiveSupport
|
|||
end
|
||||
end
|
||||
|
||||
# in case active_support/inflector is required without the rest of active_support
|
||||
require 'active_support/inflections'
|
||||
require 'active_support/core_ext/string/inflections'
|
||||
unless String.included_modules.include?(ActiveSupport::CoreExtensions::String::Inflections)
|
||||
String.send :include, ActiveSupport::CoreExtensions::String::Inflections
|
||||
end
|
Loading…
Reference in a new issue