lib/state_machine/integrations/mongoid.rb in state_machine-mongoid-0.1.4 vs lib/state_machine/integrations/mongoid.rb in state_machine-mongoid-0.1.5
- old
+ new
@@ -1,319 +1,104 @@
module StateMachine
- module Integrations
+ module Integrations #:nodoc:
module Mongoid
+ include ActiveModel
+
# The default options to use for state machines using this integration
- class << self; attr_reader :defaults; end
- @defaults = {:action => :save, :use_transactions => false}
-
+ @defaults = {:action => :save, :use_transactions => false}
+
# Should this integration be used for state machines in the given class?
- # Classes that include Mongoid::Resource will automatically use the
- # Mongoid integration.
+ # Classes that include Mongoid::Document will automatically use
+ # the Mongoid integration.
def self.matches?(klass)
defined?(::Mongoid::Document) && klass <= ::Mongoid::Document
end
-
+
protected
-
+ # Never add observer support
+ def supports_observers?
+ false
+ end
+
+ # Always adds validation support
+ def supports_validations?
+ true
+ end
+
+ # Only runs validations on the action if using <tt>:save</tt>
+ def runs_validations_on_action?
+ action == :save
+ end
+
+ # Only adds dirty tracking support if ActiveRecord supports it
+ def supports_dirty_tracking?(object)
+ defined?(::Mongoid::Dirty) && object.respond_to?("#{attribute}_changed?") || super
+ end
+
+ # Always uses the <tt>:mongoid</tt> translation scope
+ def i18n_scope
+ :mongoid
+ end
+
+ # Only allows translation of I18n is available
+ def translate(klass, key, value)
+ if defined?(I18n)
+ super
+ else
+ value ? value.to_s.humanize.downcase : 'nil'
+ end
+ end
+
# Defines an initialization hook into the owner class for setting the
# initial state of the machine *before* any attributes are set on the
# object
def define_state_initializer
+ @instance_helper_module.class_eval <<-end_eval, __FILE__, __LINE__
+ # Ensure that the attributes setter gets used to force initialization
+ # of the state machines
+ def initialize(attributes = nil, *args)
+ attributes ||= {}
+ super
+ end
+
+ # Hooks in to attribute initialization to set the states *prior*
+ # to the attributes being set
+ def attributes=(new_attributes, *args)
+ if new_record? && !@initialized_state_machines
+ @initialized_state_machines = true
+
+ ignore = if new_attributes
+ attributes = new_attributes.dup
+ attributes.stringify_keys!
+ sanitize_for_mass_assignment(attributes).keys
+ else
+ []
+ end
+
+ initialize_state_machines(:dynamic => false, :ignore => ignore)
+ super
+ initialize_state_machines(:dynamic => true, :ignore => ignore)
+ else
+ super
+ end
+ end
+ end_eval
end
+
+ # Adds support for defining the attribute predicate, while providing
+ # compatibility with the default predicate which determines whether
+ # *anything* is set for the attribute's value
+ def define_state_predicate
+ name = self.name
+
+ # Still use class_eval here instance of define_instance_method since
+ # we need to be able to call +super+
+ @instance_helper_module.class_eval do
+ define_method("#{name}?") do |*args|
+ args.empty? ? super(*args) : self.class.state_machine(name).states.matches?(self, *args)
+ end
+ end
+ end
+
end
end
end
-# module StateMachine
-# module Integrations #:nodoc:
-# # Adds support for integrating state machines with Mongoid models.
-# #
-# # == Examples
-# #
-# # Below is an example of a simple state machine defined within a
-# # Mongoid model:
-# #
-# # class Vehicle
-# # include Mongoid::Document
-# #
-# # state_machine :initial => :parked do
-# # event :ignite do
-# # transition :parked => :idling
-# # end
-# # end
-# # end
-# #
-# # The examples in the sections below will use the above class as a
-# # reference.
-# #
-# # == Actions
-# #
-# # By default, the action that will be invoked when a state is transitioned
-# # is the +save+ action. This will cause the record to save the changes
-# # made to the state machine's attribute. *Note* that if any other changes
-# # were made to the record prior to transition, then those changes will
-# # be saved as well.
-# #
-# # For example,
-# #
-# # vehicle = Vehicle.create # => #<Vehicle id: 1, name: nil, state: "parked">
-# # vehicle.name = 'Ford Explorer'
-# # vehicle.ignite # => true
-# # vehicle.reload # => #<Vehicle id: 1, name: "Ford Explorer", state: "idling">
-# #
-# # == Events
-# #
-# # As described in StateMachine::InstanceMethods#state_machine, event
-# # attributes are created for every machine that allow transitions to be
-# # performed automatically when the object's action (in this case, :save)
-# # is called.
-# #
-# # In Mongoid, these automated events are run in the following order:
-# # * before validation - Run before callbacks and persist new states, then validate
-# # * before save - If validation was skipped, run before callbacks and persist new states, then save
-# # * after save - Run after callbacks
-# #
-# # For example,
-# #
-# # vehicle = Vehicle.create # => #<Vehicle id: 1, name: nil, state: "parked">
-# # vehicle.state_event # => nil
-# # vehicle.state_event = 'invalid'
-# # vehicle.valid? # => false
-# # vehicle.errors.full_messages # => ["State event is invalid"]
-# #
-# # vehicle.state_event = 'ignite'
-# # vehicle.valid? # => true
-# # vehicle.save # => true
-# # vehicle.state # => "idling"
-# # vehicle.state_event # => nil
-# #
-# # Note that this can also be done on a mass-assignment basis:
-# #
-# # vehicle = Vehicle.create(:state_event => 'ignite') # => #<Vehicle id: 1, name: nil, state: "idling">
-# # vehicle.state # => "idling"
-# #
-# # This technique is always used for transitioning states when the +save+
-# # action (which is the default) is configured for the machine.
-# #
-# # === Security implications
-# #
-# # Beware that public event attributes mean that events can be fired
-# # whenever mass-assignment is being used. If you want to prevent malicious
-# # users from tampering with events through URLs / forms, the attribute
-# # should be protected like so:
-# #
-# # class Vehicle
-# # include Mongoid::Document
-# #
-# # attr_protected :state_event
-# # # attr_accessible ... # Alternative technique
-# #
-# # state_machine do
-# # ...
-# # end
-# # end
-# #
-# # If you want to only have *some* events be able to fire via mass-assignment,
-# # you can build two state machines (one public and one protected) like so:
-# #
-# # class Vehicle
-# # include Mongoid::Document
-# #
-# # attr_protected :state_event # Prevent access to events in the first machine
-# #
-# # state_machine do
-# # # Define private events here
-# # end
-# #
-# # # Public machine targets the same state as the private machine
-# # state_machine :public_state, :attribute => :state do
-# # # Define public events here
-# # end
-# # end
-# #
-# # == Validation errors
-# #
-# # If an event fails to successfully fire because there are no matching
-# # transitions for the current record, a validation error is added to the
-# # record's state attribute to help in determining why it failed and for
-# # reporting via the UI.
-# #
-# # For example,
-# #
-# # vehicle = Vehicle.create(:state => 'idling') # => #<Vehicle id: 1, name: nil, state: "idling">
-# # vehicle.ignite # => false
-# # vehicle.errors.full_messages # => ["State cannot transition via \"ignite\""]
-# #
-# # If an event fails to fire because of a validation error on the record and
-# # *not* because a matching transition was not available, no error messages
-# # will be added to the state attribute.
-# #
-# # == Scopes
-# #
-# # To assist in filtering models with specific states, a series of basic
-# # scopes are defined on the model for finding records with or without a
-# # particular set of states.
-# #
-# # These scopes are essentially the functional equivalent of the following
-# # definitions:
-# #
-# # class Vehicle
-# # include Mongoid::Document
-# #
-# # def self.with_states(*states)
-# # all(:conditions => {:state => {'$in' => states}})
-# # end
-# # # with_states also aliased to with_state
-# #
-# # def self.without_states(*states)
-# # all(:conditions => {:state => {'$nin' => states}})
-# # end
-# # # without_states also aliased to without_state
-# # end
-# #
-# # *Note*, however, that the states are converted to their stored values
-# # before being passed into the query.
-# #
-# # Because of the way named scopes work in Mongoid, they *cannot* be
-# # chained.
-# #
-# # == Callbacks
-# #
-# # All before/after transition callbacks defined for Mongoid models
-# # behave in the same way that other Mongoid callbacks behave. The
-# # object involved in the transition is passed in as an argument.
-# #
-# # For example,
-# #
-# # class Vehicle
-# # include Mongoid::Document
-# #
-# # state_machine :initial => :parked do
-# # before_transition any => :idling do |vehicle|
-# # vehicle.put_on_seatbelt
-# # end
-# #
-# # before_transition do |vehicle, transition|
-# # # log message
-# # end
-# #
-# # event :ignite do
-# # transition :parked => :idling
-# # end
-# # end
-# #
-# # def put_on_seatbelt
-# # ...
-# # end
-# # end
-# #
-# # Note, also, that the transition can be accessed by simply defining
-# # additional arguments in the callback block.
-# module Mongoid
-# include ActiveModel
-#
-# # The default options to use for state machines using this integration
-# @defaults = {:action => :save}
-#
-# # Should this integration be used for state machines in the given class?
-# # Classes that include Mongoid::Document will automatically use the
-# # Mongoid integration.
-# def self.matches?(klass)
-# defined?(::Mongoid::Document) && klass <= ::Mongoid::Document
-# end
-#
-# # Adds a validation error to the given object (no i18n support)
-# def invalidate(object, attribute, message, values = [])
-# object.errors.add(self.attribute(attribute), generate_message(message, values))
-# end
-#
-# protected
-# # Does not support observers
-# def supports_observers?
-# false
-# end
-#
-# # Always adds validation support
-# def supports_validations?
-# true
-# end
-#
-# # Only runs validations on the action if using <tt>:save</tt>
-# def runs_validations_on_action?
-# action == :save
-# end
-#
-# # Always adds dirty tracking support
-# def supports_dirty_tracking?(object)
-# true
-# end
-#
-# # Don't allow callback terminators
-# def callback_terminator
-# end
-#
-# # Defines an initialization hook into the owner class for setting the
-# # initial state of the machine *before* any attributes are set on the
-# # object
-# def define_state_initializer
-# @instance_helper_module.class_eval <<-end_eval, __FILE__, __LINE__
-# def initialize(attrs = {}, *args)
-# from_database = args.first
-#
-# if !from_database && (!attrs || !attrs.stringify_keys.key?('_id'))
-# filtered = respond_to?(:filter_protected_attrs) ? filter_protected_attrs(attrs) : attrs
-# ignore = filtered ? filtered.keys : []
-#
-# initialize_state_machines(:dynamic => false, :ignore => ignore)
-# super
-# initialize_state_machines(:dynamic => true, :ignore => ignore)
-# else
-# super
-# end
-# end
-# end_eval
-# end
-#
-# # Skips defining reader/writer methods since this is done automatically
-# def define_state_accessor
-# owner_class.field(attribute, :type => String) unless owner_class.fields.nil? #.include?(attribute)
-#
-# name = self.name
-# owner_class.validates_each(attribute, :logic => lambda {|*|
-# machine = self.class.state_machine(name)
-# machine.invalidate(self, :state, :invalid) unless machine.states.match(self)
-# })
-# end
-#
-# # Adds support for defining the attribute predicate, while providing
-# # compatibility with the default predicate which determines whether
-# # *anything* is set for the attribute's value
-# def define_state_predicate
-# name = self.name
-#
-# # Still use class_eval here instance of define_instance_method since
-# # we need to be able to call +super+
-# @instance_helper_module.class_eval do
-# define_method("#{name}?") do |*args|
-# args.empty? ? super(*args) : self.class.state_machine(name).states.matches?(self, *args)
-# end
-# end
-# end
-#
-# # Adds hooks into validation for automatically firing events
-# def define_action_helpers
-# super(action == :save ? :create_or_update : action)
-# end
-#
-# # Creates a scope for finding records *with* a particular state or
-# # states for the attribute
-# def create_with_scope(name)
-# lambda {|model, values| model.all(:conditions => {attribute => {'$in' => values}})}
-# end
-#
-# # Creates a scope for finding records *without* a particular state or
-# # states for the attribute
-# def create_without_scope(name)
-# lambda {|model, values| model.all(:conditions => {attribute => {'$nin' => values}})}
-# end
-# end
-# end
-# end