state-machine-1.2.0/0000755000175000017500000000000012305405267013616 5ustar boutilboutilstate-machine-1.2.0/state_machine.gemspec0000644000175000017500000000213712305405267017772 0ustar boutilboutil$LOAD_PATH.unshift File.expand_path('../lib', __FILE__) require 'state_machine/version' Gem::Specification.new do |s| s.name = "state_machine" s.version = StateMachine::VERSION s.authors = ["Aaron Pfeifer"] s.email = "aaron@pluginaweek.org" s.homepage = "http://www.pluginaweek.org" s.description = "Adds support for creating state machines for attributes on any Ruby class" s.summary = "State machines for attributes" s.require_paths = ["lib"] ignores = File.read(".gitignore").split.map {|i| i.sub(/\/$/, "/*").sub(/^[^\/]/, "**/\\0")} s.files = (Dir[".*"] + Dir["**/*"]).select {|f| File.file?(f) && !ignores.any? {|i| File.fnmatch(i, "/#{f}")}} s.test_files = s.files.grep(/^test\//) s.rdoc_options = %w(--line-numbers --inline-source --title state_machine --main README.md) s.extra_rdoc_files = %w(README.md CHANGELOG.md LICENSE) s.add_development_dependency("rake") s.add_development_dependency("simplecov") s.add_development_dependency("appraisal", "~> 0.4.0") end state-machine-1.2.0/CHANGELOG.md0000644000175000017500000006623612305405267015444 0ustar boutilboutil# master ## 1.2.0 / 2013-03-30 * Allow multiple whitelisted / blacklisted :to states when definining transitions * Fix event attributes not being processed when saved via association autosaving * Fix Mongoid integration not setting initial state attributes properly for associations * Completely rewrite ORM action hooks to behave more consistently across the board * Change transitions to be executed the same whether using ORM save actions or not * Fix around_transition callbacks not being executed properly in ORM integrations * Fix additional transitions not being able to be fired in transition callbacks * Add documentation on the order in which transition callbacks run within ORM integrations * Update README to include a topic on explicit vs. implicit event transitions [Nathan Long] * Fix around_transition pausing not being marked as unsupported for rubinius [Daniel Huckstep] * Fix all / any / same matchers not being able to be used in :from / :to options in transitions and callbacks * Fix ActiveModel locale paths not being loaded properly under certain JRuby environments [Brad Heller] * Remove dependency on the validation_class_methods plugin for Sequel 2.12.0+ [Casey Howard] * Remove dependency on command-line git binaries from within the gemspec * Fix deprecation warnings on ruby-graphviz 1.0.3+ * Update minimum required ruby-graphviz version to 0.9.17 * Fix aliased fields in Mongoid 2.4.0+ not being automatically detected * Add Mongoid 3.0.0+ support * Fix scopes getting generated multiple times when the singular / plural machine names are the same but of a different type (symbol vs. string) * Add initial support for ActiveRecord / ActiveModel 4.0.0 beta * Fix static initial states always being set even when the state has already been initialized in non-ORM integrations * Generate a warning when the ORM's backend and state_machine define conflicting initial states for an attribute * Fix all Ruby warnings * Fix callbacks not working for methods that respond via method_missing [Balwant Kane] * Fix observer callbacks being run when disabled in ActiveModel / ActiveRecord integrations * Add YARD integration for autogenerating documentation / embedding visualizations of state machines * Allow states / events to be drawn with their human name instead of their internal name ## 1.1.2 / 2012-01-20 * Fix states not being initialized properly on ActiveRecord 3.2+ ## 1.1.1 / 2011-12-31 * Fix fields being defined for Mongoid / MongoMapper state attributes even if they're already defined in the model * Raise error when states / events are referenced in a definition with different types (e.g. both Strings and Symbols) * Allow all states / events to be looked up by their string / symbol equivalent * Allow state_machine to be loaded without extensions to the Ruby core ## 1.1.0 / 2011-11-13 * Allow the transitions / known states for an event to be reset * Add fire_#{name}_event instance method for firing an arbitrary event on a state machine * Improve InvalidTransition exception messages to include the failure reason(s) in ORM integrations * Don't allow around_transitions to attempt to be called in multiple execution contexts when run in jruby * Allow :from option to be used in transitions defined within state contexts * Fix arguments / block not being preserved when chaining methods defined in state contexts * Fix super not being allowed when a method is defined for multiple state contexts * Change loopbacks to only cause objects to be persisted when the ORM decides it's necessary, instead of always forcing persistence * Fix Mongoid 2.3.x integrations not initializing dynamic states in the same manner as other integrations with initialize callbacks ## 1.0.3 / 2011-11-03 * Fix MongoMapper 0.10.0+ integrations not matching versions properly * Update warnings for method conflicts to include instructions on how to ignore conflicts * Fix state initialization in Mongoid 2.3.x integrations [Durran Jordan] * Fix after_transition callbacks sometimes not running in Mongoid 2.2.x integrations * Automatically load the plugins required in Sequel integrations * Allow all / any matcher helpers to be used when defining states / events * Allow states / events to be referenced by the string equivalent of their name * Fix observer callbacks being run incorrectly when using nil states in ActiveModel-based integrations * Remove ActiveModel Observer method chains in order to better ensure compatibility * Update DataMapper integration for 1.2.0+ support [Markus Schirp] * Provide access to the human state name in invalid_transition translations * Add support for i18n keys in the form of #{i18n_scope}.state_machines.#{model_name}.states/events.#{value} * Clarify documentation on writing to state machine attributes, using factory_girl and can_#{event} / #{event}_transition helpers * Add documentation for dynmically generating state machines ## 1.0.2 / 2011-08-09 * Allow transitions to be defined within a state, event, or machine context * Use supported framework hooks for integrating Sequel 3.24.0+ * Use appraisal for testing integrations * Improve documentation on the handling of method conflicts * Update Mongoid integration for 2.1.0+ support * Fix ActiveRecord machine state predicates incorrectly calling superclass implementation when using targeted attributes * Fix error when defining states with the same name as the state's machine in ActiveRecord, MongoMapper, and Mongoid integrations * Fix machine state predicate not calling superclass implementation if defined after machine definition * Generate warnings when defining a helper method more than once * Fix multiple machines not being able to target the same attribute if all possible states aren't defined in each * Fix ActiveModel / DataMapper integrations not overriding StateMachine::Machine#after_initialize properly * Improve documentation for overriding states and integration transactions ## 1.0.1 / 2011-05-30 * Add the ability to ignore method conflicts for helpers * Generate warnings for any helper, not just state helpers, that has a conflicting method defined in the class * Fix scopes in Sequel not working if the table name contains double underscores or is not a string/symbol * Add full support for chaining state scopes within Sequel integrations * Fix Rails 3.1 deprecation warnings for configuring engine locales [Stefan Penner] ## 1.0.0 / 2011-05-12 * Celebrate ## 0.10.4 / 2011-04-14 * Fix translations not being available under certain environments in Rails applications ## 0.10.3 / 2011-04-07 * Fix state initialization failing in ActiveRecord 3.0.2+ when using with_state scopes for the default scope ## 0.10.2 / 2011-03-31 * Use more integrated state initialization hooks for ActiveRecord, Mongoid, and Sequel * Remove mass-assignment filtering usage in all ORM integrations * Only support official Mongoid 2.0.0 release and up (no more RC support) * Fix attributes getting initialized more than once if different state machines use the same attribute * Only initialize states if state is blank and blank is not a valid state * Fix instance / class helpers failing when used with certain libraries (such as Thin) ## 0.10.1 / 2011-03-22 * Fix classes with multiple state machines failing to initialize in ActiveRecord / Mongoid / Sequel integrations ## 0.10.0 / 2011-03-19 * Support callback terminators in MongoMapper 0.9.0+ * Fix pluralization integration on DataMapper 1.0.0 and 1.1.0 * Allow transition guards to be bypassed for event / transition / path helpers * Allow state / condition requirements to be specified for all event / transition / path helpers * Add the ability to skip automatically initializing state machines on #initialize * Add #{name}_paths for walking the available paths in a state machine * Add Mongoid 2.0.0+ support * Use around hooks to improve compatibility with other libraries in ActiveModel / ActiveRecord / MongoMapper integrations * Add support for MassAssignmentSecurity feature in ActiveModel integrations * Add support for more observer hooks within MongoMapper integrations * Add i18n support for MongoMapper validation errors * Update support for MongoMapper integration based on rails3 branch * Fix objects not getting marked as dirty in all integrations when #{name}_event is set * Generate warnings when conflicting state / event names are detected * Allow fallback to generic state predicates when individual predicates are already defined in the owner class * Replace :include_failures after_transition option with new after_failure callback * Provide access to transition context when raising InvalidEvent / InvalidTransition exceptions ## 0.9.4 / 2010-08-01 * Fix validation / save hooks in Sequel 3.14.0+ * Fix integration with dirty attribute tracking on DataMapper 1.0.1+ * Fix DataMapper 1.0.1+ tests producing warnings * Fix validation error warnings in ActiveModel / ActiveRecord 3.0.0 beta5+ * Fix mass-assignment sanitization breaking in ActiveRecord 3.0.0 beta5+ [Akira Matsuda] ## 0.9.3 / 2010-06-26 * Allow access to human state / event names in transitions and for the current state * Use human state / event names in error messages * Fix event names being used inconsistently in error messages * Allow access to the humanized version of state / event names via human_state_name / human_state_event_name * Allow MongoMapper 0.8.0+ scopes to be chainable * Fix i18n deprecation warnings in ActiveModel / ActiveRecord 3.0.0.beta4 * Fix default error message translations overriding existing locales in ActiveModel / ActiveRecord ## 0.9.2 / 2010-05-24 * Fix MongoMapper integration failing in Ruby 1.9.2 * Fix Rakefile not loading in Ruby 1.9.2 [Andrea Longhi] * Fix nil / false :integration configuration not being respected ## 0.9.1 / 2010-05-02 * Fix ActiveRecord 2.0.0 - 2.2.3 integrations failing if version info isn't already loaded * Fix integration with dirty attribute tracking on DataMapper 0.10.3 * Fix observers failing in ActiveRecord 3.0.0.beta4+ integrations * Fix deprecation warning in Rails 3 railtie [Chris Yuan] ## 0.9.0 / 2010-04-12 * Use attribute-based event transitions whenever possible to ensure consistency * Fix action helpers being defined when the action is **only** defined in the machine's owner class * Disable attribute-based event transitions in DataMapper 0.9.4 - 0.9.6 when dm-validations is being used * Add support for DataMapper 0.10.3+ * Add around_transition callbacks * Fix transition failures during save not being handled correctly in Sequel 2.12.0+ * Fix attribute-based event transitions not hooking in properly in DataMapper 0.10.0+ and Sequel 2.12.0+ * Fix dynamic initial states causing errors in Ruby 1.9+ if no arguments are defined in the block * Add MongoMapper 0.5.5+ support * Add ActiveModel 3.0+ support for use with integrations that implement its interface * Fix DataMapper integration failing when ActiveSupport is loaded in place of Extlib * Add version dependencies for ruby-graphviz * Remove app-specific rails / merb rake tasks in favor of always running state_machine:draw * Add Rails 3 railtie for automatically loading rake tasks when installed as a gem ## 0.8.1 / 2010-03-14 * Release gems via rake-gemcutter instead of rubyforge * Move rake tasks to lib/tasks * Dispatch state behavior to the superclass if it's undefined for a particular state [Sandro Turriate and Tim Pope] * Fix state / event names not supporting i18n in ActiveRecord * Fix original ActiveRecord::Observer#update not being used for non-state_machine callbacks [Jeremy Wells] * Add support for ActiveRecord 3.0 * Fix without_{name} scopes not quoting columns in ActiveRecord [Jon Evans] * Fix without_{name} scopes not scoping columns to the table in ActiveRecord and Sequel [Jon Evans] * Fix custom state attributes not being marked properly as changed in ActiveRecord * Fix tracked attributes changes in ActiveRecord / DataMapper integrations not working correctly for non-loopbacks [Joe Lind] * Fix plural scope names being incorrect for DataMapper 0.9.4 - 0.9.6 * Fix deprecation warnings for ruby-graphviz 0.9.0+ * Add support for ActiveRecord 2.0.* * Fix nil states being overwritten when they're explicitly set in ORM integrations * Fix default states not getting set in ORM integrations if the column has a default * Fix event transitions being kept around while running actions/callbacks, sometimes preventing object marshalling ## 0.8.0 / 2009-08-15 * Add support for DataMapper 0.10.0 * Always interpet nil return values from actions as failed attempts * Fix loopbacks not causing records to save in ORM integrations if no other fields were changed * Fix events not failing with useful errors when an object's state is invalid * Use more friendly NoMethodError messages for state-driven behaviors * Fix before_transition callbacks getting run twice when using event attributes in ORM integrations * Add the ability to query for the availability of specific transitions on an object * Allow after_transition callbacks to be explicitly run on failed attempts * By default, don't run after_transition callbacks on failed attempts * Fix not allowing multiple methods to be specified as arguments in callbacks * Fix initial states being set when loading records from the database in Sequel integration * Allow static initial states to be set earlier in the initialization of an object * Use friendly validation errors for nil states * Fix states not being validated properly when using custom names in ActiveRecord / DataMapper integrations ## 0.7.6 / 2009-06-17 * Allow multiple state machines on the same class to target the same attribute * Add support for :attribute to customize the attribute target, assuming the name is the first argument of #state_machine * Simplify reading from / writing to machine-related attributes on objects * Fix locale for ActiveRecord getting added to the i18n load path multiple times [Reiner Dieterich] * Fix callbacks, guards, and state-driven behaviors not always working on tainted classes [Brandon Dimcheff] * Use Ruby 1.9's built-in Object#instance_exec for bound callbacks when it's available * Improve performance of cached dynamic state lookups by 25% ## 0.7.5 / 2009-05-25 * Add built-in caching for dynamic state values when the value only needs to be generated once * Fix flawed example for using record ids as state values * Don't evaluate state values until they're actually used in an object instance * Make it easier to use event attributes for actions defined in the same class as the state machine * Fix #save/save! running transitions in ActiveRecord integrations even when a machine's action is not :save ## 0.7.4 / 2009-05-23 * Fix #save! not firing event attributes properly in ActiveRecord integrations * Fix log files being included in gems ## 0.7.3 / 2009-04-25 * Require DataMapper version be >= 0.9.4 * Explicitly load Sequel's built-in inflector (>= 2.12.0) for scope names * Don't use qualified name for event attributes * Fix #valid? being defined for DataMapper resources when dm-validations isn't loaded * Add auto-validation of values allowed for the state attribute in ORM integrations ## 0.7.2 / 2009-04-08 * Add support for running multiple methods in a callback without using blocks * Add more flexibility around how callbacks are defined * Add security documentation around mass-assignment in ORM integrations * Fix event attribute transitions being publicly accessible ## 0.7.1 / 2009-04-05 * Fix machines failing to generate graphs when run from Merb tasks ## 0.7.0 / 2009-04-04 * Add #{attribute}_event for automatically firing events when the object's action is called * Make it easier to override state-driven behaviors * Rollback state changes when the action fails during transitions * Use :messages instead of :invalid_message for customizing validation errors * Use more human-readable validation errors * Add support for more ActiveRecord observer hooks * Add support for targeting multiple specific state machines in DataMapper observer hooks * Don't pass the result of the action as an argument to callbacks (access via Transition#result) * Fix incorrect results being used when running transitions in parallel * Fix transition args not being set when run in parallel * Allow callback terminators to be set on an application-wide basis * Only catch :halt during before / after transition callbacks * Fix ActiveRecord predicates being overwritten if they're already defined in the class * Allow machine options to be set on an integration-wide basis * Turn transactions off by default in DataMapper integrations * Add support for configuring the use of transactions * Simplify reading/writing of attributes * Simplify access to state machines via #state_machine(:attribute) without generating dupes * Fix assumptions that dm-validations is always available in DataMapper integration * Automatically define DataMapper properties for machine attributes if they don't exist * Add Transition#qualified_event, #qualified_from_name, and #qualified_to_name * Add #fire_events / #fire_events! for running events on multiple state machines in parallel * Rename next_#{event}_transition to #{event}_transition * Add #{attribute}_transitions for getting the list of transitions that can be run on an object * Add #{attribute}_events for getting the list of events that can be fired on an object * Use generated non-bang event when running bang version so that overriding one affects the other * Provide access to arguments passed into an event from transition callbacks via Transition#args ## 0.6.3 / 2009-03-10 * Add support for customizing the graph's orientation * Use the standard visualizations for initial (open arrow) and final (double circle) states * Highlight final states in GraphViz drawings ## 0.6.2 / 2009-03-08 * Make it easier to override generated instance / class methods ## 0.6.1 / 2009-03-07 * 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 * Allow multiple conditions for callbacks / class behaviors * Add support for state-driven class behavior with :if/:unless options * Alias Machine#event as Machine#on * Fix nil from/to states not being handled properly * Simplify hooking callbacks into loopbacks * Add simplified transition/callback requirement syntax ## 0.5.2 / 2009-02-17 * Improve pretty-print of events * Simplify state/event matching design, improving guard performance by 30% * Add better error notification when conflicting guard options are defined * Fix scope name pluralization not being applied correctly ## 0.5.1 / 2009-02-11 * Allow states to be drawn as ellipses to accommodate long names * Fix rake tasks not being registered in Rails/Merb applications * Never automatically define machine attribute accessors when using an integration ## 0.5.0 / 2009-01-11 * Add to_name and from_name to transition objects * Add nicely formatted #inspect for transitions * Fix ActiveRecord integrations failing when the database doesn't exist yet * Fix states not being drawn in GraphViz graphs in the correct order * Add nicely formatted #inspect for states and events * Simplify machine context-switching * Store events/states in enumerable node collections * No longer allow subclasses to change the integration * Move fire! action logic into the Event class (no longer calls fire action on the object) * Allow states in subclasses to have different values * Recommend that all states be referenced as symbols instead of strings * All states must now be named (and can be associated with other value types) * Add support for customizing the actual stored value for a state * Add compatibility with Ruby 1.9+ ## 0.4.3 / 2008-12-28 * Allow dm-observer integration to be optional * Fix non-lambda callbacks not working for DataMapper/Sequel ## 0.4.2 / 2008-12-28 * Fix graphs not being drawn the same way consistently * Add support for sharing transitions across multiple events * Add support for state-driven behavior * Simplify initialize hooks, requiring super to be called instead * Add :namespace option for generated state predicates / event methods ## 0.4.1 / 2008-12-16 * Fix nil states not being handled properly in guards, known states, or visualizations * Fix the same node being used for different dynamic states in GraphViz output * Always include initial state in the list of known states even if it's dynamic * Use consistent naming scheme for dynamic states in GraphViz output * Allow blocks to be directly passed into machine class * Fix attribute predicates not working on attributes that represent columns in ActiveRecord ## 0.4.0 / 2008-12-14 * Remove the PluginAWeek namespace * Add generic attribute predicate (e.g. "#{attribute}?(state_name)") and state predicates (e.g. "#{state}?") * Add Sequel support * Fix aliasing :initialize on ActiveRecord models causing warnings when the environment is reloaded * Fix ActiveRecord state machines trying to query the database on unmigrated models * Fix initial states not getting set when the current value is an empty string [Aaron Gibralter] * Add rake tasks for generating graphviz files for state machines [Nate Murray] * Fix initial state not being included in list of known states * Add other_states directive for defining additional states not referenced in transitions or callbacks [Pete Forde] * Add next_#{event}_transition for getting the next transition that would be performed if the event were invoked * Add the ability to override the pluralized name of an attribute for creating scopes * Add the ability to halt callback chains by: throw :halt * Add support for dynamic to states in transitions (e.g. :to => lambda {Time.now}) * Add support for using real blocks in before_transition/after_transition calls instead of using the :do option * Add DataMapper support * Include states referenced in transition callbacks in the list of a machine's known states * Only generate the known states for a machine on demand, rather than calculating beforehand * Add the ability to skip state change actions during a transition (e.g. vehicle.ignite(false)) * Add the ability for the state change action (e.g. `save` for ActiveRecord) to be configurable * Allow state machines to be defined on **any** Ruby class, not just ActiveRecord (removes all external dependencies) * Refactor transitions, guards, and callbacks for better organization/design * Use a class containing the transition context in callbacks, rather than an ordered list of each individual attribute * Add without_#{attribute} named scopes (opposite of the existing with_#{attribute} named scopes) [Sean O'Brien] ## 0.3.1 / 2008-10-26 * Fix the initial state not getting set when the state attribute is mass-assigned but protected * Change how the base module is included to prevent namespacing conflicts ## 0.3.0 / 2008-09-07 * No longer allow additional arguments to be passed into event actions * Add support for can_#{event}? for checking whether an event can be fired based on the current state of the record * Don't use callbacks for performing transitions * Fix state machines in subclasses not knowing what states/events/transitions were defined by superclasses * Replace all before/after_exit/enter/loopback callback hooks and :before/:after options for events with before_transition/after_transition callbacks, e.g. before_transition :from => 'parked', :do => :lock_doors # was before_exit :parked, :lock_doors after_transition :on => 'ignite', :do => :turn_on_radio # was event :ignite, :after => :turn_on_radio do * Always save when an event is fired even if it results in a loopback [Jürgen Strobel] * Ensure initial state callbacks are invoked in the proper order when an event is fired on a new record * Add before_loopback and after_loopback hooks [Jürgen Strobel] ## 0.2.1 / 2008-07-05 * Add more descriptive exceptions * Assume the default state attribute is "state" if one is not provided * Add :except_from option for transitions if you want to blacklist states * Add PluginAWeek::StateMachine::Machine#states * Add PluginAWeek::StateMachine::Event#transitions * Allow creating transitions with no from state (effectively allowing the transition for **any** from state) * Reduce the number of objects created for each transition ## 0.2.0 / 2008-06-29 * Add a non-bang version of events (e.g. park) that will return a boolean value for success * Raise an exception if the bang version of events are used (e.g. park!) and no transition is successful * Change callbacks to act a little more like ActiveRecord * Avoid using string evaluation for dynamic methods ## 0.1.1 / 2008-06-22 * Remove log files from gems ## 0.1.0 / 2008-05-05 * Completely rewritten from scratch * Renamed to state_machine * Removed database dependencies * Removed models in favor of an attribute-agnostic design * Use ActiveSupport::Callbacks instead of eval_call * Remove dry_transaction_rollbacks dependencies * Added functional tests * Updated documentation ## 0.0.1 / 2007-09-26 * Add dependency on custom_callbacks * Move test fixtures out of the test application root directory * Improve documentation * Remove the StateExtension module in favor of adding singleton methods to the stateful class * Convert dos newlines to unix newlines * Fix error message when a given event can't be found in the database * Add before_#{action} and #{action} callbacks when an event is performed * All state and event callbacks can now explicitly return false in order to cancel the action * Refactor ActiveState callback creation * Refactor unit tests so that they use mock classes instead of themselves * Allow force_reload option to be set in the state association * Don't save the entire model when updating the state_id * Raise exception if a class tries to define a state more than once * Add tests for PluginAWeek::Has::States::ActiveState * Refactor active state/active event creation * Fix owner_type not being set correctly in active states/events of subclasses * Allow subclasses to override the initial state * Fix problem with migrations using default null when column cannot be null * Moved deadline support into a separate plugin (has_state_deadlines). * Added many more unit tests. * Simplified many of the interfaces for maintainability. * Added support for turning off recording state changes. * Removed the short_description and long_description columns, in favor of an optional human_name column. * Fixed not overriding the correct equality methods in the StateTransition class. * Added to_sym to State and Event. * State#name and Event#name now return the string version of the name instead of the symbol version. * Added State#human_name and Event#human_name to automatically figure out what the human name is if it isn't specified in the table. * Updated manual rollbacks to use the new Rails edge api (ActiveRecord::Rollback exception). * Moved StateExtension class into a separate file in order to help keep the has_state files clean. * Renamed InvalidState and InvalidEvent exceptions to StateNotFound and EventNotFound in order to follow the ActiveRecord convention (i.e. RecordNotFound). * Added StateNotActive and EventNotActive exceptions to help differentiate between states which don't exist and states which weren't defined in the class. * Added support for defining callbacks like so: def before_exit_parked end def after_enter_idling end * Added support for defining callbacks using class methods: before_exit_parked :fasten_seatbelt * Added event callbacks after the transition has occurred (e.g. after_park) * State callbacks no longer receive any of the arguments that were provided in the event action * Updated license to include our names. state-machine-1.2.0/.yardopts0000644000175000017500000000010212305405267015455 0ustar boutilboutil--title "state_machine" --readme README.md - CHANGELOG.md LICENSE state-machine-1.2.0/examples/0000755000175000017500000000000012305405267015434 5ustar boutilboutilstate-machine-1.2.0/examples/AutoShop_state.png0000644000175000017500000003421712305405267021113 0ustar boutilboutilPNG  IHDRHzbKGD IDATxyX{VEE$d wNfRneiG[lLOz\r$SPET6BX@s]\0>p.3>"""p61pxi8NC4!^5`̘17nJKKY̝;@ee%`Ɔ 'XSOV]] sss׹%%%H$q3/SH$L22LuL&ԩSV@35bڴiؿ?`Ĉشir9d 44i`ǎp,3/ i4Kq8 pxi8NC4!^/ i4Kq8 pxi8NC4!^/ i4Kq8 pxi8NC4!^/ i4Kq8 pxi8NC4!^/ i4lľ}?Fu[RRGu?p3F48wպٳgѫW/' ^'pvv͛7xΝ;ƍ%~LSNQ722ԩS 5 |Kiii|}RSS!P"9['D"QD"xyy ^x H.Hk1HıwϞ";;x|5D"ܾ}۷gcoi}0`_Ub 1P4j2eJH)S0LıwPPP[[[(JT*ݻwѺuk8F [FHHR)R)BBBxa /&MR RI&1$e*//GNNPZZ ,,, \\{{{0L i***dddd ==fnH$Bǎ777}L&3fp©Spq8qNƒww:033~~~(..Fii)ʐ*\ajj~! y{ٸ JCDHLLΝ;gܻw`ooTUUZ999b8qh۶-Ǝ'_~ =kHпoܹ3=zЪU(++K,YYYrJ޽;Ν;ŋ)''G,ܳҤؘhѢEtUֱT\B-";;;211z222XԤWz*3b1ц uF=x֯_O$Hhر:zQӼyH&Qi޽T]]:ڔJ%޽uF2>R(cqhѥ[=iӆUUUUOdooO۷oֱǴܽ{^z%oS~~>HZOӧO'XLÇ<֑G9rٙN>:$&&RNޞ=:?-4J}D4qD*..fI犊h$iT*YG2x-4%%%4l0277[#M69+TZZ:Ak/,>:ubΝ;#11۷YG28vݻ= YGjV0x`#!! Y022Bll,XGj8q_OivgUUU RDTTEx+++DGG GFuu5Hٕ?Dff&9w9жm[裏X1 ,w?n޽$Jĉ8111$J_eE5Ҥ%Xu/ +++k'jjj0p@_~n""+>~::v(֭[pssÉ'пqL2߅ >666غu+(zA7߿_CS_~B`E/^ݻw Æ zh ݻu xi~'L2Ov$ L3-4oӧ1j(!ɓ{.(-9v,,,ЧO!rzAK?',cccwhŠA{ĠAF +Mjj*ܹ 3h dee!##uM\t FFFҥPCrڵ+R).^:J&Xi"gD"QO>.G[m*###8;;#--M;Aw<<<k]:F&Xi]bvUzXЉ.]-347oބPqpqq <#JSTTKK&=v߾} 5d20g ##b >bO]V-u/YNL<śoܧ>,X066FNh"?FAAAA+l1hkʔ)DDD(11c =zsEEEryvuvAFFFMz,`oVҸq̌LLLO>{nO :,YBhʕM^Jr M0LLL֖f͚Uu "*((s璓IRjݺ51W^MM~<'Nڵ+{!4Ʋb 6.] 55UFӓuMxxx4-͕`Dvv6߿/Ԑc?+7b1Μ9#ԐcΜ9X oooQZ4JӦMxyyPbbbгgOnݚuMK8 <_B#G`cx&(()))jiϽ{p%~9-4~~~d* DGG `4r#Gۅc=&&&xR:u* hQ 4|k#m۶CgE/^XW_}[lzhSL< ~%##@tt4nܸ3f7 3gĥKbx^zoeEo0+͝;wH+ 33۷gGo0+ ;r ?*^8p zu´4m0bV1Ҿ}o -- vvv&'jo~)f͚դnp +..Ɯ9s- (J|7BKfΜ$>}uü4pY 0qqqaEKHH@`` УGqݳZx1n8?X0af͚ CbK0`ڶmB,n}n11tP">>2u$l~3o>sd:VI2-[:hȠmRhh()J""3gr:utW||}:/ #Jo\.ӧOu>b1};RSSC/&HDK.eǠ1)͏?H"H5UlBFFFK/w999B2m:4 $̺ӧO3qNdd$ڒ+%''Ñ";;;1b3bӄ H,ӼyHPh1%{UUUP(w%HDSLﳎ`~Q׮]MT[l!kkkСܹS/ujjjhݺudaaA|w4>ӵk״ܼ<׿EbZ]RRRחb13f5@DDDH$۷l$۷/b=z49sFgciӧiĈ$o߾v֭qyinݺE[7|SCQMM /Իwo@AAAtaTQQQ@LJ~7. XYFNtZRI~~~ԭ[7*++PЋ/H͍,YBfhHjj*-^\]] бcۛH$"Tjg [ fŊdllL/_0OtecǎiŊB:RRRhŊԳgO@NNN`'P.X,&cccJHHy^tV/L&JWChhƌԾ}{@ZW^yk:q>8t Zz5RV988Pxx86lN<Q RwTk3!C@Tѣ&fx`bb<Zn޽{#22uԤ$''ܹsuh1 RJyfMy85͛eh\߿GENMzD"Arr2(GҜ:u 5j.pj277G׮]qQ ƥ_]4лwo$%%ap4.́,wШ4uӀp]Q F uӀ 댓Kӻwof3^iTSN_~iH$ŅF`&--_ٱqqq7X0(j&77 B˫recc|1 ڥ=k?Q3U{%mO[B~~>F+++⣏>jXi!j_011Q}vĈ.f=kkk\v ΝSݖ0S{KSXXU[#""t%ӗ65K-C[ Rwzٳg/Okݺuӭ[IRR(O6И1cTݽ{1ݿ_%&&ֹ=!!- 3`C wߙ3gֻ_\op͛778u5x ё诿"" ^~vi͛G}i4… 9;;Sbb"UTTP||Nl֭kgm111$ߟJ%H$h֭߆]? ADԥKPqa@DD_?>q?Nlk׮U4ԭ[7@==xPoz۶m#Lh""ccKSRRR4DD$HݻdiiI/2 4r9) "???u#qMŪ内D"۷oؗD"ym];?Tu}'M*M׮]4~\GzDll,F'#GԹ=**5k.Æ hunoԍXϐ!CH*ӧĄFY[n=uYMPwC嫛V4wΝe?!?~D"4},X@Չy: jzSTxkߟΝ;GUUUC>@}C=߿?_TZZJO>PU7ѣ*M I8IDAT___$???DjU1fwUܮ];U'''"zxjݽ͝[SzQv@={$^=JT+T[ǿr9jNN58ƶm}ZBСYZZ͛7%9::Raa c6DӉ&gggJdggϽ{h̙@RiΜ9?[ s{_?\x Zd +W6;w.999T*֭[ӈ#ҥKu*_qƑ5d2իݻW'ۣ[ބ mݺ}nJh„ umFӼ8qRRR}CUUU033֭[1aq FB ƅ <233QUU{uQi`bbdne(]DO>Jcjj Q0C˹!򂗗SӦMÅ pYQ 3mi@"`ٲeؽ{7Q***/`ĉgGee%;aud=z4T[Z~-W[k.0tϟ"m-BOh6Z+ |EisNmT*1fQ ֎ijm߾*EyBCC/9mիxq Vwj_ux~z`pQiڷoUVoũSt1(**͛1{l:qq3C ?4ѷ~?ٰ`4K㏸s>] jjjvZ0͈N;vʕ+W_4Add$_3g=Bgg/"oLOfFEE1N=J;w cƌ t=\#==HKKCzz:PZZR`ffVZ fffС ?Ν;޽{#** /6"%ۇ0DDD 44T!qqqűcpeLLLtAUڒPBzz:Q^^H///`РAӸD rrrp]<}YȈ^~eڳgjfǍ1zUo$y4DD$HTٲej@gggSZZJ}9::E}ڄT^^^:tľ <,'dhdccC .TYLV(|rj׮r;w.eeei)deeٳI.--_ R@@x0)MYY5bqє)S]v=YZZ… :~`ݻ%ؐH$$ֱ'4ߧ@._2ބOJ$iڴii[|?~'\\\XK͢4Æ CV}vQ„ PRR_ILL?ѹsgQ鉄Ӈuü4Ç n2ޙBmѻwoƲ/MTߜzgϲ/MZZ݅๻#--u hiQPP 3E"{uOv_vqOdddBС̐:J'hiЪU+!acc<1Z'Jݗl^EEE?wO? -#֯_-z-|8_kv5- =i{ɓ't%Cb $&&bϞ=0B`r طoQBB(Ȉ~7Q F, ќ9sƆ.]:Ju%s粎bP1ͣ1j(ԩS.J\ݾ}CϞ=믿B"d0mi@CP 66m۶eYχڵkHr֑ J. pNB" 22vvv#1!CpIpٳXYYè4kHOOG<|!f_lj'о}{ܹs# .99prr‰'`oo:jkkk9r>>>;XG̎;___DEEښu$](JzwI$#Lii)$> R:Gu ֭]zuz*=dccC8#ZBCC333 | `8unرcԔhҤI%BUUEFFҤIԔLMMiܸqz+5ٿL[w^l۶ 'O9 xyyia555x"bccq1šī1c`SGݾ}All,n߾ xxxpwwG`nnsssX[[]"YYYHOOGjj*ҐB: 00 Bpp0ڷos`y\zz:TqzѥKU|}} 4P*EII JKKQXX:`nnVZfffc69'<^Ӑޝr8]8 pyqQEIENDB`state-machine-1.2.0/examples/car.rb0000644000175000017500000000061012305405267016523 0ustar boutilboutilrequire 'state_machine' class Car < Vehicle state_machine do event :reverse do transition [:parked, :idling, :first_gear] => :backing_up end event :park do transition :backing_up => :parked end event :idle do transition :backing_up => :idling end event :shift_up do transition :backing_up => :first_gear end end end state-machine-1.2.0/examples/auto_shop.rb0000644000175000017500000000036012305405267017761 0ustar boutilboutilrequire 'state_machine' class AutoShop state_machine :initial => :available do event :tow_vehicle do transition :available => :busy end event :fix_vehicle do transition :busy => :available end end end state-machine-1.2.0/examples/Gemfile0000644000175000017500000000012512305405267016725 0ustar boutilboutilsource "http://www.rubygems.org" gem 'yard' gem 'state_machine' gem 'ruby-graphviz' state-machine-1.2.0/examples/TrafficLight_state.png0000644000175000017500000005026712305405267021722 0ustar boutilboutilPNG  IHDR7bKGD IDATxwXTG]J/(E #"FJbD-*GO~kbySMh"XbTQFJ,ԥ~Ζ\^̙97s|G@DGGnpN iDpL&C\\233ѭ[7tЁ$pׯ.]TVV⫯¢EXKc| -[26ܻw۷g- #?U.^Hf #а6L777F4D+000q1Ҙkp3f f۷,\صk&N~[_kpN i9: 78G4pstnpN i9: 78G4pstnpN i9: 78G4pstnpN i9: 78G4pstnpN i9: _Xy ڶm[>@P{>}Y6mݽQi'Lb5 7l2ԛƍS"͂\;vl 88vvvjT9pk9666>/_L&ѣG_Winp ۷c(**€п{ TXXDGGرcy&1aL:۷Wzj8L)..<<<Ջ6l@YYYLܸqéM6$htY&Z78# / '''266yѝ;wXRPQQAG`@t1ֲ 7M6-jՊ,YBeKll, >kte֒ 7'___244KYKjW^` 4{lf-A@AA͙3B!ѭ[XKj62vANNNdggG;v`-^ULbb"y{{=m߾d2kIJ!'',X@BNJ% 7 Ą)--pI'oooJLLd-*NJB>#`-IQ`` oZNT*A5>>:u*֭[ZVPTT___`׮]j+bDGGՕS[nW^o;﨧P&--&""lll(55&kkkzZ5xͅ7>S̜9h׮~ ϟ /^T`}!)) >>>8}4UZ7x#vz8у?ѣGqU>p7A7of-E'(**֮]3fnFp )) :t`-GgXnn݊;w,"7x#5j,,,cRt|bƍxUR7x$&&G~::wZ'qJr739r$++ U T}:ÇU?7x=044ĨQXKiLC!??_ysCDDƏcccRtC {UzucDEEg\b1°sN ^'OYK  .@rAtt4BNj"((D+5_n:BY,,,k!**JrBRRsܾ}Eii).\WWW$&&fѣ8~8JKKqE/U}baa(JS@Lo۶-Aї_~YgXfTsԩӧOW~I@5?p@N"ԩS'%Qرcb ^ ߽{7( hjjJW*j31*ۋ84YSPy򦉼nz-<~[nE@@rJ8;;~XczL&AwiYWjxenj/.+5O .ʕ+Xlrrr^qnnn^ywWW\twpN:5W~W1zС{{޽;3_Ȼ_~7zh{gK.x#FQެYpe#&&FVUqJ@RSS 2)~pԨQt...DbxOOZرc?i}}}XݧNDD . ^2Dw^f~gzWؘK.|rQٽ{7H$֭[+geem۶$ٙ.\Hϟ?RYYY4|p244mҥKP SZ~>s<<̞=%$$غu SN=aǎdcc,8O? =}T- TgА$͛Æ 0c 233ѣGL<X W_}Uٳg1p@>|8k9ϔ)Sp$$$74ZB:ʪUҒnܸZFvZ211ׯlnPQQAo&iӆ޽ZF/رI-BBBCm=¡C6mLo+<8~8YKbΙ3g0j(Xv-3uğB<`-) ɓ'c͚5l0AϏ B D֒L&_ FW^$(<#|}}7|:] Θp8pD"Qiݻ(DGG֭[HNNVt%`ffsss!77RTDqq1{bРAMC&"JѮ];XK.mq2 H$HIIAAAACZavsss8::^^^v X|9*aHĄ={rD I"|uǃ3i&L4 vvvj-kj-_p3ԩS}6͛% 8y$=vꀷo ??b^^^H$Bm۶D"^3 %%Ga4xa2 /Q%g@cUǏ\>###H$o^T ՌT*Ŷm0|xY2 /V f~WbLOII3|yy9~w&\R/̙ 5RjeנGEXHNN_5Ro16mB~н{wf_L.QYYJK.xWL2W}1Ց 3nGE||<|||<DQ6m3FLCyy9mۆ7n@*"!!˗/mV \-ȻWXD5֤ɉ*kp5k>/}RSS+Q *FNNN(*F5k.RSg kp ] ѩS'npNa=j,4k1xyy(((`-Ep֣BvJkpM-(BI*qF5 ==ٿZʙ3gp޽FabbSSS糖tx ;Wϟtx dRSSqQܹ&aggL2{{{34vvvR" DJJJe̞=4 GGG<{ D݋|̚5&ckk Ω7b̘1Z9ZW2ytY%G(&&4e#CCCڵ+m߾^?iaaA:tCrr2 wwF%Ul2rss#XLdhhHͽ 72e 4.##{m6""Zr%ݻwW9v.\輈j|}}k=.$$*++[~A4np%K&&&76<</I$*((3g ",277ݻW9VӧQMgڵkT^^NvRۼy2.F 6mD`Z///@.]7ҥK ;v^4]D"YXXPYYYnnݺwbݻw邂<m\ Ջ&MԨbPIII222Ą5q㚜Wu<lllu.Ei!r f̘Ѩ pٳg#&&/_Ƒ#G#Fhr^y9Om5+__xbrss#L֨\`ǏM8lllH(VYϧyZ .osܾ}B:6UWTT@&A&ʕ+Xl:w72RZt{QQ/CD044{=5R"HZs@ 1N:5R/ ^ƙn 5*R?򷙺7x5,--%]2mt"//O VVV033Zj{{z{H䘚* nhhAaܹ> ^gjp1gpyܼyIIIH$H$J ^^^􄧧'^{5UNeff0005~W (//GNNYik|={QQQ͛7ahhxyya7o:v333XYY\Qk䠰Jx)޽d$''xLMM سg͞>!?第,7FE-++GĄD"пo:spȠ]v̙3%X[[9sR&Ʋb4ߧprpp PH m۶Q~~Zuܽ{~m266&ԳgO﨨H:XSRRB}6M:D"Ѻu#| IDATÇ,%ѕ+W޽{tE9s&#[Np,iժmٲƎKB:uD۷orR$33/_NdccC|^f2ZZ ^TTD}bڵ+8p@kΦUV%ÇYKR)~~~xb2Z InnndaaA_ 0a#GVY-X9r$M27EEExw1rH!)) -RK@wU䄝;w̙3HIIAntrn(T_ϝ;wK.dkkK, xb4k,*,,d-IiʂT*#"" KKK\zԺX,W_}ÇD"a-K)X[[#''^ӦMqYo^h #FիWann\|+Wj,X@"mۦ̬;v,ѱcXi۷o'###2Z ^ZZJaaadjjRQQAs%CCZ>LB)]D3f?Uc``7ӦM)BCCYj2W=C%'KKKSFv:իI,ә3gXKi2o&tMRZD vZ`Μ9diiI׮]c-I:wk)-E߽{7D"ڳg4artttrMqq1?nBDYf/@XXҚLmۆ;b„ d-Qfaaa0`.\lM:X,ƞ={UVht/Y_huV\ڶmYgΜa-QϜ9m۶~T!CtR̘14~Ê+kJNj*XYY>c-A_~=d2-[*=:H$ƍW_!))z+߿WƆ `llJM:O>}0qD(dmml2ZD  Æ UGoWg-Nuk)UnJjM^GDD`[ 6 &&&ؿ?k)Ui?~'&ON=zX,[o۷RyE~YB?tlmmѧOu[ƌZ:DṄê ٳT\(2  Q:XKx9խG߿F\j(i֣!99鬥(077׽[n§|Obb"c%JJJXh6<99;vTB 4 V ͹vuQRixzzjL lj<;;Ϟ=oFx{{} Lj<%%v1 c-C',,o;c033 &O\g@ܹs瞛E}pqqAxxx6=zSbݺuCDDDtR˗/;ꊕ+WZ;5%mFFf̘;;;`ĉ-VӴY4999u^\XZZãF.D@:(5~U) c3@~~~t5*//[nQ>}m޼Yi7Ã._Lt9j߾} .<5aIYYYdnnNݻwrlPPӧOQWkה{Rw`ӦMdgg׬!v*O:ESN5UvԩEsI@ފm^^^.]TnݺkZ jVΝ;=lGGGziU666K.%[rrrH$5U5ȫSА*ۋiTr<^uJJJebbRϛcccӬZ m@>lQ8##LLL(00vEhܸq4_կ]SIujߦkаQj UeMc#fϞ\|G*G6MSIuj"ҥKU?ЩSuss\pvwWWWŶvnܸQocNUXBybڵkk~rOS$&&мյfeeАڶmKK.]ILVV͟?ڶmK"i…i߿OaaadeeEbzI.;;-ZD...$ƆFM7nhQڼ<3gۓ?m>tTڵkM6D/kPcWm׮)J^7 |̚yS9[oEe(Xvmz"## yyyPUidd#99F;G$%%ˋ&SPP DK,atܩS'np$ -ZZ]urM"EM5dS_HKKCQQF Uiggg`ذalU/6oތ2FgFZ[HHHX,FYKi2=b-VjQXX+W[^s(ЖkVi:uҸ&Ƣ@=52f۷oX4JN0/_FğVZwެ@(lւAʇ ^Lؽ{7BCCP.4eWs?~ƅE233q)L2hsT+SLATTԥG/ٵkЯ_?Rj]عs%۷oǤI{Eϟ~Afsܺu fb-Nt .g֬YJرc:k֬IT$AfffǺui 8u>CR꤬EsLYӨ?͹s"33{Ub H5t4ku:M"66ǎG}ZJ5zD&ҥKAD/TG/yxbS]XX/K ndd7b͚5*5<})**^8p Fyi VZ)--Eii"¬6~~~8tkkkXBB1zmpͭ[bԩ(**§~jgϞaĈx .]{{{֒9(-ĉaddI&ؼyD7R'%q9$$$7oTv͛9s%]hd`oo???l۶MhR&M{g>Zo2yyy055 *V_^^N+W$PH&L,Uvۛɓ(ZFPd;H> ǏGtt4es̞=h׮o 4,o@EM 4III4i̙p$G&a˖-Fdd$"""nlIS7&/Tk$$$7nVvBn0gL8w XKS)Z?Nw۷z#F 66VR ?31uTo1deeё$@ @hh(^G"77ѣk >> ,@v0w\III9ӧ)WΥKhܹdccC4tPںu+=|PeWTTЕ+WhժUEhtBɓ'*/_SիW"4rJJJ?Pjժb3gΝ;brsx]p5jYYYrssYfP(+錴kkk駟Xh"]QQ QQQBll,  Ѿ}{xzz9aff\RO>Err2$ NNN AHH߿?lllo>ϔrb8p@kֆF:w HͅT*T*Eaa!7ڴiSk9[l S222sΡo߾42Άlقɓ'ÄDt wk Blll0p@o0#++ &LSN)nsnp]eȑ044E={[[[ pIV0|pٳ&Kposɓ' mۖ ^C^gff(Inz066Fhh(vZy$,, wk)j%##a7x 0z'Λ(zH$¸q{nRQZZ '''RZ 7x# Cbb"XKQ kp}!00[֛np=A("44To MOOѣO77oԋfJffNnFӷo_i}ڼy3^ݕz}X BJJJҒZ{yytR,]бcLj(''D"YXXPYYYnnݺw^t)))h̙Ro74iӧ}bPIIIyddd Ѯ]7yU7IƆHNY[J7Dlll{6#fϞ\|G1yUG&ջ?//p}6 M\ 2_{߸q|.] ###lذ'NP(İaÚMЋ_* d2rsjbllÇ׺ܠ-Bjj*0|tرJ6mw]_vvveƌŲ,W^EEE222tR ==eee:gpW'Ȩ===Zn]k7""F><9#GKQ֬Ym۶$W"ƍٳg&[PP Z de˫+\ 6 "jprrB||<|||0m4 &JV0dȐfqvv HD"(]^E= 44rssaee=zBUSYY}ΨQ  LaEJJ ЩS'R7j*!+tU@hh(N:|Rō7 R7 =z4d2"##YKiڵ+k*\X[[#$$Dk)&3vX8qRz)((9Mc(++ѣGYK7nн{wRT7C~4rEnZ'_*eر8vXK8e np2fĉɥKлwo2T7 qttD``"[VV"""9s0VbUFFNEQ1Ʊcлwox1 X£A,^c-Eep+2 22@ZZ QQQX+ G'`W"D044Dyy9(#`ffBbbccZJmp%"?iXXXCRڵk:76o\o"U$!!7߄ae2LMMլ*qqqprrI/ "6o WQQ 7޽{QWTMK.L9*VPX2jՊܿcA]p˗߿F{e%11ݺucA]pٳUjrpuue+.@۶m믿ViuuCuL5oꔕ޽{HJJBrr2$ RRR J TBfff0339ѱcGxyy uw1~xoŸxDGG#::/^Dqq1B!\\\ W1Y gddݻHNNVFtrrBpp0BBB wwwUVسgƎZ ^ccc\~ ӧQPP`ҥ `ddr?d$$$ ** K,Ann.\]]Jijʕ+4|!PHC [RjjˮOcǎtBׯtnps)z].TIDATvǏjSӜ9sL5 np ?^z>^cȹoooL2֭c-G(**/erp`…ťK )M͛ٳ'5"87KŋZֲl2:u + n=z`ƍh5R;wFxx8.\V [&a-E'8pYXX0#kpݱcGXx1k9:C^txl`ǎJ5kk):ſoH[,{d2[/fꂮ1rHoaA ~dffbܹ~!;1Ѡ߸q#&N+++Rtc;wdR^ɓYKYD"&L&w///X%'OF\\޽۷o絷A׮]['$$ %%ok)z[o~MOݝ`{.M|ϣٳlBDDtARTM7ow}W:JJJ(%GŋSTTi4:z(}q Uڿؾψrfzz:6nOp` !!‰'m.u BwwKE[pCh],ʂt:x<AQDիWHNN욢,xVVBwWSSF* +W|Fl6'^ptvvSY4./&K>6 JR}- QD۷oT*-xtt4rss f^QĹeGYYYHKKje'"~̙3(..f?|tb͚5}6 . ϟ?|ɤΝ;G*XG(׮]#LF/_deO" aӦMhjjtJr:˗QTT:8^|ŋYǙ> ^?[j\~999HNNt|76-vΝ/!;;(**'vagN/OX,|>bѢE)))GS39︑ȑ#CgP(D%%%4w\l'^)b3gUWW0޽r9ݻwu(S$HpUjfu,A~ر?``XE"JR( #E:,YB_fgxQCCT*Zj\.qUmm-) B ]]]I jkkY @ @$ :uFFFXGx?~$ mݺ޾}:RXܹsHREISS-_d2ӗ/_XG%m6@zYG-a4<:::4o}H"h$:J!y(>>`Yu_.P(FTWW`k׮Evv6L&֯_\y^l6vvtww#11{AAAg+^pFaTD,[ ZHJJ‚ T*P(B~ z{{ֆvvP(00L0L >C| p:phooG[[:B 55ZZ;^Çw@ @ 0~GVx3[ss/87sڟ W(s;IENDB`state-machine-1.2.0/examples/doc/0000755000175000017500000000000012305405267016201 5ustar boutilboutilstate-machine-1.2.0/examples/doc/file_list.html0000644000175000017500000000255412305405267021047 0ustar boutilboutil

File List

state-machine-1.2.0/examples/doc/AutoShop_state.png0000644000175000017500000002536512305405267021664 0ustar boutilboutilPNG  IHDRk#EbKGD IDATxw\SD0d*"ʼnQqUںWQ}ikZVZEE CA frWB$7 3>79<9<AQHܮ]Q5HX PnX,Vmmmkk+emmm BթTZ&Nhiiy}{{;p8*Jt:L622RX]'55իWEEEŅ>| A/_LOO/w|1EСCmlllmmG=|p %%%%99999933]__~yVVV&&& `t84cX,f755UTT֒#G7nܸqjjjX"%&&>z(99F~ngggcccjj v‚h9|/]E.]tƍ;;;OOO//////SS/4%%%%%%55@[[{޼yK,;v, ғ#)QQQ.\vqq!d>ёɓǏ6665jҥZZZR5(3779rR UTT>|`aaw>Ȣ!.IJJ RSSc0K.MLLn_d NWSS JNNn]۵rJ*:hРB[PPw^CCC*j*?o;v,G֍˗/<<<ܹenPEfΜI lmmϝ;" ---gϞ&s)))'!!aL6---MRSSN dJk,kǎ xx7otrrRSS۽{7VPYY9|Yldgg,XPUU%]x/_6228pSd/?~\[[8**J#vt:ݻXqqq ȑ#mmmRڵ>įdb-kjkkW^M jjj1BMMm߾}XݻWMM͛7Ҫ?ڵkkϟcg222,,, %͛...XkGQQ֭[Rak۷oG$$$ba-GR Ν;;\QUZZZ6n܈ Ț5kp֙իW#y>֏ZSSNx"Zh%8b Fdd$ZzGDDN8qbW~ɓ'x{{{|!SLЈ711Z"RUU5iҤƄeyyy&MNHH}zUUUjjСC(0L///"`dd>QQQhJJNo]+++n; ZhnnqӆhhhxO<CyyyǏvü*۵j///mmDYEhjj?~|SSSJJrpeɥiii>R">>Wq+U˝6m@}5Fll, liiZX˗d111/^XjUJJamV!Yt#rJ`ƯJ$>|Y@ N>-yմk'O$Pܾ}A3g`->}J&8SԬ, ]̤R{ZصkJUma8R84YXX466J_ Fahhx}"9ӧ,29nݺׯgggc.tvv^hQxxxϹelg͞={a@ro>ȉ@ \v k!#22@ <}ǜ*5^sqq9|ւG }򥝝ZpdKGGɝ;w"WTǮ|xSZZb9r$NONNORmCCC_zeiiR\\>dSI?ZZZڟ7WWW:tl`޽;mڴÇcE!vuu{n@@Zp &M߰ւ1W~QqqqKd`\@8͛X Q`﮷rʂ~wyCRRҸq:w._k!jkk#M"""qwqq]k/_gZb1g**~AfU͎ Zr;v-""bܹjjjX Q,h4ٳ/_,dQ-xn߾E!((tۮ,\k!HppϫXgnn. AlmmazmmUi4%K` jسg ;w۷A'tR(SO:BvCCæMlmmi4ӊ+jjj3䢢ɓ'tCC຺: #зo.X@OO`9 Q)7n8}}}կt 4ۻ_YȒ3g4770?z"= !!bkiiEQQF 2{zz;Vc3g$L&Sd2dr`` j`2'O444400hVSS̬߼y3 $$^OO>///??ĉ9{lT|}}Y5+54hPtkϟ݅cR$<<\[[C5+5횚H?_ϯ{[SSSPP VJKK ;FKNN$''6dذa?)4˫VH$ŋoXmEEEjj… aB /0p%FC9AN1HبTHMM?R%D"yxx+]!Æ ZL"Fv'6^]\T*uܸqЮ=~0///hנ_G-[ EQrUE-[߂muХx$|&F{իWp\#svð‹h" j CPR[wXXX) b2Юyzz)##C^nnnQQQÇ a[RY5110sJQttt}FJaMII NTn&3[[[8]"p6$IA $:::=*j_z-M7v]vŔ)Sp8 5sN)6*o߶ώKlmm[[[>K/^NNN2#9=}vgggiܾ}r644&%% daacii)HӳUӟ>}RpGA:ñ0`kꢣ?suUUՠALMM u;QrIr"$IfQ@ 444ܹ#XwNRg͚,>36QXvi---iILL !&rJ+++uuu;;5k Yv-`wڵm:C5aX,44޽{w%k(VUUt77ߨxx<@:s%‰'.)RD5:::'ONQJOOACBBݻvGG GO?F3dȐΝ;|>Ǐd2yʔ)"L"tYf!@?WJJHU7nQWWwtt\|yuuw ]=p#B H$ɓ### o~Y+pSN-[ƍ(!&MinT*V7baaO?}s`k܀vMLFF-^866]>߿/I&d2/^,(+ދ]LA{c)"8rȘƌ 3332ŋUݣ=>> ) NyM$D"4p/^6pRЫ9߸qcyyyyyĉ͟?@ TVV ֒`λvLHcǎ!?b'd;D \zuJJ χ.B$zUU| 1Ι3ɋ]LA{&IG Z/^ܹ{bp&lP(s̉mkk[v+vNt:}c_,@Qk)q%X__/x}.?}YPVVO?@$y<3; =!{'d2R]PPPUUكD <Ġp]***~*ccc@+\՘1c={VQQall 3pzrzjذa ? gΜ5kd]WWW___GGɓ'Ы(\eee}7}W6`iiIP>FcƌԩS3ZZZDYܽEî|'F 綰8|w֭(Z^^ ƍ%)ݵ$J"@;W՝$qE.JY®9%''+Am&OUqq1@v[g͚%2ۓH$D"-~ry@`ԨQǎKII',, p@GE:Ζ`wג!ٹ*ЕGdee7o(C!СC̙1|UW2=~xǎpD$k߿'Hk֬AQ4,,LF+UV}S$GFF&tDztIv 'AǏKR-<䄢#.a%)%lhG{{U %.IJJz(]5㭫a͛#;I"(&pH68 :t %00PGGLF+ѣGoٲE8Et{iiiLLLLL۷o]]]5= ߲eKnn۷o'쮡`XTl"k׮ A΋ǂ_;UݣIX b h4SRR}t:f˨i1]/#|SՅ0?AV\YWWw`0>K555ุ8KH$ä`DݻwFDD̘1caw^ bJ9) BbMMMT`(*\U=QP(fƑ'9ҥq:dff&%Гc,ŋǏh W}||233l6|".addD lll+^weײO%<}}}%j1TVVn۶HOOoʕMMMGh CK/vkbJ_3}}y晚jiidddG;2(xcǞ|8N|w@+(xqPooo;;;[.+W| _())))޽2dZz/\P^^.Iw:)bmmD5 ++T:.ӶEI2mR ٳ^&W<}A#Fȡ9s?U|Œ1B׺B-ȇQ?xfhht}(.1b|Nmii:QH??ϟL/Dҕخ&LCU(.rsiA`(xrZ###'''g 3n8E]y ]J';9 HwG=s`(4'ҝ/tz@@t&Mb0lgi&Y\[ͭsr۵F2,8T$R;իWDQơU{.¦Md݊raccm۶mP5k/*OOϠ 5'Ʈ(n:t$.ܞ)놔15xT x󋏏[Zkk+.fogD18]'-)N=H̭="^_ݰaR GG.Ruɓ /]"""Dp'444TVVw}I,^ʕ+0`L?ի .]%*`֭n*))w6o޼0aªUZZZZZZ\nSSSkk+b٭l6FGGQ/Q2eʳgnݺ5cƌcKښ_| GYXX-KI_422_OʔR ӧ˿u"&&f֬Yp+(RbEAAHZco@ d BRT*BDE$ȑ#օ 5naa4a D>{ٵ/^P(8̝;BHU3{ׯ4hڵk?HJJEsBbb"{ص"$\xD"/ ͺDCC˓{KvfXZZ RWWgdddcc# *sƻ$'''o߾nPX***\]]N Ϧ푿kҥMMM%%%=gXϋ/^|fI$ZFp8yyy/b``8l0WWWWWWkkkGDD,[,..nҭY1c˗c ڵf2ɓfdDss1cH$Rjjf",,?Q}]~~>-"fff988بA KSSSii斕:::;99~W/V3ϟ/s*h%%% ,amT%Kܽ{7++ %ݻw[XXXZZZ1p@ J,)))......))0b-0GGG&---p'55B`A_n"&/j5,Xp˗cE9s믿?gΜQ***DBIIIkk+@[[[ҙc2nkkxzL6551555jڵk:e˖gfffJ~b5ݻ:`={(3i+**\qq7oh4--ή*rbdTVV 0hVlС7l(QQQ-:q\R%÷l5|K]|ǎQwܙ5k͛g Ekjj>|PUUÇ߿ڠ?VBPh4v8p CR400000/ TMXwsVXqUV>}zս*v/]466611k9RٳggϾp˚6&/:,ad2NԤh4MCCCCC@CCCnΝ;9;i$H{n۶.,;g=00NaE:hYfKp+VR\vB\]CQcݺua!aQ1|>!Z˗s!`]*na>8pk-8 O? A/#ŋd)Sjjj;>|0qD ǘÑ .H:H ɜ6mLtR_v EѬ,kkk---%蘗疖 {)#,--^[k(ϝ;AM6/f'AAAXQ2LI$ҏ?L?5ș3g rutϏ266>wrp>*:|4划䤦v1)> v Eњ@ ?XaյXQz&N ruuҥK (..nڮA222F Ŝp#ҦN T4qㆱMQYYaÆܼySMv EQ_r9|||#Fzҭ(\.7<]~ŋ'''hJ$%㕕%%%=|0770ze˖͛7O|۵999IIIO4Q fs8hT*F ?[[[̏σpppT ܮ]Q5pj?mJ=IENDB`state-machine-1.2.0/examples/doc/top-level-namespace.html0000644000175000017500000000462012305405267022732 0ustar boutilboutil Top Level Namespace — Documentation by YARD 0.7.5

Top Level Namespace

Defined Under Namespace

Classes: AutoShop, Car, TrafficLight, Vehicle

state-machine-1.2.0/examples/doc/TrafficLight.html0000644000175000017500000014005012305405267021435 0ustar boutilboutil Class: TrafficLight — Documentation by YARD 0.7.5

Class: TrafficLight

Inherits:
Object
  • Object
show all
Defined in:
traffic_light.rb

State Machines

This class contains 1 state machine(s).

state

State machine diagram for state

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Instance Attribute Details

- (Object) state

Gets the current attribute value for the machine

Returns:

  • The attribute value



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

Class Method Details

+ (String) human_state_event_name(event)

Gets the humanized name for the given event.

Parameters:

  • event (Symbol)

    The event to look up

Returns:

  • (String)

    The human event name



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

+ (String) human_state_name(state)

Gets the humanized name for the given state.

Parameters:

  • state (Symbol)

    The state to look up

Returns:

  • (String)

    The human state name



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

Instance Method Details

- (Boolean) can_cycle?(requirements = {})

Checks whether :cycle can be fired.

Parameters:

  • requirements (Hash) (defaults to: {})

    The transition requirements to test against

Options Hash (requirements):

  • :from (Symbol) — default: the current state

    One or more initial states

  • :to (Symbol)

    One or more target states

  • :guard (Boolean)

    Whether to guard transitions with conditionals

Returns:

  • (Boolean)

    true if :cycle can be fired, otherwise false



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

- (Boolean) caution?

Checks whether :caution is the current state.

Returns:

  • (Boolean)

    true if this is the current state, otherwise false



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

- (Boolean) cycle(*args)

Fires the :cycle event.

Parameters:

  • args (Array)

    Optional arguments to include in transition callbacks

Returns:

  • (Boolean)

    true if the transition succeeds, otherwise false



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

- (Boolean) cycle!(*args)

Fires the :cycle event, raising an exception if it fails.

Parameters:

  • args (Array)

    Optional arguments to include in transition callbacks

Returns:

  • (Boolean)

    true if the transition succeeds

Raises:

  • (StateMachine::InvalidTransition)

    If the transition fails



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

- (StateMachine::Transition) cycle_transition(requirements = {})

Gets the next transition that would be performed if :cycle were to be fired.

Parameters:

  • requirements (Hash) (defaults to: {})

    The transition requirements to test against

Options Hash (requirements):

  • :from (Symbol) — default: the current state

    One or more initial states

  • :to (Symbol)

    One or more target states

  • :guard (Boolean)

    Whether to guard transitions with conditionals

Returns:

  • (StateMachine::Transition)

    The transition that would be performed or nil



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

- (Boolean) fire_state_event(event, *args)

Fires an arbitrary state event with the given argument list

Parameters:

  • event (Symbol)

    The name of the event to fire

  • args

    Optional arguments to include in the transition

Returns:

  • (Boolean)

    true if the event succeeds, otherwise false



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

- (String) human_state_name

Gets the human-readable name of the state for the current value.

Returns:

  • (String)

    The human-readable state name



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

- (Boolean) proceed?

Checks whether :proceed is the current state.

Returns:

  • (Boolean)

    true if this is the current state, otherwise false



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

- (Boolean) state?(state_name)

Checks the given state name against the current state.

Parameters:

  • state_name (Symbol)

    The name of the state to check

Returns:

  • (Boolean)

    True if they are the same state, otherwise false

Raises:

  • (IndexError)

    If the state name is invalid



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

- (Array<Symbol>) state_events(requirements = {})

Gets the list of events that can be fired on the current state (uses the unqualified event names)

Parameters:

  • requirements (Hash) (defaults to: {})

    The transition requirements to test against

Options Hash (requirements):

  • :from (Symbol) — default: the current state

    One or more initial states

  • :to (Symbol)

    One or more target states

  • :on (Symbol)

    One or more events that fire the transition

  • :guard (Boolean)

    Whether to guard transitions with conditionals

Returns:

  • (Array<Symbol>)

    The list of event names



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

- (Symbol) state_name

Gets the internal name of the state for the current value.

Returns:

  • (Symbol)

    The internal name of the state



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

- (StateMachine::PathCollection) state_paths(requirements = {})

Gets the list of sequences of transitions that can be run for the current state

Parameters:

  • requirements (Hash) (defaults to: {})

    The transition requirements to test against

Options Hash (requirements):

  • :from (Symbol) — default: the current state

    The initial state

  • :to (Symbol)

    The target state

  • :deep (Boolean)

    Whether to enable deep searches for the target state

  • :guard (Boolean)

    Whether to guard transitions with conditionals

Returns:

  • (StateMachine::PathCollection)

    The collection of paths



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

- (Array<StateMachine::Transition>) state_transitions(requirements = {})

Gets the list of transitions that can be made for the current state

Parameters:

  • requirements (Hash) (defaults to: {})

    The transition requirements to test against

Options Hash (requirements):

  • :from (Symbol) — default: the current state

    One or more initial states

  • :to (Symbol)

    One or more target states

  • :on (Symbol)

    One or more events that fire the transition

  • :guard (Boolean)

    Whether to guard transitions with conditionals

Returns:

  • (Array<StateMachine::Transition>)

    The available transitions



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end

- (Boolean) stop?

Checks whether :stop is the current state.

Returns:

  • (Boolean)

    true if this is the current state, otherwise false



4
5
6
7
8
# File 'traffic_light.rb', line 4

state_machine :initial => :stop do
  event :cycle do
    transition :stop => :proceed, :proceed => :caution, :caution => :stop
  end
end
state-machine-1.2.0/examples/doc/Car.html0000644000175000017500000006635612305405267017614 0ustar boutilboutil Class: Car — Documentation by YARD 0.7.5

Class: Car

Inherits:
Vehicle show all
Defined in:
car.rb

State Machines

This class contains 1 state machine(s).

state

State machine diagram for state

Instance Attribute Summary

Attributes inherited from Vehicle

#state

Instance Method Summary (collapse)

Methods inherited from Vehicle

#can_crash?, #can_idle?, #can_ignite?, #can_park?, #can_repair?, #can_shift_down?, #can_shift_up?, #crash, #crash!, #crash_transition, #fire_state_event, #first_gear?, human_state_event_name, human_state_name, #human_state_name, #idle, #idle!, #idle_transition, #idling?, #ignite, #ignite!, #ignite_transition, #park, #park!, #park_transition, #parked?, #repair, #repair!, #repair_transition, #second_gear?, #shift_down, #shift_down!, #shift_down_transition, #shift_up, #shift_up!, #shift_up_transition, #stalled?, #state?, #state_events, #state_name, #state_paths, #state_transitions, #third_gear?

Instance Method Details

- (Boolean) backing_up?

Checks whether :backing_up is the current state.

Returns:

  • (Boolean)

    true if this is the current state, otherwise false



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'car.rb', line 4

state_machine do
  event :reverse do
    transition [:parked, :idling, :first_gear] => :backing_up
  end
  
  event :park do
    transition :backing_up => :parked
  end
  
  event :idle do
    transition :backing_up => :idling
  end
  
  event :shift_up do
    transition :backing_up => :first_gear
  end
end

- (Boolean) can_reverse?(requirements = {})

Checks whether :reverse can be fired.

Parameters:

  • requirements (Hash) (defaults to: {})

    The transition requirements to test against

Options Hash (requirements):

  • :from (Symbol) — default: the current state

    One or more initial states

  • :to (Symbol)

    One or more target states

  • :guard (Boolean)

    Whether to guard transitions with conditionals

Returns:

  • (Boolean)

    true if :reverse can be fired, otherwise false



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'car.rb', line 4

state_machine do
  event :reverse do
    transition [:parked, :idling, :first_gear] => :backing_up
  end
  
  event :park do
    transition :backing_up => :parked
  end
  
  event :idle do
    transition :backing_up => :idling
  end
  
  event :shift_up do
    transition :backing_up => :first_gear
  end
end

- (Boolean) reverse(*args)

Fires the :reverse event.

Parameters:

  • args (Array)

    Optional arguments to include in transition callbacks

Returns:

  • (Boolean)

    true if the transition succeeds, otherwise false



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'car.rb', line 4

state_machine do
  event :reverse do
    transition [:parked, :idling, :first_gear] => :backing_up
  end
  
  event :park do
    transition :backing_up => :parked
  end
  
  event :idle do
    transition :backing_up => :idling
  end
  
  event :shift_up do
    transition :backing_up => :first_gear
  end
end

- (Boolean) reverse!(*args)

Fires the :reverse event, raising an exception if it fails.

Parameters:

  • args (Array)

    Optional arguments to include in transition callbacks

Returns:

  • (Boolean)

    true if the transition succeeds

Raises:

  • (StateMachine::InvalidTransition)

    If the transition fails



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'car.rb', line 4

state_machine do
  event :reverse do
    transition [:parked, :idling, :first_gear] => :backing_up
  end
  
  event :park do
    transition :backing_up => :parked
  end
  
  event :idle do
    transition :backing_up => :idling
  end
  
  event :shift_up do
    transition :backing_up => :first_gear
  end
end

- (StateMachine::Transition) reverse_transition(requirements = {})

Gets the next transition that would be performed if :reverse were to be fired.

Parameters:

  • requirements (Hash) (defaults to: {})

    The transition requirements to test against

Options Hash (requirements):

  • :from (Symbol) — default: the current state

    One or more initial states

  • :to (Symbol)

    One or more target states

  • :guard (Boolean)

    Whether to guard transitions with conditionals

Returns:

  • (StateMachine::Transition)

    The transition that would be performed or nil



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'car.rb', line 4

state_machine do
  event :reverse do
    transition [:parked, :idling, :first_gear] => :backing_up
  end
  
  event :park do
    transition :backing_up => :parked
  end
  
  event :idle do
    transition :backing_up => :idling
  end
  
  event :shift_up do
    transition :backing_up => :first_gear
  end
end
state-machine-1.2.0/examples/doc/TrafficLight_state.png0000644000175000017500000004115112305405267022457 0ustar boutilboutilPNG  IHDR ?0bKGD IDATxg\WM QGGuբZ-j_gG-kU\(vRQd !y<) LrgIs߹CAA$ u rB A `A bSYYrx<L&3 FPX,:a" Rؘի첲222tD CD>}bmllmllLLL444 p8P___TTUYYI"\\\<=====) /AĂ H@ x3gjkkG5j( >_6???%%%)))%%%;;[KKk̙#G`" ( *//?vٳg 2{3gH"##ϟ?fee5w˗KD"PAٿ3g455.]ogg'<',Xhee%E^A A"??ƍW\Z~yd5:$$sD<͛7;::\t)33sɒ%t-[,+++222##188> K֯_аm۶+V2П燆n۶MCCcӧOpX:TATVVN8٫W }]vv̙3LRUUuPC5Y\\ܹsT u8=xkks<<<Q] ?GZZgkZZȑ#njQQ 痔o-Zpz-,,lݺu>>>/^hX`k&L}v^z5amm۷ocZPA.7N (ƜǓH۷olATA555u8C Aۻ"99phԨQC ҆`?W^ݸqCY ƍiiiׯ:D ?~X"]?wܑ#GñQr Af͚ݻwc|Ǐ䉣#ֱ J %D|ggg:O"GFx<1@:D9&2D޽۷ϟW /^ܿ?ֱ J %Deee/?ֱȚMnݚu,rBMd^^^uuurx<'''SSӘcA ֭[pd.2|С;wܹsX%j0C577r ֱ`leeeheDP QQ7n{۶m{j%C$`U$ fbbr5c 'N~1ց J`Uŋ@ŦMx@䈋MDDց `SSSsՅ b|Yp˗kkkQ( *… x<~ƌX"_.]:Dy&2D1f;w@Ό3***Q'O̞=@䑿JJJuu5ց J%Dƒd///G>>>޽{X( `r}WWWu H]]X( `;f_w: DIwޕxzzb,(((((:Dt7d_C p^:DLSSS:u 2_&YCC(33S!!*%Ddgga: DL`zdggj0D[ZZbz=Q %DiiiSp8 &hhhhii3&99hnnp֯^0`|rɒ%t:n޼y%%%Wϟ5kRWW>|xTTΎN0oUT///---6=sGšD$C ^*Na> 葉 LNMM}}}//] TWW߹s$''hjj˖X,̱c"""FKKKxzUUՑ#G , bff7o^_|õ t %DU%߿/fy GΞ= ;w@ؽ{0@'NY]v-w?NPM6Åoݺj*1 ,^ >|?%ccc555⟂ ]B Q_ӧbYYYG`c-;aZ[[ʺ|FWWW:"222666bvW n\PP )%[D^(J244t022~@>@81~ 88ԴEEEb)PWWWxH41$v!@ Q pzuDކ.KCL& oXt8xSSA{,& I"uuޞ QdߘM0۰VJ@olxV><0W?~2~1`I@ Q 6ܹsGx;>>^2N{b/ 2255B&M\|Yx[LMMWZ%fggg@RR@ttt^/@   AdG]]ɓbww8LؘF t 566600ohhHHH022266JJJ 뗖 ω'Fkkk*Y]]_l':Ϟ=#ϟ?za`+RXXSPATH~v!fa`***͵|||?~,ZwUUŋi4˫E l1lذϋp86l}rئ" ߾}8ND?bEDDSvٳn޼u C}0 G(;;Χgڃ J0 'D~ l[[[AJ0 p8eeeX"ѦDJ`/C7=":DMMMggwb{+ɔ5ܡB?C)Som ] Cuuu~~W?~޽{=zŋŢHT h3&22RO@bcc.\( XD<vڙ3gxsN:*)) bbbk]]nN\.wذa gΜ:thdd/r֭gϞHXcccrrrzzzNNNVVVVV\Kjjj666 FTV8%@rrp_]v ?QO(QF˖-;p̚yyy>|hjjmmm?~ebb'֯_?i$kkko/_Gٿ`l``TjXXZ>|XMMMtl6Nm' qaz7^`9jkk_aÆÇD553fܺuK.` 9s\\\0yj94hPpppZZ_.;v=z$rssDϧ?R555l?#g9y򤇇766 ҃ ҳ3g2 ggH@PYYI&Ǎסɓ)JeeN:$kh/\\J/.@)\R :^f )-J3U?'JKKDApp㉢OOCCC"J}}޽{ Ԗ,Y.'-((رc)L^h4(`t3x|qqpee%DD zj^eee֭[tҥKƍ1hРؘH$jjjxBboשּׁqmmmϞ= sbtIÅ w !/iiiqN5{ZZZvܩaÆRpIO/ J0HÑH$ؽ MMM===Ex<}}.~:$$ é}L&>\X 'GE??۶mܹSx͛7MLLV\)˗{h[vXU|  ޽{ ݶm]'N HNdrAAA &MD"x[HHHUw(|> ?9ء.rx֬YSpi۶mT*1>>^G uҾ(KkA477~R]]7vpp JNN -[D?9(梢444l7 yyy'NZS} =ztc@ p.(( ?Ď;FP p.ˍ^t)u|͚5_dժU 2 ]r%".^,b( =PN؀]]Εإy {U|x<^xg^'''Y/_ @>9;;khhgҺr_ %hkk+,,{Çccc޽ٳ*c9來:t萓_~?l"Ho߾|nnn.L޿/k~:88xСv(ܹ3p@6p$HMMeX Rn.Z8p3/XΝ;yf??Ahuss[hў={bbbn|vrp<f͚gϞC/&–1//n^oNR(, ":P5LAĐH˗+Oή\E(OaaƍxS:-[L3bĈǏ+h ZH</**LJ@ 0K ºqk׮,/ё 2b hmmfffK.VٯIIIT*uݺuX"1ߧP(6mù(Avv7|C&MMMwtޞxb bddo>Ej'/.]|||\dByذalllSȕ÷y5i3EHKKc2S @ pC'YfxSNɾ$00`脄I[YY)l  *((՝3g s,K6olZrbٲeL&WC.U4988P_~E~@'OfϞ-ʩ^| Ma]N۷EΝS\uuuTTT@@\!88XO?D00 x ZLss~ JtM8,$$D\ ,榠ϱgqS-ZD$O>u,}xSJ-pyN 9Q(evZ** 'dD??hZ$)uuu8nʕrI}}sΕqwIcoo7ԀτV &ud2ڭ[p8v~>>ommVuJ`G $)hkk1cE`EPV񇶶ٽ=""HGG?:&&pK4>& ͙3˟ƍ}r%,,D"uJr&K.xܩ庻cp/mmmѝTAUUղe¨Qg·~Н^2gϞ#GxMMM!ǐH$6KѳVaˉ&CCàOP Ǐl'ӗx;qĔlIJJ?~<`ԨQPUPZZZ]NK$I$hsٲea#88XOOS{)[r1bb-K[T*U6S4***K_xqȐ!۷o5 0tK.anE9e2bng޾}nݺQeK0+V֖.;fJ}KLLLd37nk";5Hď?H&1l޽A{!J0p۳gb,O4fK|.Y4222~'6 6lΝ;SRR$XniiINNޱc3YZZnٲ A Yx<b:^9r:P@ mU{` IDAT prr:[x]>jUUUN:s̛7o,,,-[6{l8_@ x .\PUUEܼ\]]2+//zQ||ÇY,ٳ]\\T۷o( #t:L&pǛH?'$$dРAX.*++ ^:y'l߾X";ǎ[vmZZ#>ʕ3g9sA5 Uz*!!!>>>))d2mllLLL444 p8(777''=zh///OOp8_jKHH*((033:92dȐѣGL[[ۭ["""_yy{{0SKK <~yEkkkGFFN>]qerٽ{̙337oNMM{nG߾}?oʔ)[ii3gl| +77 @䎆~uljD#A߿?{N 3...cƌٱcرc644?ϟ?[py<Al ΀f:Lڥ첳;j}}}}};wp8dMMRaauuu`>@@ HKKW}'~w'N֬Y#Zڵk. r@RR >>_!--_+V۷ow~066366{e FQ)4((((䔙YF#C<ոqϟ߷sKJJl66l煇ׯ_ollb/^\__@ (//>}6ʂS˛{H$Ǧj4~::>Sw5@}}.طkICkkp$xڨT!퐺m69l)fΜ`0###Aee%L7n\“'OP(:^SSvZ[[[ֿ !zjSw|Xx=F (..=mmǫkjjz{{w*Y?Ð!Cw}DT7ccc͕Oi&@xx:[nZJÇ***EXÃ]$_ї `'H_|ExxxccyyyR$ v±cpGs |Z]__#2*wamm-#:joogvvvG\]]K9Q+Hx4##`cc#[eeeBB±c֬Ycll,jJNN::yvL0p~;|`H$hs̉nmmK|AJSS ƬY FDz 2w\)!)]=ر'ΉORRxA*~:***88N~~~QQQϞ=eXAD:Em6@ Ue .# r/^%Ç~: (( E?fpdΝ;޼ydʕR}iCڱc]okk'~ǏAMMMߞ%''BBB:`>}d2:HϞ=I' +,+((װbN|OûСCGGpСVVVR xMhhɓl[ッ())144ׯ_ZZZvv6l,=qℰ766}qN566Z[[Sfp8UٳgAAA~~~CvSQԡCǏ%[n5˗/?|Qd?~LD HpL&smmm%YYYR믿»pχw =z W$: ̙3%KH)`@ٳOOO2@&E=I$ґ#Gf6e ҽ/Ή~p8 6 |x_~e…fff¡γg޺uSSSkkk1.+C?sG(׏=:,,68ƍ à b0 1$$Dحa  ???sss---. t-)E@/_$X"/JKK=zwիWO<ёF _1c,YdΝ/^| \YG~ 4Hz-8N@Nʸb100ذaB Y&&& sH$#BOOCaÆ/_ eee߿/,,,((3H$‚f[XX ɭ={޽DMM X0.*j𰳳 :8&gϞ˗:t|>)٥KZZZ*++KtrHHu455߾,dTژ1c>lhhu8V[[[ZZZ^^-))VTTEzzz&&&+EᔕYXX߿ʕXǂBkk#G,^`-[a2|q)++322']WWTTTxѽD"fSCCCuuuee0$o;lMRI?U">|(++CeeeiiJ555ccc###SSSCCC######ezW}VKX"&&&''LQ̾}~Wuj喌LbbgqqIwڵ]vDXaX>S}}0߈&.vk:L6L&S{LN&t:B#]jjjjjjr\.J)ѝt:gXzzz, l_&BCCKK|* &..n̘1pkDhϞ=(++^"nݺxgggPSS_uuu6ÁIp86%E( Hdx---8@ hhhH$<]MMM8^ ަՒՅOm'lbjii3sMMMuuu.xuuu$ijjoiSSL' ѻ(y?~m6l EKKȑ# .:2vX==.Q頮N{W*r---<vo]]p8\[x& n$ {h4AR544T*Ng24 6ZRTl|믫V:Y߿/EM0###8iZZZ:h"q777+kyo,o\oÌoüouvLRh fVlmڴ)444++Xd׷ٳg8رѣ #3&??XD5559::96ere??$}7h*--Ť Anݻwe *wϧ`p 6̞=:15 9sf֬YX *d'N;wnMM ֱʕ+'NaN08n;TYRRRaayAT˙3gxEDD\t s#N0痕a\2d AҺxbrrݻE^xvn+䇜ñc\.x >(ݻwoٲ%!!aȑX"y#FJHHb`K,xbqq1ց`ɓs:QQ7n2eʔ)S233EZZZMV__^ WLkk+ ȑ#Xǂ&6=k֬_XDuN2%===%%Eis*555%%ʪW*| D":uX0t?Ai#H&M: k֬{]$7|~a߿9s:n ihhܾ}eԨQXY|… O<ٷ%eH0t:=00~{=ֱ` 44:A@__ӧ:::...peEpƍwջwN4oQ>999ܹsXd!00pXǂ }Wׯ_1bǏNuMgg>_Gj02|رثWbLmܸU_D0۷o{{{{zz8p@~?|ɩ]@Df͚eiirDF.W^:A>),,Bxyy`K|~PP5kV]]_PIȠO6ɓX"uNNNCr ֱ ҝ={pVVVp]v[ŧ$MdѹsN> ϏD">}Xȑ#҆ `IS.)|>СC)))D Djjj/^:)ڶmBIMM:Az˦p7VS(͛7KA  suu߿?:HLL$:Az!((D"YXX?~H|}}cǎΖS(aFFFIׯgΜގu,ѻw-[F&̎=,gxxxܻwOzO F M2ENb u,|•+W]Vڍޥ>ƍcXNNNx9_>mڴǏjiycRRRA{MDDDDDDII Juww9r=:\.7''qqqqqq4/7o/@K@ >>6lؕ+W/&L`mmu8HWFFF\\kkkڶvvvzzzZZZjjjuuu6''';;P Ç3[^QxɤI^u82eʔ!CX 촷deeeee666x@t:BԤP(l6FMM U"ǏpႫ+E ۷履~>}zxx8L:"A^Pݰ}򥋋֭[ڰ'Op.(U@`6m? kqqqd2ŋ...X Rp8듓333BCC*SQQ1o<WW״4]Q\` +W:;;?z燆%%%? /IDATߗ/_V T1h4ڎ;ӵG9nܸ`  \bś7oNU0 wݻwnj(ZZZ9beev &ܹF2A)QNn$$$l߾=>>) _CRSSϞ={%KlܸDzO "{(ˋ/""".^XYY;i$///\ÇQQQYYYÆ ;w?ZAL|~ll3g]d``[2d-JR<ÇLLL͛7w\{{{A̡ӝGERSS|>377+10L &d2x|ss3p8555>|ήhjjzzz,% C F\p!w޵xF(L隚0 ښ*=A "g5 "(  R "(  R|X{IENDB`state-machine-1.2.0/examples/doc/frames.html0000644000175000017500000000067412305405267020353 0ustar boutilboutil Documentation by YARD 0.7.5 state-machine-1.2.0/examples/doc/js/0000755000175000017500000000000012305405267016615 5ustar boutilboutilstate-machine-1.2.0/examples/doc/js/full_list.js0000644000175000017500000001210512305405267021147 0ustar boutilboutilvar inSearch = null; var searchIndex = 0; var searchCache = []; var searchString = ''; var regexSearchString = ''; var caseSensitiveMatch = false; var ignoreKeyCodeMin = 8; var ignoreKeyCodeMax = 46; var commandKey = 91; RegExp.escape = function(text) { return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); } function fullListSearch() { // generate cache searchCache = []; $('#full_list li').each(function() { var link = $(this).find('.object_link a'); var fullName = link.attr('title').split(' ')[0]; searchCache.push({name:link.text(), fullName:fullName, node:$(this), link:link}); }); $('#search input').keyup(function() { if ((event.keyCode > ignoreKeyCodeMin && event.keyCode < ignoreKeyCodeMax) || event.keyCode == commandKey) return; searchString = this.value; caseSensitiveMatch = searchString.match(/[A-Z]/) != null; regexSearchString = RegExp.escape(searchString); if (caseSensitiveMatch) { regexSearchString += "|" + $.map(searchString.split(''), function(e) { return RegExp.escape(e); }). join('.+?'); } if (searchString === "") { clearTimeout(inSearch); inSearch = null; $('ul .search_uncollapsed').removeClass('search_uncollapsed'); $('#full_list, #content').removeClass('insearch'); $('#full_list li').removeClass('found').each(function() { var link = $(this).find('.object_link a'); link.text(link.text()); }); if (clicked) { clicked.parents('ul').each(function() { $(this).removeClass('collapsed').prev().removeClass('collapsed'); }); } highlight(); } else { if (inSearch) clearTimeout(inSearch); searchIndex = 0; lastRowClass = ''; $('#full_list, #content').addClass('insearch'); $('#noresults').text(''); searchItem(); } }); $('#search input').focus(); $('#full_list').after("
"); } var lastRowClass = ''; function searchItem() { for (var i = 0; i < searchCache.length / 50; i++) { var item = searchCache[searchIndex]; var searchName = (searchString.indexOf('::') != -1 ? item.fullName : item.name); var matchString = regexSearchString; var matchRegexp = new RegExp(matchString, caseSensitiveMatch ? "" : "i"); if (searchName.match(matchRegexp) == null) { item.node.removeClass('found'); } else { item.node.css('padding-left', '10px').addClass('found'); item.node.parents().addClass('search_uncollapsed'); item.node.removeClass(lastRowClass).addClass(lastRowClass == 'r1' ? 'r2' : 'r1'); lastRowClass = item.node.hasClass('r1') ? 'r1' : 'r2'; item.link.html(item.name.replace(matchRegexp, "$&")); } if (searchCache.length === searchIndex + 1) { searchDone(); return; } else { searchIndex++; } } inSearch = setTimeout('searchItem()', 0); } function searchDone() { highlight(true); if ($('#full_list li:visible').size() === 0) { $('#noresults').text('No results were found.').hide().fadeIn(); } else { $('#noresults').text(''); } $('#content').removeClass('insearch'); clearTimeout(inSearch); inSearch = null; } clicked = null; function linkList() { $('#full_list li, #full_list li a:last').click(function(evt) { if ($(this).hasClass('toggle')) return true; if (this.tagName.toLowerCase() == "li") { var toggle = $(this).children('a.toggle'); if (toggle.size() > 0 && evt.pageX < toggle.offset().left) { toggle.click(); return false; } } if (clicked) clicked.removeClass('clicked'); var win = window.top.frames.main ? window.top.frames.main : window.parent; if (this.tagName.toLowerCase() == "a") { clicked = $(this).parent('li').addClass('clicked'); win.location = this.href; } else { clicked = $(this).addClass('clicked'); win.location = $(this).find('a:last').attr('href'); } return false; }); } function collapse() { if (!$('#full_list').hasClass('class')) return; $('#full_list.class a.toggle').click(function() { $(this).parent().toggleClass('collapsed').next().toggleClass('collapsed'); highlight(); return false; }); $('#full_list.class ul').each(function() { $(this).addClass('collapsed').prev().addClass('collapsed'); }); $('#full_list.class').children().removeClass('collapsed'); highlight(); } function highlight(no_padding) { var n = 1; $('#full_list li:visible').each(function() { var next = n == 1 ? 2 : 1; $(this).removeClass("r" + next).addClass("r" + n); if (!no_padding && $('#full_list').hasClass('class')) { $(this).css('padding-left', (10 + $(this).parents('ul').size() * 15) + 'px'); } n = next; }); } function escapeShortcut() { $(document).keydown(function(evt) { if (evt.which == 27) { $('#search_frame', window.top.document).slideUp(100); $('#search a', window.top.document).removeClass('active inactive'); $(window.top).focus(); } }); } $(escapeShortcut); $(fullListSearch); $(linkList); $(collapse); state-machine-1.2.0/examples/doc/js/app.js0000644000175000017500000001453412305405267017742 0ustar boutilboutilfunction createSourceLinks() { $('.method_details_list .source_code'). before("[View source]"); $('.toggleSource').toggle(function() { $(this).parent().nextAll('.source_code').slideDown(100); $(this).text("Hide source"); }, function() { $(this).parent().nextAll('.source_code').slideUp(100); $(this).text("View source"); }); } function createDefineLinks() { var tHeight = 0; $('.defines').after(" more..."); $('.toggleDefines').toggle(function() { tHeight = $(this).parent().prev().height(); $(this).prev().show(); $(this).parent().prev().height($(this).parent().height()); $(this).text("(less)"); }, function() { $(this).prev().hide(); $(this).parent().prev().height(tHeight); $(this).text("more..."); }); } function createFullTreeLinks() { var tHeight = 0; $('.inheritanceTree').toggle(function() { tHeight = $(this).parent().prev().height(); $(this).parent().toggleClass('showAll'); $(this).text("(hide)"); $(this).parent().prev().height($(this).parent().height()); }, function() { $(this).parent().toggleClass('showAll'); $(this).parent().prev().height(tHeight); $(this).text("show all"); }); } function fixBoxInfoHeights() { $('dl.box dd.r1, dl.box dd.r2').each(function() { $(this).prev().height($(this).height()); }); } function searchFrameLinks() { $('#method_list_link').click(function() { toggleSearchFrame(this, relpath + 'method_list.html'); }); $('#class_list_link').click(function() { toggleSearchFrame(this, relpath + 'class_list.html'); }); $('#file_list_link').click(function() { toggleSearchFrame(this, relpath + 'file_list.html'); }); } function toggleSearchFrame(id, link) { var frame = $('#search_frame'); $('#search a').removeClass('active').addClass('inactive'); if (frame.attr('src') == link && frame.css('display') != "none") { frame.slideUp(100); $('#search a').removeClass('active inactive'); } else { $(id).addClass('active').removeClass('inactive'); frame.attr('src', link).slideDown(100); } } function linkSummaries() { $('.summary_signature').click(function() { document.location = $(this).find('a').attr('href'); }); } function framesInit() { if (window.top.frames.main) { document.body.className = 'frames'; $('#menu .noframes a').attr('href', document.location); $('html head title', window.parent.document).text($('html head title').text()); } } function keyboardShortcuts() { if (window.top.frames.main) return; $(document).keypress(function(evt) { if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) return; if (typeof evt.target !== "undefined" && (evt.target.nodeName == "INPUT" || evt.target.nodeName == "TEXTAREA")) return; switch (evt.charCode) { case 67: case 99: $('#class_list_link').click(); break; // 'c' case 77: case 109: $('#method_list_link').click(); break; // 'm' case 70: case 102: $('#file_list_link').click(); break; // 'f' default: break; } }); } function summaryToggle() { $('.summary_toggle').click(function() { localStorage.summaryCollapsed = $(this).text(); $(this).text($(this).text() == "collapse" ? "expand" : "collapse"); var next = $(this).parent().parent().nextAll('ul.summary').first(); if (next.hasClass('compact')) { next.toggle(); next.nextAll('ul.summary').first().toggle(); } else if (next.hasClass('summary')) { var list = $('
    '); list.html(next.html()); list.find('.summary_desc, .note').remove(); list.find('a').each(function() { $(this).html($(this).find('strong').html()); $(this).parent().html($(this)[0].outerHTML); }); next.before(list); next.toggle(); } return false; }); if (localStorage) { if (localStorage.summaryCollapsed == "collapse") $('.summary_toggle').click(); else localStorage.summaryCollapsed = "expand"; } } function fixOutsideWorldLinks() { $('a').each(function() { if (window.location.host != this.host) this.target = '_parent'; }); } function generateTOC() { if ($('#filecontents').length === 0) return; var _toc = $('
      '); var show = false; var toc = _toc; var counter = 0; var tags = ['h2', 'h3', 'h4', 'h5', 'h6']; var i; if ($('#filecontents h1').length > 1) tags.unshift('h1'); for (i = 0; i < tags.length; i++) { tags[i] = '#filecontents ' + tags[i]; } var lastTag = parseInt(tags[0][1], 10); $(tags.join(', ')).each(function() { if (this.id == "filecontents") return; show = true; var thisTag = parseInt(this.tagName[1], 10); if (this.id.length === 0) { var proposedId = $(this).text().replace(/[^a-z0-9-]/ig, '_'); if ($('#' + proposedId).length > 0) { proposedId += counter; counter++; } this.id = proposedId; } if (thisTag > lastTag) { for (i = 0; i < thisTag - lastTag; i++) { var tmp = $('
        '); toc.append(tmp); toc = tmp; } } if (thisTag < lastTag) { for (i = 0; i < lastTag - thisTag; i++) toc = toc.parent(); } toc.append('
      1. ' + $(this).text() + '
      2. '); lastTag = thisTag; }); if (!show) return; html = ''; $('#content').prepend(html); $('#toc').append(_toc); $('#toc .hide_toc').toggle(function() { $('#toc .top').slideUp('fast'); $('#toc').toggleClass('hidden'); $('#toc .title small').toggle(); }, function() { $('#toc .top').slideDown('fast'); $('#toc').toggleClass('hidden'); $('#toc .title small').toggle(); }); $('#toc .float_toc').toggle(function() { $(this).text('float'); $('#toc').toggleClass('nofloat'); }, function() { $(this).text('left'); $('#toc').toggleClass('nofloat'); }); } $(framesInit); $(createSourceLinks); $(createDefineLinks); $(createFullTreeLinks); $(fixBoxInfoHeights); $(searchFrameLinks); $(linkSummaries); $(keyboardShortcuts); $(summaryToggle); $(fixOutsideWorldLinks); $(generateTOC);state-machine-1.2.0/examples/doc/js/jquery.js0000644000175000017500000024764512305405267020514 0ustar boutilboutil/*! * jQuery JavaScript Library v1.5.2 * http://jquery.com/ * * Copyright 2011, John Resig * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * Includes Sizzle.js * http://sizzlejs.com/ * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * * Date: Thu Mar 31 15:28:23 2011 -0400 */ (function(a,b){function ci(a){return d.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cf(a){if(!b_[a]){var b=d("<"+a+">").appendTo("body"),c=b.css("display");b.remove();if(c==="none"||c==="")c="block";b_[a]=c}return b_[a]}function ce(a,b){var c={};d.each(cd.concat.apply([],cd.slice(0,b)),function(){c[this]=a});return c}function b$(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function bZ(){try{return new a.XMLHttpRequest}catch(b){}}function bY(){d(a).unload(function(){for(var a in bW)bW[a](0,1)})}function bS(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var e=a.dataTypes,f={},g,h,i=e.length,j,k=e[0],l,m,n,o,p;for(g=1;g=0===c})}function P(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function H(a,b){return(a&&a!=="*"?a+".":"")+b.replace(t,"`").replace(u,"&")}function G(a){var b,c,e,f,g,h,i,j,k,l,m,n,o,p=[],q=[],s=d._data(this,"events");if(a.liveFired!==this&&s&&s.live&&!a.target.disabled&&(!a.button||a.type!=="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var t=s.live.slice(0);for(i=0;ic)break;a.currentTarget=f.elem,a.data=f.handleObj.data,a.handleObj=f.handleObj,o=f.handleObj.origHandler.apply(f.elem,arguments);if(o===!1||a.isPropagationStopped()){c=f.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function E(a,c,e){var f=d.extend({},e[0]);f.type=a,f.originalEvent={},f.liveFired=b,d.event.handle.call(c,f),f.isDefaultPrevented()&&e[0].preventDefault()}function y(){return!0}function x(){return!1}function i(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function h(a,c,e){if(e===b&&a.nodeType===1){e=a.getAttribute("data-"+c);if(typeof e==="string"){try{e=e==="true"?!0:e==="false"?!1:e==="null"?null:d.isNaN(e)?g.test(e)?d.parseJSON(e):e:parseFloat(e)}catch(f){}d.data(a,c,e)}else e=b}return e}var c=a.document,d=function(){function G(){if(!d.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(G,1);return}d.ready()}}var d=function(a,b){return new d.fn.init(a,b,g)},e=a.jQuery,f=a.$,g,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,i=/\S/,j=/^\s+/,k=/\s+$/,l=/\d/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=navigator.userAgent,w,x,y,z=Object.prototype.toString,A=Object.prototype.hasOwnProperty,B=Array.prototype.push,C=Array.prototype.slice,D=String.prototype.trim,E=Array.prototype.indexOf,F={};d.fn=d.prototype={constructor:d,init:function(a,e,f){var g,i,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!e&&c.body){this.context=c,this[0]=c.body,this.selector="body",this.length=1;return this}if(typeof a==="string"){g=h.exec(a);if(!g||!g[1]&&e)return!e||e.jquery?(e||f).find(a):this.constructor(e).find(a);if(g[1]){e=e instanceof d?e[0]:e,k=e?e.ownerDocument||e:c,j=m.exec(a),j?d.isPlainObject(e)?(a=[c.createElement(j[1])],d.fn.attr.call(a,e,!0)):a=[k.createElement(j[1])]:(j=d.buildFragment([g[1]],[k]),a=(j.cacheable?d.clone(j.fragment):j.fragment).childNodes);return d.merge(this,a)}i=c.getElementById(g[2]);if(i&&i.parentNode){if(i.id!==g[2])return f.find(a);this.length=1,this[0]=i}this.context=c,this.selector=a;return this}if(d.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return d.makeArray(a,this)},selector:"",jquery:"1.5.2",length:0,size:function(){return this.length},toArray:function(){return C.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var e=this.constructor();d.isArray(a)?B.apply(e,a):d.merge(e,a),e.prevObject=this,e.context=this.context,b==="find"?e.selector=this.selector+(this.selector?" ":"")+c:b&&(e.selector=this.selector+"."+b+"("+c+")");return e},each:function(a,b){return d.each(this,a,b)},ready:function(a){d.bindReady(),x.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(C.apply(this,arguments),"slice",C.call(arguments).join(","))},map:function(a){return this.pushStack(d.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:B,sort:[].sort,splice:[].splice},d.fn.init.prototype=d.fn,d.extend=d.fn.extend=function(){var a,c,e,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i==="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!=="object"&&!d.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;x.resolveWith(c,[d]),d.fn.trigger&&d(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!x){x=d._Deferred();if(c.readyState==="complete")return setTimeout(d.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",y,!1),a.addEventListener("load",d.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",y),a.attachEvent("onload",d.ready);var b=!1;try{b=a.frameElement==null}catch(e){}c.documentElement.doScroll&&b&&G()}}},isFunction:function(a){return d.type(a)==="function"},isArray:Array.isArray||function(a){return d.type(a)==="array"},isWindow:function(a){return a&&typeof a==="object"&&"setInterval"in a},isNaN:function(a){return a==null||!l.test(a)||isNaN(a)},type:function(a){return a==null?String(a):F[z.call(a)]||"object"},isPlainObject:function(a){if(!a||d.type(a)!=="object"||a.nodeType||d.isWindow(a))return!1;if(a.constructor&&!A.call(a,"constructor")&&!A.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a){}return c===b||A.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!=="string"||!b)return null;b=d.trim(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return a.JSON&&a.JSON.parse?a.JSON.parse(b):(new Function("return "+b))();d.error("Invalid JSON: "+b)},parseXML:function(b,c,e){a.DOMParser?(e=new DOMParser,c=e.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),e=c.documentElement,(!e||!e.nodeName||e.nodeName==="parsererror")&&d.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(a){if(a&&i.test(a)){var b=c.head||c.getElementsByTagName("head")[0]||c.documentElement,e=c.createElement("script");d.support.scriptEval()?e.appendChild(c.createTextNode(a)):e.text=a,b.insertBefore(e,b.firstChild),b.removeChild(e)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,e){var f,g=0,h=a.length,i=h===b||d.isFunction(a);if(e){if(i){for(f in a)if(c.apply(a[f],e)===!1)break}else for(;g1?f.call(arguments,0):c,--g||h.resolveWith(h,f.call(b,0))}}var b=arguments,c=0,e=b.length,g=e,h=e<=1&&a&&d.isFunction(a.promise)?a:d.Deferred();if(e>1){for(;c
        a";var e=b.getElementsByTagName("*"),f=b.getElementsByTagName("a")[0],g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=b.getElementsByTagName("input")[0];if(e&&e.length&&f){d.support={leadingWhitespace:b.firstChild.nodeType===3,tbody:!b.getElementsByTagName("tbody").length,htmlSerialize:!!b.getElementsByTagName("link").length,style:/red/.test(f.getAttribute("style")),hrefNormalized:f.getAttribute("href")==="/a",opacity:/^0.55$/.test(f.style.opacity),cssFloat:!!f.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,deleteExpando:!0,optDisabled:!1,checkClone:!1,noCloneEvent:!0,noCloneChecked:!0,boxModel:null,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableHiddenOffsets:!0,reliableMarginRight:!0},i.checked=!0,d.support.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,d.support.optDisabled=!h.disabled;var j=null;d.support.scriptEval=function(){if(j===null){var b=c.documentElement,e=c.createElement("script"),f="script"+d.now();try{e.appendChild(c.createTextNode("window."+f+"=1;"))}catch(g){}b.insertBefore(e,b.firstChild),a[f]?(j=!0,delete a[f]):j=!1,b.removeChild(e)}return j};try{delete b.test}catch(k){d.support.deleteExpando=!1}!b.addEventListener&&b.attachEvent&&b.fireEvent&&(b.attachEvent("onclick",function l(){d.support.noCloneEvent=!1,b.detachEvent("onclick",l)}),b.cloneNode(!0).fireEvent("onclick")),b=c.createElement("div"),b.innerHTML="";var m=c.createDocumentFragment();m.appendChild(b.firstChild),d.support.checkClone=m.cloneNode(!0).cloneNode(!0).lastChild.checked,d(function(){var a=c.createElement("div"),b=c.getElementsByTagName("body")[0];if(b){a.style.width=a.style.paddingLeft="1px",b.appendChild(a),d.boxModel=d.support.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,d.support.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
        ",d.support.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
        t
        ";var e=a.getElementsByTagName("td");d.support.reliableHiddenOffsets=e[0].offsetHeight===0,e[0].style.display="",e[1].style.display="none",d.support.reliableHiddenOffsets=d.support.reliableHiddenOffsets&&e[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(a.style.width="1px",a.style.marginRight="0",d.support.reliableMarginRight=(parseInt(c.defaultView.getComputedStyle(a,null).marginRight,10)||0)===0),b.removeChild(a).style.display="none",a=e=null}});var n=function(a){var b=c.createElement("div");a="on"+a;if(!b.attachEvent)return!0;var d=a in b;d||(b.setAttribute(a,"return;"),d=typeof b[a]==="function");return d};d.support.submitBubbles=n("submit"),d.support.changeBubbles=n("change"),b=e=f=null}}();var g=/^(?:\{.*\}|\[.*\])$/;d.extend({cache:{},uuid:0,expando:"jQuery"+(d.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?d.cache[a[d.expando]]:a[d.expando];return!!a&&!i(a)},data:function(a,c,e,f){if(d.acceptData(a)){var g=d.expando,h=typeof c==="string",i,j=a.nodeType,k=j?d.cache:a,l=j?a[d.expando]:a[d.expando]&&d.expando;if((!l||f&&l&&!k[l][g])&&h&&e===b)return;l||(j?a[d.expando]=l=++d.uuid:l=d.expando),k[l]||(k[l]={},j||(k[l].toJSON=d.noop));if(typeof c==="object"||typeof c==="function")f?k[l][g]=d.extend(k[l][g],c):k[l]=d.extend(k[l],c);i=k[l],f&&(i[g]||(i[g]={}),i=i[g]),e!==b&&(i[c]=e);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[c]:i}},removeData:function(b,c,e){if(d.acceptData(b)){var f=d.expando,g=b.nodeType,h=g?d.cache:b,j=g?b[d.expando]:d.expando;if(!h[j])return;if(c){var k=e?h[j][f]:h[j];if(k){delete k[c];if(!i(k))return}}if(e){delete h[j][f];if(!i(h[j]))return}var l=h[j][f];d.support.deleteExpando||h!=a?delete h[j]:h[j]=null,l?(h[j]={},g||(h[j].toJSON=d.noop),h[j][f]=l):g&&(d.support.deleteExpando?delete b[d.expando]:b.removeAttribute?b.removeAttribute(d.expando):b[d.expando]=null)}},_data:function(a,b,c){return d.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=d.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),d.fn.extend({data:function(a,c){var e=null;if(typeof a==="undefined"){if(this.length){e=d.data(this[0]);if(this[0].nodeType===1){var f=this[0].attributes,g;for(var i=0,j=f.length;i-1)return!0;return!1},val:function(a){if(!arguments.length){var c=this[0];if(c){if(d.nodeName(c,"option")){var e=c.attributes.value;return!e||e.specified?c.value:c.text}if(d.nodeName(c,"select")){var f=c.selectedIndex,g=[],h=c.options,i=c.type==="select-one";if(f<0)return null;for(var j=i?f:0,k=i?f+1:h.length;j=0;else if(d.nodeName(this,"select")){var f=d.makeArray(e);d("option",this).each(function(){this.selected=d.inArray(d(this).val(),f)>=0}),f.length||(this.selectedIndex=-1)}else this.value=e}})}}),d.extend({attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,e,f){if(!a||a.nodeType===3||a.nodeType===8||a.nodeType===2)return b;if(f&&c in d.attrFn)return d(a)[c](e);var g=a.nodeType!==1||!d.isXMLDoc(a),h=e!==b;c=g&&d.props[c]||c;if(a.nodeType===1){var i=m.test(c);if(c==="selected"&&!d.support.optSelected){var j=a.parentNode;j&&(j.selectedIndex,j.parentNode&&j.parentNode.selectedIndex)}if((c in a||a[c]!==b)&&g&&!i){h&&(c==="type"&&n.test(a.nodeName)&&a.parentNode&&d.error("type property can't be changed"),e===null?a.nodeType===1&&a.removeAttribute(c):a[c]=e);if(d.nodeName(a,"form")&&a.getAttributeNode(c))return a.getAttributeNode(c).nodeValue;if(c==="tabIndex"){var k=a.getAttributeNode("tabIndex");return k&&k.specified?k.value:o.test(a.nodeName)||p.test(a.nodeName)&&a.href?0:b}return a[c]}if(!d.support.style&&g&&c==="style"){h&&(a.style.cssText=""+e);return a.style.cssText}h&&a.setAttribute(c,""+e);if(!a.attributes[c]&&(a.hasAttribute&&!a.hasAttribute(c)))return b;var l=!d.support.hrefNormalized&&g&&i?a.getAttribute(c,2):a.getAttribute(c);return l===null?b:l}h&&(a[c]=e);return a[c]}});var r=/\.(.*)$/,s=/^(?:textarea|input|select)$/i,t=/\./g,u=/ /g,v=/[^\w\s.|`]/g,w=function(a){return a.replace(v,"\\$&")};d.event={add:function(c,e,f,g){if(c.nodeType!==3&&c.nodeType!==8){try{d.isWindow(c)&&(c!==a&&!c.frameElement)&&(c=a)}catch(h){}if(f===!1)f=x;else if(!f)return;var i,j;f.handler&&(i=f,f=i.handler),f.guid||(f.guid=d.guid++);var k=d._data(c);if(!k)return;var l=k.events,m=k.handle;l||(k.events=l={}),m||(k.handle=m=function(a){return typeof d!=="undefined"&&d.event.triggered!==a.type?d.event.handle.apply(m.elem,arguments):b}),m.elem=c,e=e.split(" ");var n,o=0,p;while(n=e[o++]){j=i?d.extend({},i):{handler:f,data:g},n.indexOf(".")>-1?(p=n.split("."),n=p.shift(),j.namespace=p.slice(0).sort().join(".")):(p=[],j.namespace=""),j.type=n,j.guid||(j.guid=f.guid);var q=l[n],r=d.event.special[n]||{};if(!q){q=l[n]=[];if(!r.setup||r.setup.call(c,g,p,m)===!1)c.addEventListener?c.addEventListener(n,m,!1):c.attachEvent&&c.attachEvent("on"+n,m)}r.add&&(r.add.call(c,j),j.handler.guid||(j.handler.guid=f.guid)),q.push(j),d.event.global[n]=!0}c=null}},global:{},remove:function(a,c,e,f){if(a.nodeType!==3&&a.nodeType!==8){e===!1&&(e=x);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=d.hasData(a)&&d._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(e=c.handler,c=c.type);if(!c||typeof c==="string"&&c.charAt(0)==="."){c=c||"";for(h in t)d.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+d.map(m.slice(0).sort(),w).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!e){for(j=0;j=0&&(a.type=f=f.slice(0,-1),a.exclusive=!0),e||(a.stopPropagation(),d.event.global[f]&&d.each(d.cache,function(){var b=d.expando,e=this[b];e&&e.events&&e.events[f]&&d.event.trigger(a,c,e.handle.elem)}));if(!e||e.nodeType===3||e.nodeType===8)return b;a.result=b,a.target=e,c=d.makeArray(c),c.unshift(a)}a.currentTarget=e;var h=d._data(e,"handle");h&&h.apply(e,c);var i=e.parentNode||e.ownerDocument;try{e&&e.nodeName&&d.noData[e.nodeName.toLowerCase()]||e["on"+f]&&e["on"+f].apply(e,c)===!1&&(a.result=!1,a.preventDefault())}catch(j){}if(!a.isPropagationStopped()&&i)d.event.trigger(a,c,i,!0);else if(!a.isDefaultPrevented()){var k,l=a.target,m=f.replace(r,""),n=d.nodeName(l,"a")&&m==="click",o=d.event.special[m]||{};if((!o._default||o._default.call(e,a)===!1)&&!n&&!(l&&l.nodeName&&d.noData[l.nodeName.toLowerCase()])){try{l[m]&&(k=l["on"+m],k&&(l["on"+m]=null),d.event.triggered=a.type,l[m]())}catch(p){}k&&(l["on"+m]=k),d.event.triggered=b}}},handle:function(c){var e,f,g,h,i,j=[],k=d.makeArray(arguments);c=k[0]=d.event.fix(c||a.event),c.currentTarget=this,e=c.type.indexOf(".")<0&&!c.exclusive,e||(g=c.type.split("."),c.type=g.shift(),j=g.slice(0).sort(),h=new RegExp("(^|\\.)"+j.join("\\.(?:.*\\.)?")+"(\\.|$)")),c.namespace=c.namespace||j.join("."),i=d._data(this,"events"),f=(i||{})[c.type];if(i&&f){f=f.slice(0);for(var l=0,m=f.length;l-1?d.map(a.options,function(a){return a.selected}).join("-"):"":a.nodeName.toLowerCase()==="select"&&(c=a.selectedIndex);return c},D=function D(a){var c=a.target,e,f;if(s.test(c.nodeName)&&!c.readOnly){e=d._data(c,"_change_data"),f=C(c),(a.type!=="focusout"||c.type!=="radio")&&d._data(c,"_change_data",f);if(e===b||f===e)return;if(e!=null||f)a.type="change",a.liveFired=b,d.event.trigger(a,arguments[1],c)}};d.event.special.change={filters:{focusout:D,beforedeactivate:D,click:function(a){var b=a.target,c=b.type;(c==="radio"||c==="checkbox"||b.nodeName.toLowerCase()==="select")&&D.call(this,a)},keydown:function(a){var b=a.target,c=b.type;(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&D.call(this,a)},beforeactivate:function(a){var b=a.target;d._data(b,"_change_data",C(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in B)d.event.add(this,c+".specialChange",B[c]);return s.test(this.nodeName)},teardown:function(a){d.event.remove(this,".specialChange");return s.test(this.nodeName)}},B=d.event.special.change.filters,B.focus=B.beforeactivate}c.addEventListener&&d.each({focus:"focusin",blur:"focusout"},function(a,b){function f(a){var c=d.event.fix(a);c.type=b,c.originalEvent={},d.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var e=0;d.event.special[b]={setup:function(){e++===0&&c.addEventListener(a,f,!0)},teardown:function(){--e===0&&c.removeEventListener(a,f,!0)}}}),d.each(["bind","one"],function(a,c){d.fn[c]=function(a,e,f){if(typeof a==="object"){for(var g in a)this[c](g,e,a[g],f);return this}if(d.isFunction(e)||e===!1)f=e,e=b;var h=c==="one"?d.proxy(f,function(a){d(this).unbind(a,h);return f.apply(this,arguments)}):f;if(a==="unload"&&c!=="one")this.one(a,e,f);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},d.attrFn&&(d.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,e,g){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!=="string")return e;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(f.call(n)==="[object Array]")if(u)if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&e.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&e.push(j[t]);else e.push.apply(e,n);else p(n,e);o&&(k(o,h,e,g),k.uniqueSort(e));return e};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b==="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return"text"===c&&(b===c||b===null)},radio:function(a){return"radio"===a.type},checkbox:function(a){return"checkbox"===a.type},file:function(a){return"file"===a.type},password:function(a){return"password"===a.type},submit:function(a){return"submit"===a.type},image:function(a){return"image"===a.type},reset:function(a){return"reset"===a.type},button:function(a){return"button"===a.type||a.nodeName.toLowerCase()==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(f.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length==="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!=="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!=="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!=="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!=="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

        ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
        ";if(a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!=="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(var g=c;g0},closest:function(a,b){var c=[],e,f,g=this[0];if(d.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(e=0,f=a.length;e-1:d(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=N.test(a)?d(a,b||this.context):null;for(e=0,f=this.length;e-1:d.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b)break}}c=c.length>1?d.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a==="string")return d.inArray(this[0],a?d(a):this.parent().children());return d.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a==="string"?d(a,b):d.makeArray(a),e=d.merge(this.get(),c);return this.pushStack(P(c[0])||P(e[0])?e:d.unique(e))},andSelf:function(){return this.add(this.prevObject)}}),d.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return d.dir(a,"parentNode")},parentsUntil:function(a,b,c){return d.dir(a,"parentNode",c)},next:function(a){return d.nth(a,2,"nextSibling")},prev:function(a){return d.nth(a,2,"previousSibling")},nextAll:function(a){return d.dir(a,"nextSibling")},prevAll:function(a){return d.dir(a,"previousSibling")},nextUntil:function(a,b,c){return d.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return d.dir(a,"previousSibling",c)},siblings:function(a){return d.sibling(a.parentNode.firstChild,a)},children:function(a){return d.sibling(a.firstChild)},contents:function(a){return d.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:d.makeArray(a.childNodes)}},function(a,b){d.fn[a]=function(c,e){var f=d.map(this,b,c),g=M.call(arguments);I.test(a)||(e=c),e&&typeof e==="string"&&(f=d.filter(e,f)),f=this.length>1&&!O[a]?d.unique(f):f,(this.length>1||K.test(e))&&J.test(a)&&(f=f.reverse());return this.pushStack(f,a,g.join(","))}}),d.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?d.find.matchesSelector(b[0],a)?[b[0]]:[]:d.find.matches(a,b)},dir:function(a,c,e){var f=[],g=a[c];while(g&&g.nodeType!==9&&(e===b||g.nodeType!==1||!d(g).is(e)))g.nodeType===1&&f.push(g),g=g[c];return f},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var R=/ jQuery\d+="(?:\d+|null)"/g,S=/^\s+/,T=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,U=/<([\w:]+)/,V=/",""],legend:[1,"
        ","
        "],thead:[1,"","
        "],tr:[2,"","
        "],td:[3,"","
        "],col:[2,"","
        "],area:[1,"",""],_default:[0,"",""]};Z.optgroup=Z.option,Z.tbody=Z.tfoot=Z.colgroup=Z.caption=Z.thead,Z.th=Z.td,d.support.htmlSerialize||(Z._default=[1,"div
        ","
        "]),d.fn.extend({text:function(a){if(d.isFunction(a))return this.each(function(b){var c=d(this);c.text(a.call(this,b,c.text()))});if(typeof a!=="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return d.text(this)},wrapAll:function(a){if(d.isFunction(a))return this.each(function(b){d(this).wrapAll(a.call(this,b))});if(this[0]){var b=d(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(d.isFunction(a))return this.each(function(b){d(this).wrapInner(a.call(this,b))});return this.each(function(){var b=d(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){d(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){d.nodeName(this,"body")||d(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=d(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,d(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,e;(e=this[c])!=null;c++)if(!a||d.filter(a,[e]).length)!b&&e.nodeType===1&&(d.cleanData(e.getElementsByTagName("*")),d.cleanData([e])),e.parentNode&&e.parentNode.removeChild(e);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&d.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return d.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(R,""):null;if(typeof a!=="string"||X.test(a)||!d.support.leadingWhitespace&&S.test(a)||Z[(U.exec(a)||["",""])[1].toLowerCase()])d.isFunction(a)?this.each(function(b){var c=d(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);else{a=a.replace(T,"<$1>");try{for(var c=0,e=this.length;c1&&l0?this.clone(!0):this).get();d(f[h])[b](j),e=e.concat(j)}return this.pushStack(e,a,f.selector)}}),d.extend({clone:function(a,b,c){var e=a.cloneNode(!0),f,g,h;if((!d.support.noCloneEvent||!d.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!d.isXMLDoc(a)){ba(a,e),f=bb(a),g=bb(e);for(h=0;f[h];++h)ba(f[h],g[h])}if(b){_(a,e);if(c){f=bb(a),g=bb(e);for(h=0;f[h];++h)_(f[h],g[h])}}return e},clean:function(a,b,e,f){b=b||c,typeof b.createElement==="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var g=[];for(var h=0,i;(i=a[h])!=null;h++){typeof i==="number"&&(i+="");if(!i)continue;if(typeof i!=="string"||W.test(i)){if(typeof i==="string"){i=i.replace(T,"<$1>");var j=(U.exec(i)||["",""])[1].toLowerCase(),k=Z[j]||Z._default,l=k[0],m=b.createElement("div");m.innerHTML=k[1]+i+k[2];while(l--)m=m.lastChild;if(!d.support.tbody){var n=V.test(i),o=j==="table"&&!n?m.firstChild&&m.firstChild.childNodes:k[1]===""&&!n?m.childNodes:[];for(var p=o.length-1;p>=0;--p)d.nodeName(o[p],"tbody")&&!o[p].childNodes.length&&o[p].parentNode.removeChild(o[p])}!d.support.leadingWhitespace&&S.test(i)&&m.insertBefore(b.createTextNode(S.exec(i)[0]),m.firstChild),i=m.childNodes}}else i=b.createTextNode(i);i.nodeType?g.push(i):g=d.merge(g,i)}if(e)for(h=0;g[h];h++)!f||!d.nodeName(g[h],"script")||g[h].type&&g[h].type.toLowerCase()!=="text/javascript"?(g[h].nodeType===1&&g.splice.apply(g,[h+1,0].concat(d.makeArray(g[h].getElementsByTagName("script")))),e.appendChild(g[h])):f.push(g[h].parentNode?g[h].parentNode.removeChild(g[h]):g[h]);return g},cleanData:function(a){var b,c,e=d.cache,f=d.expando,g=d.event.special,h=d.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&d.noData[j.nodeName.toLowerCase()])continue;c=j[d.expando];if(c){b=e[c]&&e[c][f];if(b&&b.events){for(var k in b.events)g[k]?d.event.remove(j,k):d.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[d.expando]:j.removeAttribute&&j.removeAttribute(d.expando),delete e[c]}}}});var bd=/alpha\([^)]*\)/i,be=/opacity=([^)]*)/,bf=/-([a-z])/ig,bg=/([A-Z]|^ms)/g,bh=/^-?\d+(?:px)?$/i,bi=/^-?\d/,bj={position:"absolute",visibility:"hidden",display:"block"},bk=["Left","Right"],bl=["Top","Bottom"],bm,bn,bo,bp=function(a,b){return b.toUpperCase()};d.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return d.access(this,a,c,!0,function(a,c,e){return e!==b?d.style(a,c,e):d.css(a,c)})},d.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bm(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0},cssProps:{"float":d.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,e,f){if(a&&a.nodeType!==3&&a.nodeType!==8&&a.style){var g,h=d.camelCase(c),i=a.style,j=d.cssHooks[h];c=d.cssProps[h]||h;if(e===b){if(j&&"get"in j&&(g=j.get(a,!1,f))!==b)return g;return i[c]}if(typeof e==="number"&&isNaN(e)||e==null)return;typeof e==="number"&&!d.cssNumber[h]&&(e+="px");if(!j||!("set"in j)||(e=j.set(a,e))!==b)try{i[c]=e}catch(k){}}},css:function(a,c,e){var f,g=d.camelCase(c),h=d.cssHooks[g];c=d.cssProps[g]||g;if(h&&"get"in h&&(f=h.get(a,!0,e))!==b)return f;if(bm)return bm(a,c,g)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bf,bp)}}),d.curCSS=d.css,d.each(["height","width"],function(a,b){d.cssHooks[b]={get:function(a,c,e){var f;if(c){a.offsetWidth!==0?f=bq(a,b,e):d.swap(a,bj,function(){f=bq(a,b,e)});if(f<=0){f=bm(a,b,b),f==="0px"&&bo&&(f=bo(a,b,b));if(f!=null)return f===""||f==="auto"?"0px":f}if(f<0||f==null){f=a.style[b];return f===""||f==="auto"?"0px":f}return typeof f==="string"?f:f+"px"}},set:function(a,b){if(!bh.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),d.support.opacity||(d.cssHooks.opacity={get:function(a,b){return be.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style;c.zoom=1;var e=d.isNaN(b)?"":"alpha(opacity="+b*100+")",f=c.filter||"";c.filter=bd.test(f)?f.replace(bd,e):c.filter+" "+e}}),d(function(){d.support.reliableMarginRight||(d.cssHooks.marginRight={get:function(a,b){var c;d.swap(a,{display:"inline-block"},function(){b?c=bm(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bn=function(a,c,e){var f,g,h;e=e.replace(bg,"-$1").toLowerCase();if(!(g=a.ownerDocument.defaultView))return b;if(h=g.getComputedStyle(a,null))f=h.getPropertyValue(e),f===""&&!d.contains(a.ownerDocument.documentElement,a)&&(f=d.style(a,e));return f}),c.documentElement.currentStyle&&(bo=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bh.test(d)&&bi.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bm=bn||bo,d.expr&&d.expr.filters&&(d.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!d.support.reliableHiddenOffsets&&(a.style.display||d.css(a,"display"))==="none"},d.expr.filters.visible=function(a){return!d.expr.filters.hidden(a)});var br=/%20/g,bs=/\[\]$/,bt=/\r?\n/g,bu=/#.*$/,bv=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bw=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bx=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,by=/^(?:GET|HEAD)$/,bz=/^\/\//,bA=/\?/,bB=/)<[^<]*)*<\/script>/gi,bC=/^(?:select|textarea)/i,bD=/\s+/,bE=/([?&])_=[^&]*/,bF=/(^|\-)([a-z])/g,bG=function(a,b,c){return b+c.toUpperCase()},bH=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bI=d.fn.load,bJ={},bK={},bL,bM;try{bL=c.location.href}catch(bN){bL=c.createElement("a"),bL.href="",bL=bL.href}bM=bH.exec(bL.toLowerCase())||[],d.fn.extend({load:function(a,c,e){if(typeof a!=="string"&&bI)return bI.apply(this,arguments);if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var g=a.slice(f,a.length);a=a.slice(0,f)}var h="GET";c&&(d.isFunction(c)?(e=c,c=b):typeof c==="object"&&(c=d.param(c,d.ajaxSettings.traditional),h="POST"));var i=this;d.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?d("
        ").append(c.replace(bB,"")).find(g):c)),e&&i.each(e,[c,b,a])}});return this},serialize:function(){return d.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?d.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bC.test(this.nodeName)||bw.test(this.type))}).map(function(a,b){var c=d(this).val();return c==null?null:d.isArray(c)?d.map(c,function(a,c){return{name:b.name,value:a.replace(bt,"\r\n")}}):{name:b.name,value:c.replace(bt,"\r\n")}}).get()}}),d.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){d.fn[b]=function(a){return this.bind(b,a)}}),d.each(["get","post"],function(a,c){d[c]=function(a,e,f,g){d.isFunction(e)&&(g=g||f,f=e,e=b);return d.ajax({type:c,url:a,data:e,success:f,dataType:g})}}),d.extend({getScript:function(a,c){return d.get(a,b,c,"script")},getJSON:function(a,b,c){return d.get(a,b,c,"json")},ajaxSetup:function(a,b){b?d.extend(!0,a,d.ajaxSettings,b):(b=a,a=d.extend(!0,d.ajaxSettings,b));for(var c in {context:1,url:1})c in b?a[c]=b[c]:c in d.ajaxSettings&&(a[c]=d.ajaxSettings[c]);return a},ajaxSettings:{url:bL,isLocal:bx.test(bM[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":d.parseJSON,"text xml":d.parseXML}},ajaxPrefilter:bO(bJ),ajaxTransport:bO(bK),ajax:function(a,c){function v(a,c,l,n){if(r!==2){r=2,p&&clearTimeout(p),o=b,m=n||"",u.readyState=a?4:0;var q,t,v,w=l?bR(e,u,l):b,x,y;if(a>=200&&a<300||a===304){if(e.ifModified){if(x=u.getResponseHeader("Last-Modified"))d.lastModified[k]=x;if(y=u.getResponseHeader("Etag"))d.etag[k]=y}if(a===304)c="notmodified",q=!0;else try{t=bS(e,w),c="success",q=!0}catch(z){c="parsererror",v=z}}else{v=c;if(!c||a)c="error",a<0&&(a=0)}u.status=a,u.statusText=c,q?h.resolveWith(f,[t,c,u]):h.rejectWith(f,[u,c,v]),u.statusCode(j),j=b,s&&g.trigger("ajax"+(q?"Success":"Error"),[u,e,q?t:v]),i.resolveWith(f,[u,c]),s&&(g.trigger("ajaxComplete",[u,e]),--d.active||d.event.trigger("ajaxStop"))}}typeof a==="object"&&(c=a,a=b),c=c||{};var e=d.ajaxSetup({},c),f=e.context||e,g=f!==e&&(f.nodeType||f instanceof d)?d(f):d.event,h=d.Deferred(),i=d._Deferred(),j=e.statusCode||{},k,l={},m,n,o,p,q,r=0,s,t,u={readyState:0,setRequestHeader:function(a,b){r||(l[a.toLowerCase().replace(bF,bG)]=b);return this},getAllResponseHeaders:function(){return r===2?m:null},getResponseHeader:function(a){var c;if(r===2){if(!n){n={};while(c=bv.exec(m))n[c[1].toLowerCase()]=c[2]}c=n[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){r||(e.mimeType=a);return this},abort:function(a){a=a||"abort",o&&o.abort(a),v(0,a);return this}};h.promise(u),u.success=u.done,u.error=u.fail,u.complete=i.done,u.statusCode=function(a){if(a){var b;if(r<2)for(b in a)j[b]=[j[b],a[b]];else b=a[u.status],u.then(b,b)}return this},e.url=((a||e.url)+"").replace(bu,"").replace(bz,bM[1]+"//"),e.dataTypes=d.trim(e.dataType||"*").toLowerCase().split(bD),e.crossDomain==null&&(q=bH.exec(e.url.toLowerCase()),e.crossDomain=q&&(q[1]!=bM[1]||q[2]!=bM[2]||(q[3]||(q[1]==="http:"?80:443))!=(bM[3]||(bM[1]==="http:"?80:443)))),e.data&&e.processData&&typeof e.data!=="string"&&(e.data=d.param(e.data,e.traditional)),bP(bJ,e,c,u);if(r===2)return!1;s=e.global,e.type=e.type.toUpperCase(),e.hasContent=!by.test(e.type),s&&d.active++===0&&d.event.trigger("ajaxStart");if(!e.hasContent){e.data&&(e.url+=(bA.test(e.url)?"&":"?")+e.data),k=e.url;if(e.cache===!1){var w=d.now(),x=e.url.replace(bE,"$1_="+w);e.url=x+(x===e.url?(bA.test(e.url)?"&":"?")+"_="+w:"")}}if(e.data&&e.hasContent&&e.contentType!==!1||c.contentType)l["Content-Type"]=e.contentType;e.ifModified&&(k=k||e.url,d.lastModified[k]&&(l["If-Modified-Since"]=d.lastModified[k]),d.etag[k]&&(l["If-None-Match"]=d.etag[k])),l.Accept=e.dataTypes[0]&&e.accepts[e.dataTypes[0]]?e.accepts[e.dataTypes[0]]+(e.dataTypes[0]!=="*"?", */*; q=0.01":""):e.accepts["*"];for(t in e.headers)u.setRequestHeader(t,e.headers[t]);if(e.beforeSend&&(e.beforeSend.call(f,u,e)===!1||r===2)){u.abort();return!1}for(t in {success:1,error:1,complete:1})u[t](e[t]);o=bP(bK,e,c,u);if(o){u.readyState=1,s&&g.trigger("ajaxSend",[u,e]),e.async&&e.timeout>0&&(p=setTimeout(function(){u.abort("timeout")},e.timeout));try{r=1,o.send(l,v)}catch(y){status<2?v(-1,y):d.error(y)}}else v(-1,"No Transport");return u},param:function(a,c){var e=[],f=function(a,b){b=d.isFunction(b)?b():b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=d.ajaxSettings.traditional);if(d.isArray(a)||a.jquery&&!d.isPlainObject(a))d.each(a,function(){f(this.name,this.value)});else for(var g in a)bQ(g,a[g],c,f);return e.join("&").replace(br,"+")}}),d.extend({active:0,lastModified:{},etag:{}});var bT=d.now(),bU=/(\=)\?(&|$)|\?\?/i;d.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return d.expando+"_"+bT++}}),d.ajaxPrefilter("json jsonp",function(b,c,e){var f=typeof b.data==="string";if(b.dataTypes[0]==="jsonp"||c.jsonpCallback||c.jsonp!=null||b.jsonp!==!1&&(bU.test(b.url)||f&&bU.test(b.data))){var g,h=b.jsonpCallback=d.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2",m=function(){a[h]=i,g&&d.isFunction(i)&&a[h](g[0])};b.jsonp!==!1&&(j=j.replace(bU,l),b.url===j&&(f&&(k=k.replace(bU,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},e.then(m,m),b.converters["script json"]=function(){g||d.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),d.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){d.globalEval(a);return a}}}),d.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),d.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var bV=d.now(),bW,bX;d.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&bZ()||b$()}:bZ,bX=d.ajaxSettings.xhr(),d.support.ajax=!!bX,d.support.cors=bX&&"withCredentials"in bX,bX=b,d.support.ajax&&d.ajaxTransport(function(a){if(!a.crossDomain||d.support.cors){var c;return{send:function(e,f){var g=a.xhr(),h,i;a.username?g.open(a.type,a.url,a.async,a.username,a.password):g.open(a.type,a.url,a.async);if(a.xhrFields)for(i in a.xhrFields)g[i]=a.xhrFields[i];a.mimeType&&g.overrideMimeType&&g.overrideMimeType(a.mimeType),!a.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(i in e)g.setRequestHeader(i,e[i])}catch(j){}g.send(a.hasContent&&a.data||null),c=function(e,i){var j,k,l,m,n;try{if(c&&(i||g.readyState===4)){c=b,h&&(g.onreadystatechange=d.noop,delete bW[h]);if(i)g.readyState!==4&&g.abort();else{j=g.status,l=g.getAllResponseHeaders(),m={},n=g.responseXML,n&&n.documentElement&&(m.xml=n),m.text=g.responseText;try{k=g.statusText}catch(o){k=""}j||!a.isLocal||a.crossDomain?j===1223&&(j=204):j=m.text?200:404}}}catch(p){i||f(-1,p)}m&&f(j,k,m,l)},a.async&&g.readyState!==4?(bW||(bW={},bY()),h=bV++,g.onreadystatechange=bW[h]=c):c()},abort:function(){c&&c(0,1)}}}});var b_={},ca=/^(?:toggle|show|hide)$/,cb=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cc,cd=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];d.fn.extend({show:function(a,b,c){var e,f;if(a||a===0)return this.animate(ce("show",3),a,b,c);for(var g=0,h=this.length;g=0;a--)c[a].elem===this&&(b&&c[a](!0),c.splice(a,1))}),b||this.dequeue();return this}}),d.each({slideDown:ce("show",1),slideUp:ce("hide",1),slideToggle:ce("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){d.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),d.extend({speed:function(a,b,c){var e=a&&typeof a==="object"?d.extend({},a):{complete:c||!c&&b||d.isFunction(a)&&a,duration:a,easing:c&&b||b&&!d.isFunction(b)&&b};e.duration=d.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in d.fx.speeds?d.fx.speeds[e.duration]:d.fx.speeds._default,e.old=e.complete,e.complete=function(){e.queue!==!1&&d(this).dequeue(),d.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig||(b.orig={})}}),d.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(d.fx.step[this.prop]||d.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=d.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function g(a){return e.step(a)}var e=this,f=d.fx;this.startTime=d.now(),this.start=a,this.end=b,this.unit=c||this.unit||(d.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,g.elem=this.elem,g()&&d.timers.push(g)&&!cc&&(cc=setInterval(f.tick,f.interval))},show:function(){this.options.orig[this.prop]=d.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),d(this.elem).show()},hide:function(){this.options.orig[this.prop]=d.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=d.now(),c=!0;if(a||b>=this.options.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),this.options.curAnim[this.prop]=!0;for(var e in this.options.curAnim)this.options.curAnim[e]!==!0&&(c=!1);if(c){if(this.options.overflow!=null&&!d.support.shrinkWrapBlocks){var f=this.elem,g=this.options;d.each(["","X","Y"],function(a,b){f.style["overflow"+b]=g.overflow[a]})}this.options.hide&&d(this.elem).hide();if(this.options.hide||this.options.show)for(var h in this.options.curAnim)d.style(this.elem,h,this.options.orig[h]);this.options.complete.call(this.elem)}return!1}var i=b-this.startTime;this.state=i/this.options.duration;var j=this.options.specialEasing&&this.options.specialEasing[this.prop],k=this.options.easing||(d.easing.swing?"swing":"linear");this.pos=d.easing[j||k](this.state,i,0,1,this.options.duration),this.now=this.start+(this.end-this.start)*this.pos,this.update();return!0}},d.extend(d.fx,{tick:function(){var a=d.timers;for(var b=0;b
        ";d.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),e=b.firstChild,f=e.firstChild,h=e.nextSibling.firstChild.firstChild,this.doesNotAddBorder=f.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,f.style.position="fixed",f.style.top="20px",this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15,f.style.position=f.style.top="",e.style.overflow="hidden",e.style.position="relative",this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),d.offset.initialize=d.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;d.offset.initialize(),d.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(d.css(a,"marginTop"))||0,c+=parseFloat(d.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var e=d.css(a,"position");e==="static"&&(a.style.position="relative");var f=d(a),g=f.offset(),h=d.css(a,"top"),i=d.css(a,"left"),j=(e==="absolute"||e==="fixed")&&d.inArray("auto",[h,i])>-1,k={},l={},m,n;j&&(l=f.position()),m=j?l.top:parseInt(h,10)||0,n=j?l.left:parseInt(i,10)||0,d.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):f.css(k)}},d.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),e=ch.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(d.css(a,"marginTop"))||0,c.left-=parseFloat(d.css(a,"marginLeft"))||0,e.top+=parseFloat(d.css(b[0],"borderTopWidth"))||0,e.left+=parseFloat(d.css(b[0],"borderLeftWidth"))||0;return{top:c.top-e.top,left:c.left-e.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&(!ch.test(a.nodeName)&&d.css(a,"position")==="static"))a=a.offsetParent;return a})}}),d.each(["Left","Top"],function(a,c){var e="scroll"+c;d.fn[e]=function(c){var f=this[0],g;if(!f)return null;if(c!==b)return this.each(function(){g=ci(this),g?g.scrollTo(a?d(g).scrollLeft():c,a?c:d(g).scrollTop()):this[e]=c});g=ci(f);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:d.support.boxModel&&g.document.documentElement[e]||g.document.body[e]:f[e]}}),d.each(["Height","Width"],function(a,c){var e=c.toLowerCase();d.fn["inner"+c]=function(){return this[0]?parseFloat(d.css(this[0],e,"padding")):null},d.fn["outer"+c]=function(a){return this[0]?parseFloat(d.css(this[0],e,a?"margin":"border")):null},d.fn[e]=function(a){var f=this[0];if(!f)return a==null?null:this;if(d.isFunction(a))return this.each(function(b){var c=d(this);c[e](a.call(this,b,c[e]()))});if(d.isWindow(f)){var g=f.document.documentElement["client"+c];return f.document.compatMode==="CSS1Compat"&&g||f.document.body["client"+c]||g}if(f.nodeType===9)return Math.max(f.documentElement["client"+c],f.body["scroll"+c],f.documentElement["scroll"+c],f.body["offset"+c],f.documentElement["offset"+c]);if(a===b){var h=d.css(f,e),i=parseFloat(h);return d.isNaN(i)?h:i}return this.css(e,typeof a==="string"?a:a+"px")}}),a.jQuery=a.$=d})(window);state-machine-1.2.0/examples/doc/_index.html0000644000175000017500000000635312305405267020344 0ustar boutilboutil Documentation by YARD 0.7.5

        Documentation by YARD 0.7.5

        Alphabetic Index

        Namespace Listing A-Z

        state-machine-1.2.0/examples/doc/Car_state.png0000644000175000017500000016333312305405267020625 0ustar boutilboutilPNG  IHDRCktwbKGD IDATxw\'0DD 8_Wqh([*b:h{P:E()a H_S.G_r9F>cr-$h- <@K&В$h- <a0 CvJM%AP"r\.7p}@e/ <y 6n&&& `ؤ1BOOhʔ)/^$j=S@xb{{{mmm''Yf5@('O%'';w}9rdii)!p8BHqqܹs7lؐ_I.]cryjj*!ԩS;v͝7o˗u떗g <ڱcGuuu@@@~fϞݻwﺺTVV.]ٙ.[rڵyƍYYY7o6lܹsd((BkNfLLL!ϟ? !5&M"-ЎXcǣn~KխDZZZOKKK mҦM׏YUUd'Mwx3TJa2Hbe>BvF()SSSBHIIbMvvvښ({f&zIV9qDvE]̧.BFM9{bK.YXX,Xi٠()___&wWTT߿~OY._, !˗/ +--={o~~~߾}i`;%t/^ |)5jTPPdoL>͛7׭[w}##BP( ~zFFurrZlÛ|Z### \m0@IN2%--M&66}<ZhJf͞=Ν;" .شiN]?믿 :{ڵt:|xԩǏXN:ؘbD"J?[\\PPPַoqyzz}QD"9p/q 2dv>y](6lM<dk׮ϟ8qW_}F=(w~[lqpph 0w=*((1b?#G7B444&LQZZڵkW??F ŋڶm{ggF߿\.W\ٹsyݻ?쳉'>|)j>>vPXzfӛM~Ǐ}!7Izz}Kz۠=z8ѣB!_~iZMf>>>O>;KHHѣ۷Owxx@ʣG~tGݻvz1ʕ+մOt OO~M"Qի{622;?AYYϗH$ť"(;;}t4yZ]vm̙t ٳ0 LVWWw]OPFІAB/^`0jkkkjj$I?3@}hy1޽;A!sl >#ӧ۷ק;[hQ]]555D:&@gϞҝcƌ177e%8p á%3Ц?E,gdddeeI$ZH$ x<ޞYXXXwb-X`RT`9gMQ hN ѣǎ;޽YMMݻw߿_WW`0,--x<ɬ,+++..NIIIKK+++#<*++k׮]MMM666upM6BPGGm?9C"""bqǎ̙ӽ{wzEJJJLLLddի+**LMM'NիW[L:رcZcaaTpM6ݻw5jƍ_YÇ8q|СSLСCT*_r㩩NNN3gΜ:ubBBB=e3{Q 3Љ`ywww5k]|˫j>⢭ݜa0mvݷo_ðӧ{zzڵᆪV;HF&@7o޼yh֭<+POMM2d!… EEEtAhn2, ` X`50119}{Ew"<@*((4h͛8s}$$$ 8pVJt'Bp<@sz~q-ggg㼇!رc...~~~ɡthr ۷ǎۭ[?C__8͝;wƎx…@`t=@s 2dȈ#]p>|XXXꚛKwV ܅ ͘1cϞ=lv 5''gذa;wmۖ8<@Ӻsΰaf͚sNAwU\\r4G 4hҤISO)((ׯ͛7@~C,  pttx"á;NczGhh(<44y]]]Lfddq_ttW^wѝughr9j<… |5ү_m۶_~, 4ǏϘ1G;KӚ6mڍ7ڵkGwTSuu5MLLtrrР ŋkkk;99͚5QUU044LMMOuuu |{>x𠫫ϥK_ 3 jێJΞ=[k>>thET:!rѢEÇ߶m!ԩS;v͝7o˗u떗xKyy)S|||/_>dȐRݺuY:wWYY9jԨ7n|Ze˖n߾ ͡M6G9wܹsj@EBX,VPPbͷ~K9|b?!dB;v2}te999 &ێJ\p`ܺu jڵthp<ʢ?{ʊZͩ5ɎvvviiixM MtYŚK.YXX,XfW^U,GDDB Dե^R R sbx֭}I-,,p<@SChE/_nkkVZZzY__}*aXǏzjeeeLL7|Kt۷|رcԝ 8zhYYŋB'55%K>|X1 4]$  _r-[6|p[X,VAA ߿_QQѫW 6Ӈ ??۶m'O7n\pp׬Yd2+**^@paЬ}k֬iw44y7@;;G@eF& @C=\$Q3֊D"Bg#6yeެ޸6r߱+w-((D&O|`&'b/^ J3Eouuu---MMM .fL}' `zAHtttmmmpM@$%%={,\\Y۶mMLLz ۴id\bmFԠr@7J$PwlSSSSUUb2zzzЀs\MMM]]]MMMmmmjABc4h)K>}tuuO,?}411ӧO˥^Ոg"8 J=88X.F&LcǎtP.Tkkk|BiiiYYYYYY^^^bbb+S___Q_),4VI&w</--*M˗/;wʕ+]v9r;pN"8 ^ WcoMM/_6֭݃[r6eggIw &yyygϞpBTT!=00pرtGBl!^j+V2_CC߶m&:/6ўG۶mSSSNҳgϞ8q"**9ѣ#Fӣ;4կO\.bCn߾}6>jԨ'&%%999Q0 GUٳ'##C[[{ԨQGO;@ r ϷvqqOp??;v4r+MΟ?ĉW2ѣG9sO?Р;4 333333ŚlG]hQtt4!dٓ'O{n߾}ObWߺugΜI]_xQ]6twY(9rW^.]z𡹹9!$%%e&&&W\qpp}g}d2e2Yϋ&DtP^r_}kkkSTTtǣP($4x.\ |;w޾}7?6l`hhhhhaBݻ}7feem޼yذa>>>s-** 6 ӧ5pڴi_ <@Ax@;;;77صk]r/lD"!DKK{ԩ!dϟ?'UVV8  !|> ??bddxKcS]]M@B [YYM4`:u`ΝtGE!Oo''H???Le˖.]}}}\nxx@ xo0L*W˥R)!yuuu >*++?fx4yhr7&L`eek׮3f_vmҤIދC#og2nnn6mJJJ:xT*o޸ !vrrrzŇښ}ڷoO)--U<4y&&Tee/0lذ'NԽjO4(&&z}=!bBjkk !Y{BCCo4zhBٳgk.]daa`ꥋ !$**J{@hMMZ'O|k֬7n\ff'Mp- USA\RRjժD`B'~B._L}Y0~xB_|yرpjwb嶶aaagϞ۷/%Kl5k*++ϝ;R(//lMx:% ߲eիWϟ?gCCCC@ &uuuٳg7x'!!!"XZZΘ1cҥTyu߿oddTPPm۶'O֎7.88xk֬a2>ʃBa@@322\ӲeˆzD}}}gg[nEEE555scȐ!VVVkAd~m<t҅PRx<7xP(,((xtPAOTX,޶mͪU|xhtho&ɃٳgϦM)ݡ@e9::fgg Bt xиpMTGUUUPPw}7gΜ`xhRJtQ:7o0`&'h h bqPP?0,KظK.tQ.r<<<|РAtPM]-[uuۃkjj/^#<<%55à MZ_u֭@#ww;wiӆ,֭[\.W^tPM]-ޭ[UV?>99cFVWW?s AHhh#l\8hh:th޽MMMijL@ 1LOOНf7nʚ6mATC.ӝ d2پ}6lPQQ~yq8Cϭ7o޼`UWWիtPe&ѣG7oސ!C>}h"xPr^^^7o; mnݺzj84yP.eee>>>{fXqqqh߾=ݡO&M6p̙tǡƍ0T* ߱cǘ1cNVb8------%%%%%%)))33ߦM| 36(ww?sȑtgPqh޽;UViiiѝ/^H[BBBrrrQQ\.g0l6NK9sN<hŋe˖;vlaaat'N:԰X,%H].K$jfwx~iZssoߞwe &ӯ_冄`8=(+Vl۶mp8!C;wDlbbzo믿V\vZ h@'%%}7NJҲ?USS0`˗O۹u떇Ǒ#GOތ1!njCMIwVsCsZ|8NLL?j<(9==5kְXh̙Ќ1aÆ7n:u 5٠Cڵ޽{CBB{Aw"fff`_֡C7npW޲aɓ'WTT4cvu͛7НAfɓ'{xx|'L& ""O>ӧOҥKl6֭[FFFf=zdɒ%͕|r֬YF; @*ȑ#ݻp‘#GLLLN~wׯURRҽ{쨫L&SSSo{Ç:܌X,?~ڡC^M MV^^ѣ>쳤ѣGӝ222ƌ3`&yݰ0+#EGGwgчZb޽{'y󨩩3fLVV͛7۴iCwVϓ"׶m[nѝ <رcϿ3>>͌3͛g``0qĦIݬd2̙3⢢:vHwMDzz9s/_vZNJP[s+**]]ݐYfٯ`0~Ǭc~>_p8F%$J͛wً/2q4}ɓPMvvnllr{Ff0NNNYZZ\tI1`^,ڵkԩ:t9s3gKMM-==O?155,))iΓCIэ3ѣG...h'BpgϞ3gNTT9݉B.\`0Fs-ZoQ/=z޿%!b)߿V~~>2''2a꥾>!z+FpҶm&>RTT_s80LFojBȘ1c Fϟرٳg롁g̘5- ,Y蘕uǏ(!4yoJKKǏdɒK޿ɉDLp8rp@JtrrJNNd[lҥɓ'?ǑOvrrZpӳV\Mw7PSSKIIqqq_)//׷SNG]n]BB A9| ʕ+_~%=u" R8::&''oRcNhkk٩S'BϩoСC3gδn}oiQQQ+W>}z``esc;vm۶>>>ӦMkIryttÇ=jhhtRooo&=(|$\"H-Z4rH8xdȑHфR Խ&M222RLV"*ɓCkii9rxB333Ok׮;4ō{ \vN:dgg/[ 5@egg߾}:y$3в,Y]vWNHHHOO_fMmm9s޶ !۷océ!%%%VzH$ &xzz6ټUAA_|ѣGׯ߼yŅP daa?ܸqcǎK,111ҥ… ϝ;'ݻwO2)44+33Ν;gV0#44<44 `山K.UtGo۶ɓƍ ޿5kLfEE!$$$d߾}YYYKK3f,]F`ӦM;w455ݴiӄ >djD$]r͛iii,Ύ1ʲ└"ӷo_aÆ}rhVUUUs=z(5 @TWWo߾}l6;00˫}*)//T9OKK~[x<=֭[~s?phf|>„ [l?>qE*߿Æ `oWd2YaaaMMMYYYmmH$ hkkiӆtet֭?M6wѣqׯOKK5kֺu['&GjS;dUVyxxL2%>>5ʣG 2eݻw?===cbb92}t#33o=s挻{=&/66vҤIjjjݻw;{u|9qD>…#G3gάY/\pʕtP.]zݼyo߾ݻ """9pM6Mھ}# ]BBСC=<<wmnnNw(%&TUU}˖-۰aorNڳg&OܫW{uܙPJ ɷ.999}_u#FZ/^YСC<իɷ"'O666-q;v ܻwәLPl-v1lذ~ƢƷD ؈R={6mڴrʌ/5?ꫭ^xիwt9}ӢEMrJ---Ck,2x"@|//={Dw9sOH!d2Y~~D")++Dr`p8mmm===CCCzC>x`ܸqڵKHH0337 |<ʹZV^צM77`[[⚚BzN.^x{ <8$$XD'''y<^jj*!D _r[[[&&&54-EQQڵk<舩몶wȈx]_[[уUWWؼԩS3g:t'ttt7'O044K.O>tPlll]]r={ܲe :x+z ] #G+44400P.?|ܜSoТ⠠[m޼y„ ɿUiiiDD͛7X,c2ՕeeeŊ_SRR8N߾}=<<<<<>(!CBBbbbڶmXXկܳg޿7M,]~"Ν;v,!}fffM~Ç j,Xs<_hR- Z(THjժ jjj @ՠɿJ*^zСCϟJzrwwwss0`nu֭gϞ͘1cYZZ ®]޹sGGGG"̝;ȑ#?_1;eSreddXZZR?}𡋋 w?%5 Z*l&)J;kLL W\ONNvttKKKkЂ>:}u벳/^9?|~HHȱcǞ?6cƌ &5QҎ?~رgϞΜ9/PWWo.\믿J$55޽{={vڴi> 6lXկܺ"HRWlӦMYYGԬj&>ʼn >}z``fh ht1yM6CMzD\}G.]?О@]b%lsQfAԯUUUbXCCiyy>&oddTRR" q۳hoвdggZ̙3nnn?#~h}>ٹs;wڵ+33oi_s ! { H$??3ե:}ŚFٳ5!Z-ŋ/;wrڵ[nzD"ټysΝN<2k,1̶lْ~ݻGDD]o>w+dGMEϞ= !ъ5'Nhخ;FM9{bK.YXX,Xi7hxt+W4AcP7Pr.\pttd_5Q.;;{4iqJ,/]t .ܼys1|>q굵C ?QvTRIIEnn.Y*,>>ƍ&M &Պz%J=z̿B455wu(~c+pd2۶m~k׮75^N2cccC+o={6ydgg窪谰0xڵ&e˖'N<޽{666t <D}'_^190`S|ʕ+۵kGkdh&l6;,,lwDo^pƦM4P7PB`ѢE=:uԝ;w>C!y2l…v 7oq'FEE9sƍ fSG8;;ӝN. 2@ d2YuuX,BD"QmmD" rLFLNNDGGm۶{677ZZZ\.ijjjhh驫hkkcs Jo#F;z7ofX6lJEśT*>|޽{@555SN?Ri]]… gΜi``@w4x₂b)J`0 !Tf\.K=7uuuuDb \a=FFF֐?hvҥSN78@d2پ}6nX\\hѢ+W %MСCN;v,Y>T*1w=4DR\\ϟ+{QQQaaaqqqaaayybcuuu}}}z/_Y`X&J. bqeeeeeX, bX$QWE"QIIIqq/JJJ^|˺:tuu ܼCKL_ IDAT:ti:U|7oÇ/^8tPj~~~qqqg^f݉TɯZj۶m/_4hY>T*-,,ܲe˞={]ֿ>LVXX_\jŋ455MMM۵kglllfffbbҶm[333ccvڙjjjxB |RyyyWUUyzoaaannnaaAYU|,^o4zoܸ1hРW^t'wQ&u֕+W9s>;KdW^֭qTDee%ҩsrr|~~~D"!0vYYYY۷UhBHIII^^GjmvرSN:uԩ [(|@#zE@@ݻ &`S&̙)SK.;K#Æ hAUQQQVVVfff233_|Iv‚L{9UBtttbh:u`2[c5oXi~55kΝ;)-\NWPPPPPGG2O.S$ןdkܜzkjjjrss={RiEET*TfbX);vС:ݧf*333{5cƌ42ggN:]r獤R)WtuEop8ةS'KK:P3A3J Duuu+++꺽}ΝNB*lذom)ZaʞMMMMNNNIIɑdTرj_|+VܸqcҤIt'zҥ۷o8wNNNVVV6mAMMMrrrbbbZZZTTTllD"uwwwss;v^$b844?~X,ӧȑ#]\\\]]U@p޽6lظqLBWW&-GN6,MN.;6999))/HQQQJJJzzzZZ#J xB+Bjb{?}4++K"?;})AzTDQGwbE,abA<bl`z)'JґHB -c% ̳a<;rHFF ޲O铉 ޲tT* 5m``I!.L&3-- F JNNNJJI a oxB@ `aZZZ6mڴfb ^^z5$$$993---466>{ MMM'MDLE#""mۖ;eʔUV999 KI'NxBOOwuu퇇bXYYYÆ ۿu_aX %**JuhUUr fP!OJKKSRR?"""bllÆ >~pF@ v֭'CEJJӧÛΝb ;;; ׮] ILL444^p!/?7mڔ4۷-Qdggڵ#Gܿ_ gBBBrrP,>X[[?~ oY_t:fC]&ABss3W쓓ʖ#EbMnϞ=xK4HyŞ={޾}lٲŋ+))-CHHHDDDZ`-[E"--mo޼;wݻe^jjݻ7iҤǏQG455 ?>޲O?TVVG\zȀ)))EEEiii333h"!/?>}JLLCQTAAм}GMMM|xرxK4H޶m[ttԩSW\4VUU-[lӦM.KԴo߾@ssӧO[YY-Q񣷷wzzM6m$"".%hѢwޥx-pSL뾾Y즦+%+++_i4Lzgiiiii9j(mr#G~ԩSZ?SVV tqq!|RpիW۶m{֭[ D ź}M oNϟX&88x«P={|HHg2Bo,^oY%**&%%%$$UTT,--1]GGGFF]aTWWGR-JLLkSfz𡄄ġC9k! kںs+VWJJJ|}}ᆈ={ xԩ444ڵW#,kϞ=w:uSF:Ἴ+V|rݛ7o W`el ŋ)S2juu'6>&%%ell v333ccc---~IN@ b[񓓓륤K Y,NH$K.u /F@0a֮]o-`ԩS[nUPP?f̘DA]]]PPP``ahh1c@Щ\`˗/V^8|&888 `ʔ)aaamɷ\rx˂?vvv'OU{~~>@SSejj%ZxZK[[[fff||Ǐ?|Ң1fkkѣG[ZZJHHRRR7[R(k׮v:~# v^rqqٻw> R޿|;w_~&LIIɚ5k޽DʻwEEEoݺ5b]\\###ǍےѬY%b@``CJKK9/UVV{.660^6kȑl<a2>|clllAAB133nii|rkk+{}2q{{{$Q vƍ;|0ԉ[n >}) ((^rϏL&_xqڴixKD p7oޜ9sf/[nK۷o$DKK͛siW_>ΨQ܄ zÇ>|)..ngd2ZdIPP XX;~ o/OppҥK\zj@@={yk׮-\>w`ֶhѢׯ͛77M %`0=l2e&N:[[[ h*@KPTT,//杖BȜ9sfҤI~kϩe0MMM L&EQA( NGDMMMGGGWW%zAˋō#ׯ__|ɍ7FD~K,щ[  |UQQQQQ ?QeX%%%{{{??_^lmm][[=T8#H"""X(*z E ~[QSSsʕC  2a֭Ȁkݥ*..ŋÇ}W^S>0k֬ 455-pS__?o<W- n\rA-۷D"x'Ėq@@RZZZw[ ZzG\r mmm%$$JJJ!\rvv.'h FFFÇG?`R󢢢ZZZcǎ7lpȡCX o B /&&&&&6s3gΤ]WWsN ի߼y#&}ee5kT՛7o-//{=޲ X,֎;ٱc޲gϨTm6mDR?ރkuw}IIgϦN,WAAׯ?lhhϔB7܎Y,LRw|XIIѣDX;Ap鵵O>%b'NXرc 0>}4agg . @Qt…wލ>|x/F"##) #.\knn[>>555& hb?~Z^^JbZr^xpܹ+_qlW3qD&!!/ ,͛'N;׭[Gi4ҥKSSSque !C=zؕ# |MOOOOO/''oY /_R˗/[~JKKήg.TF[[[mmnַ_)))zzzx "p1d-Azcwڵzj))?6F\\I@@@iiiIMBu77oޜ;woY/^5CAA͛7)))o==#GbEEE8■-Z4gΜ\__ߎZ9sfkkktt. @~k׮={vp<x{{755ݺuō!&&vzn](|ffRwgHo400 H|@0ZJJJիsss i￯_^WW/w30~R+tY4߈cggga?񎎎SNIKK0aB$$$.]ӧȿ߿%勛x\\!//bt駟~*..믿TTTg}'N- Ar̙۷o߸ql+WnܸqyޯbK&`GBBB]]x B< =!C[[Ԇ wW^@跮p\2=U<)--uss=z4>|400[Adܹ)))ǏqㆮC8J>R[[qFyfĈ5WX,… ?kQ^dɒ]v]ݻxBW|yݺuׯڵk}}}b= ͙6qq .H[аsNQFa괏hkk UUU>z(FٱcJ} ނ X,Tbb"޲ecccnn؈,KCCɄ x/@ӵ2loo_~NRA佽9ThkkaOEF ,@KUKKKFF!66P\\a555%K0`&%%rQ-,,醆uuuvS\Y\[wY`׷pʔ)`Ybbbqqq?~Xn`X)w+V6H\\\ddd7JرCxj999 e͚5}---d5kCӧOI$ҙ3gdp2yd]]je!37n H%"¾!H 姟~lhhXn9HJJA/~8qD:9? 7n9rdE>}WTT M$zCo[W0uNSN2dٛutt_xqMM T5A fffJJJΛ7oÇTTTd/Ś;K|r佋>ɓ' `LL ޲փ;ׯ|l977w̙''t>] OPVV&//#O}2|!'jjj-[_UWWZ~-;wl]-y 2L"T˗o&!!ۃ^xYـ^^^?EQ!\II/$$_~~5LS__󊊊'O*+++))颢 BG IDAT0yɽ{볟_f \TT’Fus8XRRR&Lc0ׯ`ݓbbbϞ=kccCP>P;^bii `@UU< $]‹%g{}ǁd;}.$''ꪩ}UWWYFDDŋot˒3g»7nD*Px)ݻx B7Kxt&(..hZN?z( d2L&?7vB;KfÖE.--USSXbd* `ccCeeett4YaرEEELzzCkbbQXX+_v9y򤟟 G)ϷYGx|r@hh(ނ'OXu QPp9;~Q(|dgghiifGz:ҥKVc,+&&ɓ'=JKK#!Wx}`466JHHHII1zo)++>}zqqqDD;cbvرw:t>}痝cYY>+p==[CzCCC''3g-ˠd֬YǪ]inn%K} (,,[`hhhlݺߟS=vd\={/簲b?nmm-//wK/ggΝcKFFSa'};"lF [ǶgS`3Wg3TeeeKzϵk( dŋ---xەr> qxiii%CIRx$550sLvH{6oL+g\tL&7j ەNfUUU>֯_¡Ngbb= eذa})jw=zwرٳ࿦x Od07;v8::&''CȮnMMM999v'x!-F?гCQ̙3ґNNNQN;B' 4?)*s3횭*3gt>6"''߆<<|JB'5_ʨ̠>  ,]#ٚ '<{LLLݝe˖QT33ܤM$b%_~g$6++3f-'h N7n䂂>d[8i?>~(--  t:f: k{ e+б[dINgDEE'O ݣ( 7fffv%A{{lihhϕ/_0a %O@ hRu Jsx񌎎TXibbb/UY7իWRwڱ<|ǸY MLL4i|5N&mmm={555 N{)ЍȈ?>x'+ߎ@YYYPk)|HK|}}ՙL&b`<{ }D 555ʘfxK!̜9snNPUUՃ}۾}ʏ?VWW_r%<ׯ_*:t;v;YXXxQsss^DD ^NaW)\u/;qqquzڰaC/UYtz-,,$%%%%%MMM5X/_R***7gggYYYIIIGGϟ?àЮ|YY-##{555fff2=z$""iӦE<zÏ?h4ZPP2bYZZ(rrrOt|Q:V67lؠdLѠ#='E t!|Q[[?|p3ˣlmm}ӧO\իW/>}tz! z:szfɣ(ѣGCExb A>FFF{zjLLLIIIWb/\PZZsv޽L&?x۷o , HÆ {%rmmmQQQps5X$h|g |ﰲZv-R7ZiGQ@KK ***j``0eʔe˖۷ի޽x+Ƃuitm|X,,N?|p_D"މ` Ś5kxN+=Ǐ ʕ+x 00 a0v\144[ AEL@Fgwww,xÇ#G֭[WPPRUU3bĈ?~ܾ}{666ZYYmܸ̙3/_LKKkllH8q$۷oΆ.dիWǍi& իW^Ԟٳٳg ;AӧOwss'O}N+kkkcccuZ)///--577ǥyQQQeee'eRL<KHa2Ν[^A跎~OII~ppիMH$--I&-[oNJJQZS7oDYt*L=K~ӃTOO/00۷ogϞҧOQmhhP(N8`̚5+00kͨ(@QQ/ڵ{(((}cD7ݻԩS0$**jĉyyyxB@@sAo?~KKKKOŌ,222:S,k?[ׯ6mڲe ޲9\.׉%lOO>urrk!l-;wŌ3wK?|βe6n܈ J555tuuUPP[[ZZx 7z踸8Lxߋ?mmmT*֭[skz֭[MLLEPx򥖖Ns :"c۷ol zzzADFF]|oAիW>|G5P<vFMouu5bAZ444puf),,Jx(۷oVZ5| b466^t۷111 e„ 3fpqq*TWWܹٳgEEE:::Fڿ?ۼyWL ܰ͛7{˗/G޿YTTTWWѣGk׮}=JA&)..wΝggg3LЀ!p>(ǯZj̘1"Bjjj$%%񩩩|A a?*[III{{'NzYpxLhPOC-cjkk7ˑb3+A [;NQT}}}}}}y***FFFFFFZZZ0$NII=!|pp9sFG*'NtҺu{4550&`0 Fcc#lwxہ  -I"k,2̋(:+X, R[[{պ˗/}`ɒ%nnn5ʪ.,,2""bϞ=O|Ν;9"!!cgdd?^IIٳg&&&o߾={6D=yrrn޼TUUs 6xyy=zv3f矎W[[;uԕ+W+**r(֯_?q_uΜ9RRR^^O(q͜L&{{{TWWK.ƍ57߿]8tʕ+uttpbd2KJJRRR޽{Cmڴi...fff222{#RRRrrr:::Ɩ'N2e -[iӦGEFFFFF>~ŋ/^x}||||||fffnnnnnnEEEUUUUUU?׹{nWb8Yf۷oᾀ'O8|QVV333%%%͛7o |2VΝUVC___;waBB-<ܰaog֬Y]ܿB',,q%K.].?~x&/?;mmm%$$JJJ!tuvv'Q_~D\-R*J"DDDf̘ehB;Çaii)@QQci>&&&;wx^نϟ?wtt[Yx? |o 6\\\xW)$$$xiѣЎ?~`` iق w{222LMM=zԩ6_hll,//kyy9o߾UVVB;&/rrr4MRRFt:hXFΝ;gDDDZZZh4,X0vXᥢBAAիW&Mq++m۶)+++;vllllQQpdff ~Z#\x&//OWWwذaIII߭pq[[B!Hk$##x0 >>~ԨQX \o,L&SfLC"?}׭$i4|nn./;ڭê*nW8#`mmmkk{vے5ĄkhhPSSۺu+(`@7(eeeeeeeffffffeeujO4 nP(EW\k׮544;|>=F;*{FSSSqqqqqqaa!PZZZ^^_ϡeddE3nDܺu **BPOO?IJJ׬Ϟ=:u*fff"bff6uŋRYYY)pd}}=_CkjjG^RRRZZn4hhh 0W!Cֲ PUU 'OzjNNNMM YkzaƼhѢBB#իʺ*inn2dȼy`+Z] ه@},,,f͚gϞvOtիWݻ`ܹsL&:Y !AMMMMMMRԔ/^8vX]] ZZZFFFа766666MNNƮ!!!.]ڼyugwܡR{,Y_>w1%%%_|Y2Nq555sssyyyyyy%%%hSԾ!aQ(ɓ'ϛ7_Wp{111O} >~`0m!d=勛]RRRcc_w!C޿֭>}İaBCCKKKGgϞ? :::<<|޽NNN03A^h544Z)򱱱>YXx3gn߾䉆ƪU!|a\\1c! s/_L‹lƈނ@ppF,VVVtR*cǎ~H$h~e8xVST111YDDDRRAbccW֮]]Ǐ[XXhXimmi4g//))ёv{iEEŒ%K%$$+++RFQQQZZZ߾}-x= xbhE  oذAMMMAAaɒ% #88FIIIu*?/ >|˗/<R˗666T*UEE%Gn),,X$!#FPWWgߏ4勉ĉ¸! BEEE @Rǎ$@{ne 8NgqqqEL&FXv&&&9r$(**nݺu͚5x B/^񩭭iepRYY)//]rMcMAQN8poYz ɓ'mVUUձh,ӧOO0޽{g[~SPPHNN_vmWq  Eݞ'dEabA$%%O8pATTT6mڄ E- #Gs=EQ499y…: 077gO@ 3da{h3{IMMݽ{!<0^fff/D bnn Fss˺/̛7OWWoAׯ_#r޽PYY@~дKNN633khh(!!aff7ENRڵDRڮkhh8uꔇ… o޼8 N}qĉt:][[۷oUH;}4lJv ۷oAߊΝ ڵk a:VB@455={kͼ<wxo<''gĉ4MBB W|Yf͘1c𖂠hhhڵ o)}lmm߾}[WW+&&T&&&[?wY+mAgŊǏ[n#qVZi,^oA ,At:]MM-44/A%%%{{{??ؗ+**Npvf˗/cgv o߿8uV6 k;-++Ev+%K.]b+}ݻw W\a?ŋ1BΓd[[~Sp9pF.G1v+$&&~y߮09:#@mFu;5U&UII .`pA7nKLL[>d׮]qqq]U(..^lB177ewP h(())`ǏWPP vgii kG6H@O1o8ܮ iǏtk= {X+WH$N#F5_wwk S)A9F%L&N߿oA:lҥoFu;5U&#G‹ѣ*)fff0'#**B>|ke˖H$U N02~~~BA|}}ycw0avg 8dy}}}WGZMMwG3D"YK~yfǕv<}E_.]:e+Vl7ۭ'#(Ǐ/^\SS~DF%pႵ+?~ u&+0˗/y{WS6lؠ)!99_|/ @R333MFє͛bc*lE߿/YXBBp0{QQ eƍ3;vW̺SN2d bl߾]SS X, ={tUAnW|EUUQ|3Bjj;SRR\]]III=/4xWWWǡS}P=x zmSSSLrms=o1Hkll 2d{o2v˗/w,OP_@ ]\\uI; Iw ¤$11155g}.רxyyUTTTTTxzzϮVAn߾p09BѣG}>A K#111{{?~[`S!W[TRR۷t:EQSSST{ܸq;;;SSS(Ԅ   .W#>jHHHQVTT5kNffS?~laaAR}||W>3339\eeeenn#,,,xx"@KKwa0+:52ҘLf`` kkksss  eĉ\MA.\Е1ޟ;iӦu7"),fܓN/^ `gX#\JHH`sW󈤤 {MEg̘wF#A W^yQG>EEER^>V\lذ>ٳϏBz """~~~T* &a#%%%UUUEmmmk׮~gױCuerjjjcE˗/RT*U__=~;w\;cBQ(77neM x sΖxBxyy U~~~W yb=x`Ĉ$\/騏#""*++oݺ Hxx8VJTTTWWK# uuupիy}S(]]݄q=+++׽cތH lٲ/JKKz?۷o_pƬ3 ~aٲeݺ~=IwRRRhB*++b?~Z^^JboGt3g{LE' [^HLL$H7n.:**wlEZZHKK(d2i4[ٶmQ1bĶmPݺu+Fc2\o/`0C ABpp!C8pKEѿKZZz=^ƝEEEJ~ Çbw>F)++;::v3iy?naa!)))))ijjȾa XTToߔ{=i_FՕ}ЎG&''w^~;rd8YYbElll@7ݻu )lCܓ)%$$#TWW75*|5蘜\__Esb䈈^聨= ##J>}O{! &X[[swV]]^lav`0222pƌouYEQ3frS}Ċ+8"tLYo[(O0cu۷222'O{---.\֦R-`ftktm˖-pcD?`bbr/_U䁏ӻuIw-yv=RGGA@OO%k-y8E 6tAE>}D"ad333_zuҥ]vzzz]ޯʗN{[r YYY,)Jyy(?)**ZYYIR?M/_D=H'W"""III\mPX(~yСC|uA*:o޼> R|y4gΜ~X" dgg"bgg BojjaÇ˖-X[[&=nY㞐d{]\\IkTMMMvQx9gs\OG;L}5u!@@f–%(H⨈U{+nVYDV8Qq* doFHIϋk'IrƕhuUHD" %...Fņ`D"Bi4)@1mڏ[BML>|!j*fVVB%ׯ_io+@oa-FFFMf͚5{vL^*ɓ'H 6 (5tPUUզw;ZLLw}rrr W yb͛GPMM։ڌ3fR۷~/lذֶU*oBׯ_oڣیGDkkd6} 77jUK"֬YSYY1vXJJJl-k[{`3 =ٳg @h ڨžy[Gmy[VjkkLAll,}!`2MGٳG{,--uuu{8?{O[dee:tr婩ر.ԵmݺUMMitL^*J$ݻwS(QF}f>033å{͛ B'22Rыi96oެaddtر[3_>yd^ڎGL&c[_Lݓ{S(CC^...XŁ !D d;4_xL&SWWwΜ9gjjj_"##)JM#"".\(L{O;w5 H'OLPhS]XXC m=Q[VQ|TZUU5g+++*ګW.\jڑ;k,Pӹ;}ӱ!Nt:ɓMQYn<>%%%4mƍ-ٹ{egϞijj}{!D"\\\VZu޽Y4#&&H$={U\$-ܿft///Ј#:*՗444L>]SSSIIiժUrX fDΜ |rϷGر]`0F$;{{իWEl߾!.={mmmuf+++\PK.YfبԧO_~%<<%pX,k3 vORg̘ \"khh`8w^qqqtt-[|}}B 111ݶq'N,YdX{qoo[vٲeyyyoWI~B7Zۊٲe˓'O<==ׯ_?dȐNtCCÉ'vY^^. {}UlE9$Hnܸdɒ~Kllׯ_~M鶶{077WVVg2|Dw޽~۷YYY"H[[o߾}uuuuuumxCC6|W t+B-o <|pͱNNNSNņtijgϞ?̝;wʕÆ VUU KT*}n߾mkkdw\t8,Ǫ\r…?7b%H 222rrrrsssssB:::Xfhh`0 uuuuttzѣGΌVWWWVVVvyy9w-]T@gBBB~Z~dGrrrDD+++3l0;;v9@ QTTTzzzN<z>pdM'Xk6fg0XEO>]xl M<ܺu ӵk>zK|HHRiiiEEEEElC60MUU{K*7[TT$TUUmmmmlllmm{emmmϟ?=zt>}\tysFcƌݻktz >~x`` akDܼ}m۶۷oS(Ʀ}e k$5ӄ]J(VVVXlH=,,,lmmMMM?[#9͛]3ݼysҤI}dd7rD2fۧaLmjjJR1jll,***(((((/OQQbYޒg͛6m|q C?~~~[lYzukL9 !!OLLᘚboMMM MMMMMM"(jjj***9-N:t(sK=`Nˆ;H$Dx={43eH~<}?r办ܹs/^,c 2Ppx<^CCCmmD"!4MYYYSSSEEEUUU[[wy4: (-[;víAS?2dHVVgK$$$lٲd2y0kll2򊊊X+6Q%ZZZ؆&Mi4Zhllrl6k6ʰ>`;c麹lLz322FIR]&Uٳg̙3a„3|@PXXXTTeM|>g[Ӱ!3eeeuuuUUη_'H$efffjjjddim^-$JO>z pp4l0__ӧO.d-%233322233jjjTJ At:5 ª#F>V`EVezFFƟ9s lll++--}eNNNӯҖACCCִlbbbjjdcc#o8qb׮]s]jl>߀bի׭[d@K̚5͛7ɓM6ݿ@ +55:?|ÇouuummGR(ART6%Wd|l+N&C QmXZZy);vlvvS|}}G! ݻw+7HUU 5#{aSUUUoMRWWm Ba_!TۓFF]aؠb ?˗/o޼w88xk ]bGdұcǦMW`pCCC8~x9opUeee/_|errrII BHOOD0L }C`"NŀUƚi,K$h}:;;;;;ήxݻwsXd)??_SS@ |u<Έ#( <;h cc#G̞=;?{lӦMW^ؚ'Je=éx y***4 ~6TTTe D[nݲe˴iӎ=jWSS-ڵx}zΝw}V*`96l=˗߿D#F9rn{]Kܹ eʕ<ݹ$&&nb) jl$61JWW$>>~׮]+V)X{gl6[Χ/~3LMM/^#[ܹssttpႢiڋX,;wnDDɓ'Ow8$<<1cƱcڴ_[װG"4yzzblҐ333sXX;(Ο??`K,}veeegƐm6D$cǎN-YFbc誶l±A]]~;d LLL.]w 󤧧;88ݻpŚ2e @7o; _D"ٳd2o9sD6 2y2k֬OW0v؊ vb8&&ϏL&<űgx%g̘ahhH"yoII 3P.wDtLPLg6lؠU]]w ܹsD"155@@xׯWQQqtt|)ȣkhhXXXDGGSdϞ=d2yڴi]X]]ݔ)Sd}$IO|xG455ݰXloo﨨(PwPrpժU&&&!Ç>C$EGGO8QYYYMMmڴiO<;aXK,QSS366 khh;":y3i!;֭CY[[zJ*tvDX=Db26l(//;3qDozz9snݺw8_nn/biiy)lhݽ>$$ݝL&S(ooМXQ&?i$6\|H$>~W ,ҫ˗8q.^XYY֭:f@{捳ʶmP(ܼy˻w:˝=={}v4K,RUU>}zlllHq?R[[b ,sᴳZ7|*LVVVo&Mb2Y_׮]#/ށS^^oQT-- #(b8::zD"2<<*vظsNUUUNX[YYQ={t\dD"xIx<^XXX>}B7o.((;v Htuu8ЅbccC&kkk;?k׮#\]]]@|0555aÆ/z~:Vŋ_=ee{tuu/n:CCC}_^^cw}}@R(>}\v pB"\zAIIi]?2y H&Nh``Еn`Z[2b} eX]M$9rDOOsQDVD"IJJڰa3Hh~~~aaa-Toڮ]YM7JXBŋ=== \bEBB9rQQQ&MRVVh5A..''g$Y>'?~ɉD"͘1#//. p8vvv}«,~l=BQUU󋉉Q0 }>༓y!:1<<ޞD"$''wܵÉ C/Y$&&?kbcc[{i==HS@ؠU KKKŋccce>yMMMDDUUUI$#GBL&MP(+V;"T*Y|ɓ:Ґ+d2G(Çaaa}ŊnذsZÇ'O&xp [[v\BSㅆ䀀VSN#z:hhh2d Lz*HܴiށE͛#tuuO~I9\Q(رcĈJJJJJJF:~xee%ޡt˖-u:""۶m+--Rhzϟo>c˗/Ϝ9YSS秪w\3fLMMɓ'Gw8xϟ>}zRRӧ'O.笭 =rH]]ܹsEӧOcbb޽UUUOOѣG=]]]Lݻwqwo޼0a_E ( .ܼy+ld2:xAY[[H>99ѣG=JHH3f NX,ysnܸr?X[[D"̼q˗?N|||N:j(\ޭ+/N<Áxǂk׮9s֭[&M?~~_>mڴ_tI[[p'Jwڵjժŋ߿-!!!O&HK.VjP ,G̙3[QӂIMM{ݻw?~F1bĈAC{W 4mڴGv<]__ .&&& B*jgghootb877͛7)))޽{mnnD"4hА!C qm-rk׮}ASSsnnnnnnhlg={36ݣGcz{{ۨ BZ~}HHȭ[E.Xg?~<''ng1;wYf 8F"ofΜ9lذs}kaamõgϞ 6ժL~„ W\9wUTT:-H*,,}{e2x#_"##Ϟ=dhh8uTleNbŊЈ)StQNNnLLW'ݽxb˖-7o޴ߧNM2kU&oddՌn`#;0G,|ߏ{>|Ç wp &xyy]xHII>|ݻw7W^^.l] `jhh`d2Y]]]YYYOO@AXZZ\.x|>!JR555i4¼M:hGl6Ҳ+%*>} LLL322Zx췕\lLܼo߾_ڵk!WWk׮ΌP!xÇO:|̙/^=P()SdbGikƦ%|"u߫tOOǏ !bqRRRHH7J%111uuuxG&7nPQQkxd:88 ?+J[[[Xy)))!ggӧO*k׮%M(мSSSWW׏H$QQQXB'O @-]pĈ!!!{!2lذĚ P@@Ѧw7L^$!H$k8NBB%aٻ֘8o޼. ###K8qℲرcalr2yWXXhbb2p@.w,N$v IDATh4-""/–̙3ȑ#u?J$TnooO$Pasd-S4mʔ)M,~:v!Q٩:.m۷OٳgB-+,,;w BHMMmȑvzeQRUU奢O9W__?m4"}@2իWƍka x<^TTL vH$ G&?Ǐ)ʖ-[:8)99Jfffd29 ݻwxǥB ,lO/?`}D"_ՒL>==](BYXX);;ԩS3f077GQTOOύ7>z:ICCÜ9so 8F]t Xπw]D\\ܨQ<==Ga\xŋqqqL&wƌL&dʊ[R(55˗/{zz~$,>[gϞ>}:lkkwD ݻw~~~~ȑ?]{>z_Ւkl҃ ***jjj\.:n?w޽/^p8aÆy{{vzr"22rҥ'N}9ҥ޽{iiiFRn"9^L&}6GIK.an۶m֭Ʒҙ3gRRR݋w,OGGٳg & UKeLJ__!TQQLHHt;wȶcccBÆ kcH <}tΝcƌa0&MzKKKKKK/]dɒ>}@ߌiӦegg[vՖ;sL\\@Lŋt:}Ȑ!]`ɜ_c [BhjjĈbWWׁvԝoƍx0BBBBC ILLr6l@-^{wP{>z<ׯpsl=+V%''{zz5D}6 ɤRo߾mokzzzbdh4׺unݺյIs%}9qℼ_bÇj,뻦W^=Z__ƍ p]k}jjjwҥ$GGΉ; NNNƷo;qС'N LMM~WA-߿aÆ/^`5|…B#G,((ػdѢEX8gg[u/_LLLLLLLJJ*,,D{xx;::*hp~+WhjjΛ7oL&ߐ}رcYYYjjjG5j@O?%>s̝;wFaool)5th&,{u^^T*UWWwtt߿?[[[wfLU/^s@ 066vwweV!>>>..\.`CB]d]D" ޻woPPPHHHo߾ HРAfff.]B?߾}ʕ+P*~]EE%66NwShZ*//+ :ԩS ;_*iLשhFgg޽{a;X\XXUPP B W^Xnkkkgg3YLۑJ#GG9::|:FFFk׮KwO/^0`ޱt#Rbb蘙?@<@\.wժUٵkloЎKFˡBSS{zzd}59yWWH$:::7n\d ޱt5ş؈FRL4662v <~x9996lѣ7+吟v9Fkɷ#G2ӧO,//󢢢 /V!*ձؐxCCC&)jL=3x˗/\ԩS˗/-&B)))fff-LqerxãGBxJ,Q///mUUUx}ر,55ѣG5 BW@ ի'M %''2d7oFRHRPܳge˖%$$P(//Cb&&&Y&99Y[[{hhhhIfx<555mm8ԯ_74!duuuXG,A4Nbkɲt===}}}$ɃǏWSS[pҥK 7cǎݺuk *;;;YfС'O...~Bh޽˗/5kݻB˖-;{ݻw*--ӧŋDҥK޽-4wss333hM"۳gƍ7sLl۷o]]]{q{{/_ϟ?D-dh&d2D"{֭CMMM󫨨߿ _dggL++ҾBa}}}MMM}}}}}=˭x`c vmm-ŶJSSSSSv ---$ dTUUϟ?w\333… WXv˖-@ 4(999++` |}}/]Zlف!w 0ɓ'IwwwX:ovmcǎE0LbBs9yɓ'gϞE믿߿xxE b +={LJJrvvn!P}}P(dٍG Z,EDreUܛ>G:Nt:Fnjjjjhh`7?e-h AKUUU;vxȑ=zt˧wk֬Yxq Ҷ,&==% DP(XQqkk묬R/{{{WWW?~X6`\ [''bXv|SSSؤ$lveKbM 999-9?B`;VGG!f% f#D"7BFP( FRʚ ECCCYYJs݄i_( mf͚ ٳ'[[[ "Z. eX:#RfE"BH\RRj~:\\܉'digFkK$fYYBzv-+P^^ñלb Bl1DRQQQUUźfc,L&kjjbYz>;:duH$ǏyǏoٲiʔ)SLimA8ŢڣDlKe7mڴiӦÇ?xgϞM[ %vvvF† 3B11BȶW%OMF  <Z{W\\ӯ_333\***|>Gaޘ*nFFF"9@R+++hll,;Kϝ;rO 3eqղ{ZoXGi `;غXA~LD>qDYYիW̶lҳgOիW?\6 (n;wȶcccBMl>v… Gaw&&&*++6=9tttܶm XN^QQLHHtO_aÆkת CIIiرgϞ-//ã`0MvĉW8[-drdd۷\/6nܨ,+?p@UUٳg]̜9ݻmF׃Π4t-[$$$O_^SS7449rʕ+Ϟ=[lp8xnl2y䌌 =ϟ?G͟vvvxG>yw޽{)))߿'HVVVVVV={馦LZ |:>ebh_;vXzǎ;g͚b^zekkOjiiEFFN45@&T**((3000555222007006455[VyKH$6ry<ބ  vС"##=rMLL\\\֯_oeeÛOg򱱱&&&| %%%,[o\FYYJtPTee^irlv}}@ hEï(VZ?4L(<.[ZZZQQkkk|>ϯ544`z*@ԨT-3;;{ĉ0sXYYrwFfwµuuu\|'HII=z4ށ _v=@"O< Y:ӧ{w hXAddO?{t70OV>;V; dڃy:ɼŋG.dee <݃4o<ESS3::z֬Y&M9s&;"<999)))xxK(t}a0n(Jg[Ș,Kg+Q$KYBIInq[-JqL6P1b}o=zqYOs~g=z Cccn)**{W3uuummm^^^r/gjKJJ^^^^u4ZGG<㧦~0'''yyy奥GGGYYYr/~J:eeeUUU[[[r/r RSSbvww<}}}srr<<<&&&ollhZG? _p8%I*..noo̔{NLGeeNt np>66vddd2p&p,..mnnFGG˽WV ---MMM<(y9<<ɩ+++ {?vvvfggߋz?%|;v}eeebbb~~fiڂŒ R)$h0׭VN e'j,-----搐d???]Bm`4z}QQZvݥ%cg=??{yy%$$h4FV###}||>$9]Ń'RWPP볳U*o#Cnn/jU(QQQΪWT޿?==9BaX$I$f) R|F 7Jpod29\kb? TTμ AhAj %;{(y %;p'΅]0ٳiIENDB`state-machine-1.2.0/examples/doc/index.html0000644000175000017500000000635312305405267020205 0ustar boutilboutil Documentation by YARD 0.7.5

        Documentation by YARD 0.7.5

        Alphabetic Index

        Namespace Listing A-Z

        state-machine-1.2.0/examples/doc/Vehicle_state.png0000644000175000017500000013673212305405267021502 0ustar boutilboutilPNG  IHDR`pvbKGD IDATxu\8- "YXbXyq6b ""( H ҵ?~@a{g^]$ D&:- A *.%%%MMM--- bH$~~~#**JP5 AUUU_TTTPPPPPPTTT\\X)((ȨwSnBQ0XĄ/_ deeeeed(BwUYYb FII N򊋋 ***B`dd$ @'@`XEEEYYYYYY_|/D]]]cc#nR""" ERRRUUUUUUEE\$H跘LO޼y배r2f``o``F&w}_'$$,99rرF޿QYYURRi(??n߅tQXX"UMM|ԨQ _ |;w*++ 4nܸ &;VCCB #''!!!!!!?1662eʼy444z>$b=z`0dddp>3|p555_j볲p]__OFegg7c B~400ݻ!!![[[O0Ȩ;:#///$$իWO<)--׷={>ѡ^~}֭(**Κ5tԨQ***]~游8ӧvvv .2dH oW^E|||666ӦM=817oܻwgvtt$:40??$555{{{{{{3=yݻAAAӦM[jդIz$HbccϜ9sYfO2d2߿ׯ[YY^zƌT*9 ]LLߍ7H$ҢEV^m``@`% `߾}999SN]zu71~~~/_nnnvuuݼy$A#''իFFFJ555N?d;\]]p hiiz} V\yf‡uuuﯨiR+DUUU򲷷^FAǏ>|xt HЫ1+W߿?77w;wTPP :xكԬYfDGYÎg!.رɾ]~^Z377_bŘ1cΜ93#333=<!$%%ϟ?]\\Ե ѷoߖ,Y2|p11IQzAA͛7YfŊD={VLL:e:ujiii$:<6lأGΞ=S555#E<==߾}b,,,<== ^,СC&M5jTBBԩSkݻ7<<<==}]gHЋ-_"%%ťOty&&&>|8xѣGGJtD7ӧ߿cd0\-fff6fOOO&} -޿?k׮?pŋy{{oذp@/RPP0qĊ7nXZZN7b2{rqqxPX'N7NKK+..#IKK?yd֭6m$:"+dee3;;Bݻw?/^\dIKKKg5H Xvslٲw^*JtD}RPPҥK>}*++Kt8Hqqq666?4hӧ4v ظx^~}֬YDӷ455=\]]p1⬭544ZvΘ1ѣGhbI&~͛7urddw ''gԩ/^Bhܸq/_%K~mH@ѣGDDDN?!&&'N4ɓ'DzNIIѣG011yǏW^ o (//f0^iߵk̙cggLt8M>D"=x@PPpfbbr ?g jjjlmmR/^dXӧOꕙn~0IIIcO~m۶pF=e̙0@jnn>}Ǐ544t۷oϝ;ݻg&:e޼yo޼>o = ~G=ҒN{NJJp@622Zp/ѱ:t:HSSӧ$@$'N\x1 !,, 0 ]`,ZHIIѣD^zիW'O- :tΎXyyȝ;w z>>>>|9ѣGر˗/ܔ&v jjjAAAd2MOO!O~,,,~T>PtWo߾@2eс"ř;#qvd}N|+dG?kȑ>>>픁رc .TQQ!:ܹѣGDB<<>>N7okAA'Oӭ;9sO6mZ`` @nQ__ E?{?|!cEEEEEE!!'N 444lܸXLLlB  Ʋe˦O.,,lffj{1F0a1uӧO(++VH-BBBjjj` IIIظJMM !gϞbfuu78ˌ=B^L2zw BѣGVVV [[[*n ӧFFFDoƌO>^__ >` 2DDD$>>&H_9 B#.3d?gߵ/_ު|VV%%%MMMoݺŹEGGGHHH[[{ɒ%xQPSSDRWWOMM7o^ii)QQQǏ4hy~~TTԤI$$$455I$RSS'xEsss eeeWWǏy[n񩬬?~̝;wcѳgioo/++uV___ Pcc#ݻwL2tMQddd֯_Msd]]].SV״~D" :X,lbb/>Mdɒɓ's_~ 6N4D"EDD୵jjjϞ=+++;}t^^.PXX())2i$P@@Z^^.--^]] &&VXX ǍUUUqFвed>>>%%Ȫ 333Ɖ˳??۷o+++ϟ??zh*C'xcYYYYY٢EBϟ?gXxiioڴ̙3.\@3Lƍvޝݒ L&ߺu=ukŲ7o^}N]`ʲen߾ފIKK?~YY{+38sL;) Çbbb***se gСC/^bB[neܹs'Bh۶m5wFeff"RRRlllDDDΝN|7s̅ r_^LL !TRRh4;B/_fD]/nذ!t=gaaBBׯNj"\ËK.SNNN!777=txx1;;!dooώB:tx޽jnn7=ܹsR#9޽Ϝ%t:Bٳg3!!텈kZ}}= >>^OOա>L8Q\\\CCɉNIq 6hii 9::s633WRRZzuPPl'-_ֶzH \Z ǏUQQh/^Σٟo谎:]]ݑ#Gfaa!!!1j(c:*B&Mrrr#BEEEm!)VkjjE wbp~^sE_;E!L&šC"rss[_1/;;&**s`+x|ebĈiӦXpN6 !TTT$'''%%{;r…K.-]9uwy#G9sfժU \\\֯_ ww'NTTThz11u֝}׭[hwK\H$B9p-[{;Fuxƍ;Ν;8p%K{\v-3fǏ322qŶ1whϞ=nj;$H.cc㘘WW]vȴڊo}o J%}&H:::)))xMVVdذaqqq >}ˉ'>y688xĉ***Ϟ=7/^ƁV'ՅL"%%u%d2߾}G%%%Q(Z͋7v~YZZ~!:fИ1cBCCB!!!?&>hP"(=~XQQqڵxqʔ)!|(>>#Gʼn'˗!<]ppjÇ#k_YM*B͛E'$%%mQUXmj`d2NLLL>;zgkQ{cÝ𦚚V "nϞ=֭nUO2SkX/_\~kٲecƌcRsCTTt[n={lTTT?UE/J5ء(:R|||x[n񕗗psR]***ܗF#jjj{֭[kkk544n޼Y^^~UUUȻ@VVvȐ!iii89N3g8{:\VBB/v&AOFqOzno7n矜ܶmBԩS۷Ō -<<<83K_ÃqАFh4]]]oo簾&͛7 L8ӧo/((X`*F366~:ֲ2ggg AACZÏ%44BTTTYY޾XZZL{]J9|wwwyyyIIIinn[lСCI$??Y_G3k-: ;!jn"鰎:&..^.))8KڹpHl{%6pȑuֽ}O>aasqV$HٹzHruuyWrrr֖͛vj-: |r<<<\FaEu ,k͚5!GGGGGGk'Յ؍8//s禦!fΜI`Teee$~Ɇ ,BԙQ9v= }[cc[N>{CCC{{{.*߾} w޳gϤVXb :?҄ z]毿۬Y9baaAH$^|O>9sٳϟ?3tB())͞=ڵk,9@ώbm߾]IIo֬Y555JJJ~zذa-?;b/wD///<B1{^bb"BH__&Aܹsc0NNNj/Q__UT'''MjjjYYYYYY!Lbcc>j(d2zijkffsTTc֝n b7oCz[='8PRRv$HЗ07oڵmyQzC-,,4 `M CDzv Έp.ϯAGGw+++ p gO%HgϾ7{ ޯ\JJ*00pDqyv횟_jj*G555%uѢEeeeO>m $3\\\v%##CtDknn>޽{?h"#N&''sGjhhhhh_ ===Ϝ9S7lٲ>O'ꙙMMMx4//ݻww޵'HѣG?@fII}b MMM8x𠡡wLfUUU7o>)Sh***JIIQjjjJJJaa!BH\\\[[[?8#h?bqq?ƽׯ_}}}z?(((I񢩩)nncN)((߽ׯǏ?5' pa'텂DDD.\@t,_mmmtt˗nࠣp(ݻoݺ9Rwppp9rdr9IϪ]`ʲen߾ފIKK?~YY{+;=sL;wZZZ\KKK111sСC/^bB[neܹs'Bh۶m5xLZJJܹsvЇiM$n޼)((8jԨtc6l HV~ С_xٳׯVUUR!#F,^֭[=D"q|MLLãxbІ rsssss'MD""""V??㣢***B/foE?^g''')))EFFVUUdN˥={V]] &&VXXbtuu9s{ QF׌;VWW&,,ڸq#Bhٲe?qOII 6F[[3m$襚VXA"nڧ+ 6lXNNѱ=L&333+V=z4{ 6z+Vx{{?x 33xY,kڴiL&@b۷ODD,]^RRh4"[}!^č<8w!t%x!x!tvܱg,kӦMT*׳nڴs ;鸞!$''}?k˖-쫡DŽ!bccT$***NEcGhGee۷o֬Y3aFFF߻w۷>~H&^Jt U\\,""}1bBյV7҂"ɜ[[be;122mZ000`X/^@=ybzL&R(/_X/_"^xPIu\*z֭;D?6{cǶSi^ښN߿ؘp N߂ݻgkkKt8ESSSrrrbbbbbbBBBRRRNNBHPPPGGGOOoСCQQQO&%%ځ˗x"--g$^BBCZZDӳqrrb&v5!QQѪ:NHH111:^[[4jkki4heeeccڵk=<<>|6m׮];V^^Iu{{7ot!luuׯϝ;Ge A%33ʊիWDӽ s@@K(gxҡ!$((8l0]]]#F^JKKΞ=Kt,׃g̘555%%%v &Ǐ?z())B,XlDjkks(UUUw2hРj""" Bhڴieeeƍ344>>T*gu_yy97'MΝۭGg @oQXXP8 J}ѱt'O<`sې!C&NoGnٲEHHSPPcy{{#555xu᭝IƎz!38EGGST55@ccc>+++eddBBBjjj^~-'''//99:{ڏΨs玳3e[ZZzɒ%ׯ_/++8qa FWKLLP(Ν$$H@ɓ'KKKX2c ϟ?OhllpႫ9n2'((hjjrԩ7o "nDEE *_Xb\<<<9!Fh!; |r<<<\F; *++ ʜ544jժVY!8::"֬Y8gGmڴiĈ J3f111OXpk:u7Og` ކ .\ڏǬFuu1c޽{Rjkk?}fQQÇ>|ĈÇWWW!2y 6:tX/_N:رc앏=rqq)//onnn2l``0{#FKII`ZeewBCC_~!]]]++ &XZZhȥK|ҭ>|8s̠ɓ'wX$ ؿkggw<SS)S\|XNPlllzz:4h;1bgС7noGٸq#ѱgqqqƍ4i͛7[Ut dee'X,qqqv:4|pUUUBbOv޽۷oϚ5XBsssYYzlٲlIJJ!TTT# Cgb2YYY>}훠#LMMF <7wŊvd2'LPRR_ -ӫB4MJJjРAL&NI*++СCǍgeeO3f (##['NkG{<$e1555112dȯZv .^pBo222lmm%%%)|]vT*i[2???:::>>>99955555B㉌544zlߌ8===))ӧO>}JLL544411133ooNl߾ɓ1119yvȑ;wFDD >˷@@aXw NNNP(Ç9rСC3iiiiiio߾0a9sx񢋋Khhѣw6l)h"&!JKK###߾}PUU% 0|p ѣG1BNNguС۷{xxxzzK6sLwﲧ@m_LL̲eҘL׮][ Ɨ/_RRRRRRpTSSWkOUTTdddg?!744444400|FJcc D Qo(44f˖-^^^ܿ $@vv˗/_###cǎ3FXX3mii ~ߜ9s-,,N(--ptt9 BBB5443d24~+**z۷o e е._rʓ'O3ҝ;w-Z4s̀^^^_yzzFFFы>|IYYYYYYQQQTT׎Eo߾egggffrfDeee!AAAhkk=s暚ϟ?#rcƌ}O][ AS^|yС`99 .^XOO;TVVvͫWFFF-_}ik֬~FFT ҥK߼yoE ԩSV,3o~bȑ7==˖-1c ;ɓ ,:4 TKJJp_dffVVV"""JJJ8Y† (//Oxܢܜ|%''Wd%%%---\HQQ111ӧOvoff˟_ _d2X[[oٲʪg|fdd%H&L~*A,BuO\KmmDb(ŋ ~ݻyyyI:_~]x1D :Kݽ{o֬YiLLv^^^'w5 tƻw:>| :Nm۶>װɓ'ǏU}KJJ B(<<@!++{'%%} .&&*))iii4mx?|/"8w} ѣG[^ZhVn%BYhnY#UUUbbbuuu=v/q- 1))p@#VX;; CCAun!A;-[FR/_Lt,(99YQQQGG-qA:|t7\\\ 9#BEEEm[|v\C&9*q  TUU?D7;`h…\>*))qww2dȉ'-M/hee6mZ/}Ç޽Kt,U[[NRmmmv _Nt,QPP@t,?[ƶw=Wװ\t)QI NJ%H)))$-?* g1RHHCt;vHHHcQ( l-}IIkPXXw^EEEm :rJ4s|EϞ=SQQ? 3Э[zޖ q=i sd2_x1m42}Y<0Iuu5B7o/a.//믿444B&&& vpp]v-4].$$D]]F}b}i9swQ̍=* 2_~211&:w$\Ւ%KuE2^ r(+++]>//!!:ҥKfff!l{3gΰ_r!vWX!""BR~4yѷo߄&NصWӧOO0BϜ9˽Rss˗/W^;w#q۷o$$$J?}gn,nϟ?'G}F]@:Çbbb***s-..nbÆ ZZZzzzW fϞ=gRHHHPPײp5>7BhܸqQQQ555wF[oL4vXÇ(o޼Ǐ&L<Bx)[^^^PP0>>3ߡݻwnÆ ;{l-vܹ}u^paʔ)||| eĈnnno塚mjjzlllh4/t(t:CJJB̚5իWa~???===;wz,] [NN{CCtUdrr2RdddUUUPPneԶpmmgN>-###--{ؽ{7g͚qӧO!B:144h4MWWۛl3 ˗/G#++Mh'NLMMͱpU NJJJ dmmjkhh ;w.%KBCC{M[ee+WBd2pW\LdfffzzzZYY>>!!O啑 d<FaOY__`0JJJ󋊊jkkq===} ޽{ݻT*|ԨQ3ZZZ""""##߽{ciiiooogg'--%k A]zz7mDt,=!99yĈ3jB7yUUUuuuxk]]EƒhC<1޾};f̘" ^s blCC{.\P__ҥoRqM:uΜ9hiirpTT,>xc=zTCCׯJOOϧO婪MIIyUUU544RT*J****)) ʔ{bUTT hkk?~$dXxNNNl6;)) lݻgϞ#F`K7OX,Vkk`_s?Nꚛa%n޽;}tF]"񚚚#G\jҥKϝ;S}ooاOj_zꜛ;aUU˗/_rNKK---Gnnn._222Cm޼ylذ111NJ&Ϝ9?g}J\nVVݻL2k,~6 ``0^z󴶶9ոqF-]ZDߋA?C 233d@5jX=&###f;}p/]xH$~C|Jw9p~jXXX(''`n+VΜ9_qFF>;QFF+--̙3S Bp^xqԩŋkhh|||_'xEAee۷o322 !TPPЎ;JKKUUU3SLijjJII:aڵkI|r}}}8KCCr&;;Ғ^XXlj}Fy͛7,{UUvoVJJŋ_d~:O$2zhKKK 4 }>"ᨵ9;lݺ555ѣGX2,(((x/E<==RSS~ 287`._%'\vݻ֭xex1t 322.]{QKKKAAjO}*%%d2 d2 6[\\s8p8؅^rrrrrr 𶼼*̈ 6AP ;/_D\,ZhƌeeezzzX"rOd2Lxz˗;"7n\VV륤1˗vvv}nqx!ڑ#Hғ HG! J᥺:33sǎX"FeddM}}}HHÇY,XBBf,@pOUU`~$//OP@ƍсY!n}Dݝ=x`X2455%&&TF̙\2`{F|---sνw^/{HCx<ިQ&Mt)cbbbL+c8  t3p8. bDFFf˖-m۶͘1CYY@$?U ЂἽ/^`0ep8uĉQv  ! H]Rtgggeee*:>Í9`ʔ)jjj`_7pB<555%KO:5|* lMM ?%%%фy֭M6ag/X@]]]^^&22okkp4ŋz$aaavvvjjjk֬{.=zEQ˗_zUp_$hԩrrrZZZ .ȃEx}_~boW^IJJ饦2h[[[8㍻ܖwrrJOOg0?#テ>\JJJGG'))?tppx <]UUUׯ_---  HH;vLJJݻwX"+GGGXe^lUTTsxpFFFƊ xs IDATB8 )ևe NOOߺukVVĉh4ZMMM{{;@RRΝ;6lHII!HǎSWW7766.((466ܹ߿RǏ߷oERRٳ/VOi~~~YY.|7][[ p8D"ه^~]ZZৗ:@GG[fΜ)fDDD,]ca`p8qƙ8q̙3l6{̙7n:J--񒒒#Ftp.K H$7kmmf͚~akp`O AD0LV[[[ddiue˖egg|QS555 6m@KKkݺu" ^_'z-&T244|Ammǵ455߿/c8< |XOrOE5pl x0_˽o޼4ìD7@\mgg'ֱ={HII~.6555 `eeڊm|pnv.{Q[[[555566p8 ͛7wiEpXC.#Pn:0eʔ0!`EPPPUU/v?[_ܹn:!* ,Gϐؚ _mllŷrmm-E;ai9!7ooX"vRSSDbHHH'66ӓ@ hjja CF ;vsssY,1'!! @ .^iA  s޽'ƍTYY)={lVzHw_4 PZZʿ7==sZxMu^^/ XF!" FFF9O`rYYYV.))( _tbGG\Ĥ‡脄D|1LCCɓ'P***$366v #v| >rH7obeee0ZC`}.!0|ׯ[YYM:UFF&111++kƍ#{yy8⬪ A 8+"N=._wSη#''d2ؠ 8.(11&**ZZZk.^f?A6Dw4![vlݺĉ=̜6rw;v---*4m4@BB\LOO0a§c`\˗ 8SMM-55o.xbK2aaϝ;7330:;;ϝ;gff&!!/6vE{l sfdzؠQdddDCCLqMkkkxNƽxQVVVSS_.x0bm'N,\PWWᄏr ^D*,,2e x>{I0|XVVV^^np1cə򻘢^yAqqqzzzGp8ܴi&s._̿w}K`;{oݻh vF8|d``0uׇDGG||믿Ltuu{yA3ˀ%HAAA{ϟ?XdIw-3wܑ#GvH"$%%Sɓ8NSu .ʋ-gVh  "2$OOOQa菄CCC2u,_ݫ ,qm&X,F|r]]իWT*S?7H$JHHx<^SSgW^ gv7o^AAMtt4`֬YgΜ)k֬Gy|vz|9J.>3 cc㊊ eII{׃NEEɸq6##GZZZ^^ŋj "2$:>qDYY/ڎ &h" }{>Aspp Hp 駟Ǝ www~o"|a'$5d ݻɓDCCC)))yQbر[lb˥˽=/~VoĿK.>Jںnpϟg5 &U]]pppDO goN4O.[i|?&---CCDXKm``0`U>|D"͘1K'0"E{(uTw ;::III-YH씕믿lrroϋ՛} `4h~TD"&L(=]v-@ J⯨HWWZ8NTT#;vv-`< )11QIIɉ3 eeeGGGq~{^gg'~?p8<omm3gffyf[O>f&t_4p$%f]%X 9rdwуnx"Yr%@M׺j%)))//vW^ ~1!C`nσ6eʔ8j|pqttȨ+++6"흔b>V666OYY?|-333ww7o Bh璇ڵk ޽nݺ>G :C("|㼤FhSW˗>>>DS]]cǎQQQt:}]z1D"x<*iՅ+Ķg..vYR`~dfee8@ptt>,󭣣###)X,ٳgm}}xUћ.]qrr/ty{{ |ˆ$eeeX < riik<"U]]mbbbee`0Ejkkw1m4ڵk^|٥pEll,vgruuX]]0"%%uY\zN>ѳ9s樨ɹx&H՞nnn_z\.7!!WPP |ݻw3tM8NB ާ)))d2F|KC[#:y捿 D􌍍:"DT|a!A07d yy~hdGN1b`ɝ=---$$dɒ%*@OzhqBCCއ-3q< f[2a y򥊊ܹsLR'##ȑ#^^^6J:rg3d"e˖^a e`ܹ۶msuuEo\]]o~޽.5ֆ>'H/_ 7n@CC/++Ss\SSSOO/}a*''7zATZOOO8TAFFDRTT\~` d(C! ;r'%%IJJڵ @ Dq֬Yj?h:ClO}:..FXSӀ"΅ N:UPPܬgcc}vAak%H.\򊊊6ŋ<&\nvvvRRRfffrrrqq!˗//Y۷zzzXǂ1;;;==˗/f<<''''';RRRHPD577gff>{,555--NFݻw' 3szիWۍ7K*^O><@Xp9A!ёٳ7o޼y󦼼455x<B$?~ĈXG p |ܹO:7n7윜dxNȌ?^~m422j(ss{pٓ'O}d`^__+W<|PJJkڵwl={644u̙7n4 [ns玣+<==386==L 7oސA! X,333 .`ˀ711YtEϟs8KKKGGlj'ւXRo:uj}}}ZZVTWWTTTVUU|Ç555qYYY)))555uuuuuummmx^WUU.ɓ+W\~b֬Y&LWQQ:::222bbb>|H L9c X,~Xa2!!!yyyVVV>>>K.T26wހ  򀀀3g߃DAHtawSN9RXmx.W&Muaa2Ɠ'O :5k֬/d2妖xc$INNNNNNJJJL`)EGGpQF;88XXXP> loo/)))((HMMMNNhkkSTTtuu;w0̋>r UUUqS͛tٳe˖K.M6M#""C!AbX#FXr%`p8NNNgΜgSrwwwvvJIQ׮];wfSSS''sa "rt:VOyivv6PTCCC]]]YYYYYYEEE9998t`|۷[fffY@[ŧO>sLSS7|uPM:UUU5""BQ[[[7lp̙nذAt;BDBt]v>3rʂ>\DjhhWqqۿI3fyU !_Oˇ7nܪU,YF틃gϞM2e̘1n 񇿿``A~ Rkk%KD7G}ij.%%%utt;N8=zݛ7o:+++}2d`dff_tIFFfٲe6lwM0aܸq7nﻐ 6{yy NA >}۷C`n /--ŊsΝ;wtttݿkWWW>oɓ!?yŲQTTLLLD3 @ ?~8NwqqY~o6X,-OJJQs[l NLL52 i񦦦Xm۶mڴ]߿}v|||gg f̘!sI̙#srrP:DGGǭ[N>GVX#nSia/^d3.;k֬l99A^ Rnn1c?~<>իW'&&:;;n߾}Νb--3gzxxgzիWk̴֬yKɓ'}}}cccUE O٧N:<@Xp8h5=z-*>ϙ3ĉXŀ ~Jr\ #Gx{{kkk |}}cbb۱{xW_}F$wލa<3YYYA5669rIrpp:ᘽ IDAT͚5 @x@x9ց I8Abd2~:1bddcooo߾/^qPQQϟ2eKbbfgϞUUU[zutt4`[[[#G577} eff9;; 7uvv9rDYYY__?** p?|>rrr͛pA8Nll,D&wj.^H pƏu | N<#۶mQp0z訨(BP$$$6kkkkkk,Yx\.iӦ'O hiiZ"));vX,;/]$LII LzhQ?  XYYEѣGw3.KA iXG!^:;;N:%a>SPP_CT*9D" >K&&&555"㕕y{{Dvڵp8}Dx_RFFFAAɓ'D"QSS߿  888`!g"B0PX,VJJ *Httt^`HUUP]] y<ޱc%$$L;']\dIrr͛ETbN /^,((o{LJ,^xi<}ƆB}#"###߽{jժg̘1#..[n-]@c b >|@?}l"_3---5---yyyc{nn.łÖ/_rp/tTUUHHH脆vtt|>ݏ|ъӦMknn:A-220vP޿u ۷R(1ch4uuuiiW"A 8pL&c8z1\|#Gő#G޼y+++{ ?\RRUX TQQQTT sᇈiiiGGwttl޼íXbu|VFF?n㏣F: NjH$III {1" .v&&&XG!F(((.RSS :3mmm}}}@ TWW{zz+++7(//߼y78|,quYSVVdllE}9Nttɓq8ÇE4҅ յ/\ ,77"#& ֯_?l}||ħ5޾} HNN:aXpׯc #F 8qbhhh]]][666޽;0}%HK.:u<((Fp8gg+W|i/UWW?xgg甔7***DmVV PM!22 D%D:ėڸq#D uh'6 "H$NiߴGEE{yy^z nZ*++}펎k>:?SFFf=?X{""D\.7>>ׯ_\ru԰ K.moo'$)+++***((((((**jjjbXpm'+++))$!!ahhhbbbdddllLѤ{n۷4M%''8n>/nݺu-2{yy)++O>aX477HY---8NUU`ԩ ʮO<,,,NMMM__͚5j h/^XXXHII},yyy6@cc LLLdee-,,{APzz2J?~{@^^d1f֬YC&,Xfϝ;WYY2[Ǐ~ƍ%66_~vZCCȝ;wx{{AǻEFFzyy=zB̛7/%%0UU|B=zoQSSRSLٵkܾM6>}ɓԩSjjj3FAAa?noovG'OD"$/](={LQQx%%w$%%Ν;P%%%***83@=ztTTHIIP(pXHm嵵K,xʕ+mmm]6w^օ ל?d(##ß0g 4LLLƎ x_xΝuEoooѣCOEE>?\tvv&''D"@ 8:::tHlg(++۷ot͚5W^g ;v8q5k>}*V222H$`ۭmTYYyA=jԨ={dddlaٳ'O&)˥Rr"AY8?&zp]| NTjÙ H$KJJ4`bb"-yyy### ~Q; ŋfyG{Yf ᇫWc]oeffxu?כyɓ'1x ,~z{{ CXEEE``@:"ڽ{7III ???~8t3t}ȑ#hxbƀ ȗZAEEEp葒ӧEtںlٲ6 W^eX222}󙶷KIIM<.\xMSSI [>^⯙3gsŊ}uy>[ZZ ?Z !,,l= C|ѕ+W\|ƍ SSSSRR>}+C&ܨT*V_eee||G?^TT`0rrrp*yyyyyy))).d24DR̾+WWWsssqAA֭[O;vի/^mUСC!!!޽zjϞ=/_1bĖ-[-[&))9`$&&ݻVVV[nussjii3? bkqF8冄[n`BWE eŊfffNNN}<IZ[[eddYΝ;wwwCJJJD3===99\8;*@MMS_}ZWWgcctX5QԺ::nddttt9U/w&)X`ܼy3""">>N\.NjDX@IIׯaY#FYXXaE5d2Lvqq:Agii#G;veCµz[[ :rȪUieeuڵO]< eG"n&H9s̙3gCD"ӧO_hS ; ""JJJ~~~f244QVV::t4iҡCpۧ8qbӦM߿ Aq ^ŪL8kJJJڲJ[[NNN}#vvv۶m˛;wnCCCjj`Æ [nݰa\QQQT;8%WW䔔KK˖cǖnnn=ڲeKII?;gffGFFhݻw?NއcXgf2EBMA l6d1L6 @pc KP TjDAD<={644,\pݺuqpyѢEϞ=t޽s_h bB$6]SSipaX#KbݺuϞ=c0VVVw0a܀N9r$""c֬YAAAgΜٶmg0='Hߏ=ZSS333~صkWllׯ-,,6m4ydn %%%kkǏ3ZDuvvZ[[755ypOQSS_+SRR" RRRrrr$IIIIջ9  jL&3"""$$$//opDܼyѣGWXq?p\nDDի#""zs QE,--]]]8Ї~ %,kĈ&LpD3g LlGIJJ qㆆʕ+׭[uPb͛wA___gϞ͛7f߻wo0^C!f^566.,,: rjkkܹ0# YXXuq 2o޼F}}{ZZZBB ʎ666T*)99pdC Ґx|ʗ.]"_okiiM4m„ ;7#;]O.((0aט1c 7j(Pcddt,XzȡC]"ՀDQT߾}Ǎw "bbڵevڕ/I#5cqq1i}(fKl6ٰdw A:3118p`^VX!󃂂 w^X3KKˆeee&o߾QF|r̙%%%NNNE=zt͞dׇ[bŒ%KգG.//tNbxѢE?SLL̀.@e׀BPW LҵkyGPxu͜9sذaVVV***խ lٲݻw={֭[Ϟ=ϯog% [(b2ٱcEQ=>>>EYFr EQǎBQԼy?\SSS-@NNEQӧOoz헔l2MMM]]ueggwNj``bG|5ݵ(nAg̘QZZOw-̙3[n-//oeo-++#Kv^=B44H277711133333322255511!ƚW't=Ҟ=z=C IHH˳${RRRlmmɌ,䔴4C(((zU[4662L:;tʀ??\WWuֹAm{CHV2'<oĈjjjt@d8 x޽{TT'|'Oˤ$E>x/_] BOcƌ|޽{鮅fb888xΜ9(RQQ177777߿!yI$^"!Fr0222222244444400 >Mh777nH`s˧Lr)aߗL:MQ yJ%BCCFCCVe˖-[ɓ'O/]tdb*))0 22rڍ7n߾鈢(͛7iv EQޗ/_V###Ǎٓ.%>/inz]IIɻw޽{WVVݻbKr<$1%ccى 200kن#ۀ1cDGG{{{~:((h޼y&9dȐ]v :˗3f̨HHH =v֯_s߻wڵkϞ=+(( .Ђ =233O>ϝ;W*^%<<|-r{:u3gdoߖ^RKRQQաC֬YCw-t2d.Dn5]⿚=-**j(և,˱F>b7m$DRAA5k>} o>U'Nzj]]ݔ)S;v…]v1  mB[N8QVVM%'ȿ௿Y^^^?cZZ={vRRҋ/@|@(jժUYYY ;!ULLǏGAw- (Բ=)'}}}AvJ(x)T ---%E9|͛%OŋwU͛7>>>M ! y̙3-z;tM$/2>jfڵ~~~7 RSS F(-ыSC@(j޼y ;rHK?S߇Hz5#LMMeD`0/^ӻwK.yxxtYRG>>>6my&'۷{ްa/ݵt)Pp~kzDfMOdYTYYYIcdL çM1ɓ'?y$55UJ~)bÆ ?cvvdE8pr(:z;Cw-]СC>>>/^ eGr$t*@BsˇMℲrccccc#8x𠂏! N>dk[띜LLLg&\{{[~7trejkS 4=W[[( ]]]===| B.r+++cEE˽~||]vIړGfffBB݅- HEĸJVMobxɩ j}TS%%% Mm6hJCC0* L8θq.~QQQG~E>}@>U@(j۶m~~~k鮥sݻosvvIRj q6WYYYOO>) ]]])tO~Lw-kllٳ@>[@wuu-**z왎t/֭N?\.RMw ¦+))(%IM::::::f7bz!k׮}ajj*݅H;v*|GEQ:u 讥S{n yh潁lxJ#mvH$8}(M萃entbiitݻw]?뗐駟] R׽{)S888lܸr:XMMʹiTTT033S*++F&A&Ԓ,(( iܬEQ,ifXl6[[[l>FJJJ~~ .DÇHAEQnnn .dXK.# 'Oիx}}}m$Z{x<^vv$zUUUUWW&Kzzz,ld[[[i"3jkkc.uP(zzz sN C(xʕ+l3.466zzz&%%ٓrj\.WUUUUUq\%9痖VTTH^j&-4ETN27.ȢÇcM3+WLNnEQ۷o˛?_~Iw9аr7nDDD`ZODf*où$,)+b$A(E6+ƖWSQQ! S$5lH"ZdU cccnJw!RgԨQO>9r$ݵyHEO>~Xp!崑P(tww{kƎKw9Х3_yMMMmm-YF킂d*ɢU-7ZyXYY@+--Hzabbyr|~YYP(#٬Cc2kVUURUUedyy?$ *  9(ɓ'ofrvv_h}_bŕ+W֭[w!UUU+j$: <x"<D\.W($qUWW7}.`%cd=bd2utt$]ˍ,[[λݻӌC#⪪*Ew-rEEQVVV7n\xqxxΗ/xyy_~d2۟$$ V V"YnoD2]4av-X,$K)?:e y׏輋w6e,4SDQӧgΜzj;vlٲEJF~+""bΜ9G[@ӣ("M[$8jҜE2]$&,>/ H-ҍ⊊oJ6Uȫ$bK4SUUݞ+=yDYYyȑ&M1cFrr#9FII<.]:w\ff֤I&Loܻw///y$ϟ?g'Olϛ"H.j) HĨQ?~q__߀m۶-X >|ٞ={>zŅJkH:dUbH>>q\My\cJv"<|`//x,Y2k֬_}X,n6SѣG7oIZ֯_?iҤƆ@ prrAAA ݻw;w={F>޽{qqqSNe0 HDIFv|xRRRff&?77GӧO~:Yv300p:{Yf?EQK.=|@@ NZv-վYx<Ǎ@KZjܹ|IQ,lܸqE HKEQ |A''~mժU?LMM\ۍL&SEE4dȐ<ɔ)))Eۧy[nlkkVn1iӦ" ͛w̙={\xco$kk#F:u*;;{ӦMHGd.++++++555f.]p8}y߾}lAbԩSC 122RUUUVV(J$~ u_E)2442ɨR֭srr>|سg 6gݺu7ormrVVٳggϞmbbp¬'O,YDJ6|>=WpttLIIywcc#GC׵gϞkײX(.ш`0E|qәԆ):}Puu{=zd2lmm9.\$555===###55XUU>suu7nܐ!C~s2fѢE۷o xzzv=''j&++++++**"3ʶVt{Zddd瓡MEeddp1,33kE%chiiM>}E}6...%%%==ohhp&Nد_aÆa?-H3gΌ(ꫯ<==ɫL&dREi( %-11ƍ wΝ˗O2ԩSE9;;gddJSݺu=o@ @'A@(s̑ݯ_bPEΞ= -! :p3grss.Dmܸ1...11B f)p$ASdXH 8X,ȠiI@JY[[t"ux<^NN=݅!$RjjjC-]\\.@! rqqA@j)::_~XS3 ruuʢJw  ftt4݅Hҗ/_9BH/UUqƅ]v횆 t$j .|ׯ.DZ̘1C[[BH'^rBBrrrbb݅-$j***_u``X,]r[n_y@͛7/33S53Ye H1cƈDǏ]okk[H ]]]cccNw- z:u?ݵ3$ 9ź{.ܹ݅sk׮֭ݵ3$ 7oޜ6mړ'OJw-]aĈt @fL8͛7 ݵt;v|wiiit 0 Ȍ'OzJᤥ=z  dݻ?jaaAw-]dرeeeL&Z@`oo߿YIIr:]``Ǐv>..v K455޽{AktIII+Vؾ};@A Ȟcǎmݺ5**JCeeSv옗ݵ(4$&L %֬Yt̙K]C;}􉏏WSSsrr ~M#iO>\p󫫫鮨5ϟLKKߴiSQQGIc=^\]]ѣG9RSStRooo ,@(^xf:t营eW BUUٳg;V^^>  :TIISo͛3gΔxxxlݺƦS턀 6888 _ٳyf͚eoo߱I8"""888..rܹVBL@@Eի,cccѣGZ[[\.7&&ѣGQQQ)))ӧO1bQ2 Z|||dddTTTBBH$211ݻ7p8vvvl6bX,uuJ.[UUr322ӳ\]]G_hjjoC@(~󟌌OQWWp8a]<t8$+//ruuubXIIIKKKMMMOOOMM P~LIENDB`state-machine-1.2.0/examples/doc/Vehicle.html0000644000175000017500000073577312305405267020474 0ustar boutilboutil Class: Vehicle — Documentation by YARD 0.7.5

        Class: Vehicle

        Inherits:
        Object
        • Object
        show all
        Defined in:
        vehicle.rb

        Direct Known Subclasses

        Car

        State Machines

        This class contains 1 state machine(s).

        state

        State machine diagram for state

        Instance Attribute Summary (collapse)

        Class Method Summary (collapse)

        Instance Method Summary (collapse)

        Instance Attribute Details

        - (Object) state

        Gets the current attribute value for the machine

        Returns:

        • The attribute value

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        Class Method Details

        + (String) human_state_event_name(event)

        Gets the humanized name for the given event.

        Parameters:

        • event (Symbol)

          The event to look up

        Returns:

        • (String)

          The human event name

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        + (String) human_state_name(state)

        Gets the humanized name for the given state.

        Parameters:

        • state (Symbol)

          The state to look up

        Returns:

        • (String)

          The human state name

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        Instance Method Details

        - (Boolean) can_crash?(requirements = {})

        Checks whether :crash can be fired.

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (Boolean)

          true if :crash can be fired, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) can_idle?(requirements = {})

        Checks whether :idle can be fired.

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (Boolean)

          true if :idle can be fired, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) can_ignite?(requirements = {})

        Checks whether :ignite can be fired.

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (Boolean)

          true if :ignite can be fired, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) can_park?(requirements = {})

        Checks whether :park can be fired.

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (Boolean)

          true if :park can be fired, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) can_repair?(requirements = {})

        Checks whether :repair can be fired.

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (Boolean)

          true if :repair can be fired, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) can_shift_down?(requirements = {})

        Checks whether :shift_down can be fired.

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (Boolean)

          true if :shift_down can be fired, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) can_shift_up?(requirements = {})

        Checks whether :shift_up can be fired.

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (Boolean)

          true if :shift_up can be fired, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) crash(*args)

        Fires the :crash event.

        Parameters:

        • args (Array)

          Optional arguments to include in transition callbacks

        Returns:

        • (Boolean)

          true if the transition succeeds, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) crash!(*args)

        Fires the :crash event, raising an exception if it fails.

        Parameters:

        • args (Array)

          Optional arguments to include in transition callbacks

        Returns:

        • (Boolean)

          true if the transition succeeds

        Raises:

        • (StateMachine::InvalidTransition)

          If the transition fails

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (StateMachine::Transition) crash_transition(requirements = {})

        Gets the next transition that would be performed if :crash were to be fired.

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (StateMachine::Transition)

          The transition that would be performed or nil

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) fire_state_event(event, *args)

        Fires an arbitrary state event with the given argument list

        Parameters:

        • event (Symbol)

          The name of the event to fire

        • args

          Optional arguments to include in the transition

        Returns:

        • (Boolean)

          true if the event succeeds, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) first_gear?

        Checks whether :first_gear is the current state.

        Returns:

        • (Boolean)

          true if this is the current state, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (String) human_state_name

        Gets the human-readable name of the state for the current value.

        Returns:

        • (String)

          The human-readable state name

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) idle(*args)

        Fires the :idle event.

        Parameters:

        • args (Array)

          Optional arguments to include in transition callbacks

        Returns:

        • (Boolean)

          true if the transition succeeds, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) idle!(*args)

        Fires the :idle event, raising an exception if it fails.

        Parameters:

        • args (Array)

          Optional arguments to include in transition callbacks

        Returns:

        • (Boolean)

          true if the transition succeeds

        Raises:

        • (StateMachine::InvalidTransition)

          If the transition fails

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (StateMachine::Transition) idle_transition(requirements = {})

        Gets the next transition that would be performed if :idle were to be fired.

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (StateMachine::Transition)

          The transition that would be performed or nil

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) idling?

        Checks whether :idling is the current state.

        Returns:

        • (Boolean)

          true if this is the current state, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) ignite(*args)

        Fires the :ignite event.

        Parameters:

        • args (Array)

          Optional arguments to include in transition callbacks

        Returns:

        • (Boolean)

          true if the transition succeeds, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) ignite!(*args)

        Fires the :ignite event, raising an exception if it fails.

        Parameters:

        • args (Array)

          Optional arguments to include in transition callbacks

        Returns:

        • (Boolean)

          true if the transition succeeds

        Raises:

        • (StateMachine::InvalidTransition)

          If the transition fails

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (StateMachine::Transition) ignite_transition(requirements = {})

        Gets the next transition that would be performed if :ignite were to be fired.

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (StateMachine::Transition)

          The transition that would be performed or nil

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) park(*args)

        Fires the :park event.

        Parameters:

        • args (Array)

          Optional arguments to include in transition callbacks

        Returns:

        • (Boolean)

          true if the transition succeeds, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) park!(*args)

        Fires the :park event, raising an exception if it fails.

        Parameters:

        • args (Array)

          Optional arguments to include in transition callbacks

        Returns:

        • (Boolean)

          true if the transition succeeds

        Raises:

        • (StateMachine::InvalidTransition)

          If the transition fails

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (StateMachine::Transition) park_transition(requirements = {})

        Gets the next transition that would be performed if :park were to be fired.

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (StateMachine::Transition)

          The transition that would be performed or nil

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) parked?

        Checks whether :parked is the current state.

        Returns:

        • (Boolean)

          true if this is the current state, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) repair(*args)

        Fires the :repair event.

        Parameters:

        • args (Array)

          Optional arguments to include in transition callbacks

        Returns:

        • (Boolean)

          true if the transition succeeds, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) repair!(*args)

        Fires the :repair event, raising an exception if it fails.

        Parameters:

        • args (Array)

          Optional arguments to include in transition callbacks

        Returns:

        • (Boolean)

          true if the transition succeeds

        Raises:

        • (StateMachine::InvalidTransition)

          If the transition fails

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (StateMachine::Transition) repair_transition(requirements = {})

        Gets the next transition that would be performed if :repair were to be fired.

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (StateMachine::Transition)

          The transition that would be performed or nil

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) second_gear?

        Checks whether :second_gear is the current state.

        Returns:

        • (Boolean)

          true if this is the current state, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) shift_down(*args)

        Fires the :shift_down event.

        Parameters:

        • args (Array)

          Optional arguments to include in transition callbacks

        Returns:

        • (Boolean)

          true if the transition succeeds, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) shift_down!(*args)

        Fires the :shift_down event, raising an exception if it fails.

        Parameters:

        • args (Array)

          Optional arguments to include in transition callbacks

        Returns:

        • (Boolean)

          true if the transition succeeds

        Raises:

        • (StateMachine::InvalidTransition)

          If the transition fails

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (StateMachine::Transition) shift_down_transition(requirements = {})

        Gets the next transition that would be performed if :shift_down were to be fired.

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (StateMachine::Transition)

          The transition that would be performed or nil

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) shift_up(*args)

        Fires the :shift_up event.

        Parameters:

        • args (Array)

          Optional arguments to include in transition callbacks

        Returns:

        • (Boolean)

          true if the transition succeeds, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) shift_up!(*args)

        Fires the :shift_up event, raising an exception if it fails.

        Parameters:

        • args (Array)

          Optional arguments to include in transition callbacks

        Returns:

        • (Boolean)

          true if the transition succeeds

        Raises:

        • (StateMachine::InvalidTransition)

          If the transition fails

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (StateMachine::Transition) shift_up_transition(requirements = {})

        Gets the next transition that would be performed if :shift_up were to be fired.

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (StateMachine::Transition)

          The transition that would be performed or nil

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) stalled?

        Checks whether :stalled is the current state.

        Returns:

        • (Boolean)

          true if this is the current state, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) state?(state_name)

        Checks the given state name against the current state.

        Parameters:

        • state_name (Symbol)

          The name of the state to check

        Returns:

        • (Boolean)

          True if they are the same state, otherwise false

        Raises:

        • (IndexError)

          If the state name is invalid

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Array<Symbol>) state_events(requirements = {})

        Gets the list of events that can be fired on the current state (uses the unqualified event names)

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :on (Symbol)

          One or more events that fire the transition

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (Array<Symbol>)

          The list of event names

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Symbol) state_name

        Gets the internal name of the state for the current value.

        Returns:

        • (Symbol)

          The internal name of the state

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (StateMachine::PathCollection) state_paths(requirements = {})

        Gets the list of sequences of transitions that can be run for the current state

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          The initial state

        • :to (Symbol)

          The target state

        • :deep (Boolean)

          Whether to enable deep searches for the target state

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (StateMachine::PathCollection)

          The collection of paths

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Array<StateMachine::Transition>) state_transitions(requirements = {})

        Gets the list of transitions that can be made for the current state

        Parameters:

        • requirements (Hash) (defaults to: {})

          The transition requirements to test against

        Options Hash (requirements):

        • :from (Symbol) — default: the current state

          One or more initial states

        • :to (Symbol)

          One or more target states

        • :on (Symbol)

          One or more events that fire the transition

        • :guard (Boolean)

          Whether to guard transitions with conditionals

        Returns:

        • (Array<StateMachine::Transition>)

          The available transitions

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end

        - (Boolean) third_gear?

        Checks whether :third_gear is the current state.

        Returns:

        • (Boolean)

          true if this is the current state, otherwise false

        
        
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        # File 'vehicle.rb', line 4
        
        state_machine :initial => :parked do
          event :park do
            transition [:idling, :first_gear] => :parked
          end
          
          event :ignite do
            transition :stalled => same, :parked => :idling
          end
          
          event :idle do
            transition :first_gear => :idling
          end
          
          event :shift_up do
            transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
          end
          
          event :shift_down do
            transition :third_gear => :second_gear, :second_gear => :first_gear
          end
          
          event :crash do
            transition [:first_gear, :second_gear, :third_gear] => :stalled
          end
          
          event :repair do
            transition :stalled => :parked
          end
        end
        state-machine-1.2.0/examples/doc/method_list.html0000644000175000017500000004714012305405267021410 0ustar boutilboutil

        Method List

        state-machine-1.2.0/examples/doc/css/0000755000175000017500000000000012305405267016771 5ustar boutilboutilstate-machine-1.2.0/examples/doc/css/common.css0000644000175000017500000000005212305405267020770 0ustar boutilboutil/* Override this file with custom rules */state-machine-1.2.0/examples/doc/css/full_list.css0000644000175000017500000001253712305405267021510 0ustar boutilboutilbody { margin: 0; font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; font-size: 13px; height: 101%; overflow-x: hidden; } h1 { padding: 12px 10px; padding-bottom: 0; margin: 0; font-size: 1.4em; } .clear { clear: both; } #search { position: absolute; right: 5px; top: 9px; padding-left: 24px; } #content.insearch #search, #content.insearch #noresults { background: url(data:image/gif;base64,R0lGODlhEAAQAPYAAP///wAAAPr6+pKSkoiIiO7u7sjIyNjY2J6engAAAI6OjsbGxjIyMlJSUuzs7KamppSUlPLy8oKCghwcHLKysqSkpJqamvT09Pj4+KioqM7OzkRERAwMDGBgYN7e3ujo6Ly8vCoqKjY2NkZGRtTU1MTExDw8PE5OTj4+PkhISNDQ0MrKylpaWrS0tOrq6nBwcKysrLi4uLq6ul5eXlxcXGJiYoaGhuDg4H5+fvz8/KKiohgYGCwsLFZWVgQEBFBQUMzMzDg4OFhYWBoaGvDw8NbW1pycnOLi4ubm5kBAQKqqqiQkJCAgIK6urnJyckpKSjQ0NGpqatLS0sDAwCYmJnx8fEJCQlRUVAoKCggICLCwsOTk5ExMTPb29ra2tmZmZmhoaNzc3KCgoBISEiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCAAAACwAAAAAEAAQAAAHaIAAgoMgIiYlg4kACxIaACEJCSiKggYMCRselwkpghGJBJEcFgsjJyoAGBmfggcNEx0flBiKDhQFlIoCCA+5lAORFb4AJIihCRbDxQAFChAXw9HSqb60iREZ1omqrIPdJCTe0SWI09GBACH5BAkIAAAALAAAAAAQABAAAAdrgACCgwc0NTeDiYozCQkvOTo9GTmDKy8aFy+NOBA7CTswgywJDTIuEjYFIY0JNYMtKTEFiRU8Pjwygy4ws4owPyCKwsMAJSTEgiQlgsbIAMrO0dKDGMTViREZ14kYGRGK38nHguHEJcvTyIEAIfkECQgAAAAsAAAAABAAEAAAB2iAAIKDAggPg4iJAAMJCRUAJRIqiRGCBI0WQEEJJkWDERkYAAUKEBc4Po1GiKKJHkJDNEeKig4URLS0ICImJZAkuQAhjSi/wQyNKcGDCyMnk8u5rYrTgqDVghgZlYjcACTA1sslvtHRgQAh+QQJCAAAACwAAAAAEAAQAAAHZ4AAgoOEhYaCJSWHgxGDJCQARAtOUoQRGRiFD0kJUYWZhUhKT1OLhR8wBaaFBzQ1NwAlkIszCQkvsbOHL7Y4q4IuEjaqq0ZQD5+GEEsJTDCMmIUhtgk1lo6QFUwJVDKLiYJNUd6/hoEAIfkECQgAAAAsAAAAABAAEAAAB2iAAIKDhIWGgiUlh4MRgyQkjIURGRiGGBmNhJWHm4uen4ICCA+IkIsDCQkVACWmhwSpFqAABQoQF6ALTkWFnYMrVlhWvIKTlSAiJiVVPqlGhJkhqShHV1lCW4cMqSkAR1ofiwsjJyqGgQAh+QQJCAAAACwAAAAAEAAQAAAHZ4AAgoOEhYaCJSWHgxGDJCSMhREZGIYYGY2ElYebi56fhyWQniSKAKKfpaCLFlAPhl0gXYNGEwkhGYREUywag1wJwSkHNDU3D0kJYIMZQwk8MjPBLx9eXwuETVEyAC/BOKsuEjYFhoEAIfkECQgAAAAsAAAAABAAEAAAB2eAAIKDhIWGgiUlh4MRgyQkjIURGRiGGBmNhJWHm4ueICImip6CIQkJKJ4kigynKaqKCyMnKqSEK05StgAGQRxPYZaENqccFgIID4KXmQBhXFkzDgOnFYLNgltaSAAEpxa7BQoQF4aBACH5BAkIAAAALAAAAAAQABAAAAdogACCg4SFggJiPUqCJSWGgkZjCUwZACQkgxGEXAmdT4UYGZqCGWQ+IjKGGIUwPzGPhAc0NTewhDOdL7Ykji+dOLuOLhI2BbaFETICx4MlQitdqoUsCQ2vhKGjglNfU0SWmILaj43M5oEAOwAAAAAAAAAAAA==) no-repeat center left; } #full_list { padding: 0; list-style: none; margin-left: 0; } #full_list ul { padding: 0; } #full_list li { padding: 5px; padding-left: 12px; margin: 0; font-size: 1.1em; list-style: none; } #noresults { padding: 7px 12px; } #content.insearch #noresults { margin-left: 7px; } ul.collapsed ul, ul.collapsed li { display: none; } ul.collapsed.search_uncollapsed { display: block; } ul.collapsed.search_uncollapsed li { display: list-item; } li a.toggle { cursor: default; position: relative; left: -5px; top: 4px; text-indent: -999px; width: 10px; height: 9px; margin-left: -10px; display: block; float: left; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK8AAACvABQqw0mAAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTM5jWRgMAAAAVdEVYdENyZWF0aW9uIFRpbWUAMy8xNC8wOeNZPpQAAAE2SURBVDiNrZTBccIwEEXfelIAHUA6CZ24BGaWO+FuzZAK4k6gg5QAdGAq+Bxs2Yqx7BzyL7Llp/VfzZeQhCTc/ezuGzKKnKSzpCxXJM8fwNXda3df5RZETlIt6YUzSQDs93sl8w3wBZxCCE10GM1OcWbWjB2mWgEH4Mfdyxm3PSepBHibgQE2wLe7r4HjEidpnXMYdQPKEMJcsZ4zs2POYQOcaPfwMVOo58zsAdMt18BuoVDPxUJRacELbXv3hUIX2vYmOUvi8C8ydz/ThjXrqKqqLbDIAdsCKBd+Wo7GWa7o9qzOQHVVVXeAbs+yHHCH4aTsaCOQqunmUy1yBUAXkdMIfMlgF5EXLo2OpV/c/Up7jG4hhHcYLgWzAZXUc2b2ixsfvc/RmNNfOXD3Q/oeL9axJE1yT9IOoUu6MGUkAAAAAElFTkSuQmCC) no-repeat bottom left; } li.collapsed a.toggle { opacity: 0.5; cursor: default; background-position: top left; } li { color: #888; cursor: pointer; } li.deprecated { text-decoration: line-through; font-style: italic; } li.r1 { background: #f0f0f0; } li.r2 { background: #fafafa; } li:hover { background: #ddd; } li small:before { content: "("; } li small:after { content: ")"; } li small.search_info { display: none; } a:link, a:visited { text-decoration: none; color: #05a; } li.clicked { background: #05a; color: #ccc; } li.clicked a:link, li.clicked a:visited { color: #eee; } li.clicked a.toggle { opacity: 0.5; background-position: bottom right; } li.collapsed.clicked a.toggle { background-position: top right; } #search input { border: 1px solid #bbb; -moz-border-radius: 3px; -webkit-border-radius: 3px; } #nav { margin-left: 10px; font-size: 0.9em; display: none; color: #aaa; } #nav a:link, #nav a:visited { color: #358; } #nav a:hover { background: transparent; color: #5af; } .frames #content h1 { margin-top: 0; } .frames li { white-space: nowrap; cursor: normal; } .frames li small { display: block; font-size: 0.8em; } .frames li small:before { content: ""; } .frames li small:after { content: ""; } .frames li small.search_info { display: none; } .frames #search { width: 170px; position: static; margin: 3px; margin-left: 10px; font-size: 0.9em; color: #888; padding-left: 0; padding-right: 24px; } .frames #content.insearch #search { background-position: center right; } .frames #search input { width: 110px; } .frames #nav { display: block; } #full_list.insearch li { display: none; } #full_list.insearch li.found { display: list-item; padding-left: 10px; } #full_list.insearch li a.toggle { display: none; } #full_list.insearch li small.search_info { display: block; } state-machine-1.2.0/examples/doc/css/style.css0000644000175000017500000004121212305405267020643 0ustar boutilboutilbody { padding: 0 20px; font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; font-size: 13px; } body.frames { padding: 0 5px; } h1 { font-size: 25px; margin: 1em 0 0.5em; padding-top: 4px; border-top: 1px dotted #d5d5d5; } h1.noborder { border-top: 0px; margin-top: 0; padding-top: 4px; } h1.title { margin-bottom: 10px; } h1.alphaindex { margin-top: 0; font-size: 22px; } h2 { padding: 0; padding-bottom: 3px; border-bottom: 1px #aaa solid; font-size: 1.4em; margin: 1.8em 0 0.5em; } h2 small { font-weight: normal; font-size: 0.7em; display: block; float: right; } .clear { clear: both; } .inline { display: inline; } .inline p:first-child { display: inline; } .docstring h1, .docstring h2, .docstring h3, .docstring h4 { padding: 0; border: 0; border-bottom: 1px dotted #bbb; } .docstring h1 { font-size: 1.2em; } .docstring h2 { font-size: 1.1em; } .docstring h3, .docstring h4 { font-size: 1em; border-bottom: 0; padding-top: 10px; } .summary_desc .object_link, .docstring .object_link { font-family: monospace; } .rdoc-term { padding-right: 25px; font-weight: bold; } .rdoc-list p { margin: 0; padding: 0; margin-bottom: 4px; } /* style for
          */ #filecontents li > p, .docstring li > p { margin: 0px; } #filecontents ul, .docstring ul { padding-left: 20px; } /* style for
          */ #filecontents dl, .docstring dl { border: 1px solid #ccc; } #filecontents dt, .docstring dt { background: #ddd; font-weight: bold; padding: 3px 5px; } #filecontents dd, .docstring dd { padding: 5px 0px; margin-left: 18px; } #filecontents dd > p, .docstring dd > p { margin: 0px; } .note { color: #222; -moz-border-radius: 3px; -webkit-border-radius: 3px; background: #e3e4e3; border: 1px solid #d5d5d5; padding: 7px 10px; display: block; } .note.todo { background: #ffffc5; border-color: #ececaa; } .note.returns_void { background: #efefef; } .note.deprecated { background: #ffe5e5; border-color: #e9dada; } .note.private { background: #ffffc5; border-color: #ececaa; } .note.title { text-transform: lowercase; padding: 1px 5px; font-size: 0.9em; font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; display: inline; } .summary_signature + .note.title { margin-left: 7px; } h1 .note.title { font-size: 0.5em; font-weight: normal; padding: 3px 5px; position: relative; top: -3px; text-transform: capitalize; } .note.title.constructor { color: #fff; background: #6a98d6; border-color: #6689d6; } .note.title.writeonly { color: #fff; background: #45a638; border-color: #2da31d; } .note.title.readonly { color: #fff; background: #6a98d6; border-color: #6689d6; } .note.title.private { background: #d5d5d5; border-color: #c5c5c5; } .discussion .note { margin-top: 6px; } .discussion .note:first-child { margin-top: 0; } h3.inherited { font-style: italic; font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; font-weight: normal; padding: 0; margin: 0; margin-top: 12px; margin-bottom: 3px; font-size: 13px; } p.inherited { padding: 0; margin: 0; margin-left: 25px; } #filecontents dl.box, dl.box { border: 0; width: 520px; font-size: 1em; } #filecontents dl.box dt, dl.box dt { float: left; display: block; width: 100px; margin: 0; text-align: right; font-weight: bold; background: transparent; border: 1px solid #aaa; border-width: 1px 0px 0px 1px; padding: 6px 0; padding-right: 10px; } #filecontents dl.box dd, dl.box dd { float: left; display: block; width: 380px; margin: 0; padding: 6px 0; padding-right: 20px; border: 1px solid #aaa; border-width: 1px 1px 0 0; } #filecontents dl.box .last, dl.box .last { border-bottom: 1px solid #aaa; } #filecontents dl.box .r1, dl.box .r1 { background: #eee; } ul.toplevel { list-style: none; padding-left: 0; font-size: 1.1em; } #files { padding-left: 15px; font-size: 1.1em; } #files { padding: 0; } #files li { list-style: none; display: inline; padding: 7px 12px; line-height: 35px; } dl.constants { margin-left: 40px; } dl.constants dt { font-weight: bold; font-size: 1.1em; margin-bottom: 5px; } dl.constants dd { width: 75%; white-space: pre; font-family: monospace; margin-bottom: 18px; } .summary_desc { margin-left: 32px; display: block; font-family: sans-serif; } .summary_desc tt { font-size: 0.9em; } dl.constants .note { padding: 2px 6px; padding-right: 12px; margin-top: 6px; } dl.constants .docstring { margin-left: 32px; font-size: 0.9em; font-weight: normal; } dl.constants .tags { padding-left: 32px; font-size: 0.9em; line-height: 0.8em; } dl.constants .discussion *:first-child { margin-top: 0; } dl.constants .discussion *:last-child { margin-bottom: 0; } .method_details { border-top: 1px dotted #aaa; margin-top: 15px; padding-top: 0; } .method_details.first { border: 0; } p.signature { font-size: 1.1em; font-weight: normal; font-family: Monaco, Consolas, Courier, monospace; padding: 6px 10px; margin-top: 18px; background: #e5e8ff; border: 1px solid #d8d8e5; -moz-border-radius: 3px; -webkit-border-radius: 3px; } p.signature tt { font-family: Monaco, Consolas, Courier, monospace; } p.signature .overload { display: block; } p.signature .extras { font-weight: normal; font-family: sans-serif; color: #444; font-size: 1em; } p.signature .aliases { display: block; font-weight: normal; font-size: 0.9em; font-family: sans-serif; margin-top: 0px; color: #555; } p.signature .aliases .names { font-family: Monaco, Consolas, Courier, monospace; font-weight: bold; color: #000; font-size: 1.2em; } .tags h3 { font-size: 1em; margin-bottom: 0; } .tags ul { margin-top: 5px; padding-left: 30px; list-style: square; } .tags ul li { margin-bottom: 3px; } .tags ul .name { font-family: monospace; font-weight: bold; } .tags ul .note { padding: 3px 6px; } .tags { margin-bottom: 12px; } .tags .examples h3 { margin-bottom: 10px; } .tags .examples h4 { padding: 0; margin: 0; margin-left: 15px; font-weight: bold; font-size: 0.9em; } .tags .overload .overload_item { list-style: none; margin-bottom: 25px; } .tags .overload .overload_item .signature { padding: 2px 8px; background: #e5e8ff; border: 1px solid #d8d8e5; -moz-border-radius: 3px; -webkit-border-radius: 3px; } .tags .overload .signature { margin-left: -15px; font-family: monospace; display: block; font-size: 1.1em; } .tags .overload .docstring { margin-top: 15px; } .defines { display: none; } #method_missing_details .notice.this { position: relative; top: -8px; color: #888; padding: 0; margin: 0; } .showSource { font-size: 0.9em; } .showSource a:link, .showSource a:visited { text-decoration: none; color: #666; } #content a:link, #content a:visited { text-decoration: none; color: #05a; } #content a:hover { background: #ffffa5; } div.docstring, p.docstring { margin-right: 6em; } ul.summary { list-style: none; font-family: monospace; font-size: 1em; line-height: 1.5em; } ul.summary a:link, ul.summary a:visited { text-decoration: none; font-size: 1.1em; } ul.summary li { margin-bottom: 5px; } .summary .summary_signature { padding: 1px 10px; background: #eaeaff; border: 1px solid #dfdfe5; -moz-border-radius: 3px; -webkit-border-radius: 3px; } .summary_signature:hover { background: #eeeeff; cursor: pointer; } ul.summary.compact li { display: inline-block; margin: 0px 5px 0px 0px; line-height: 2.6em;} ul.summary.compact .summary_signature { padding: 5px 7px; padding-right: 4px; } #content .summary_signature:hover a:link, #content .summary_signature:hover a:visited { background: transparent; color: #48f; } p.inherited a { font-family: monospace; font-size: 0.9em; } p.inherited { word-spacing: 5px; font-size: 1.2em; } p.children { font-size: 1.2em; } p.children a { font-size: 0.9em; } p.children strong { font-size: 0.8em; } p.children strong.modules { padding-left: 5px; } ul.fullTree { display: none; padding-left: 0; list-style: none; margin-left: 0; margin-bottom: 10px; } ul.fullTree ul { margin-left: 0; padding-left: 0; list-style: none; } ul.fullTree li { text-align: center; padding-top: 18px; padding-bottom: 12px; background: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAHtJREFUeNqMzrEJAkEURdGzuhgZbSoYWcAWoBVsB4JgZAGmphsZCZYzTQgWNCYrDN9RvMmHx+X916SUBFbo8CzD1idXrLErw1mQttgXtyrOcQ/Ny5p4Qh+2XqLYYazsPWNTiuMkRxa4vcV+evuNAUOLIx5+c2hyzv7hNQC67Q+/HHmlEwAAAABJRU5ErkJggg==) no-repeat top center; } ul.fullTree li:first-child { padding-top: 0; background: transparent; } ul.fullTree li:last-child { padding-bottom: 0; } .showAll ul.fullTree { display: block; } .showAll .inheritName { display: none; } #search { position: absolute; right: 14px; top: 0px; } #search a:link, #search a:visited { display: block; float: left; margin-right: 4px; padding: 8px 10px; text-decoration: none; color: #05a; border: 1px solid #d8d8e5; -moz-border-radius-bottomleft: 3px; -moz-border-radius-bottomright: 3px; -webkit-border-bottom-left-radius: 3px; -webkit-border-bottom-right-radius: 3px; background: #eaf0ff; -webkit-box-shadow: -1px 1px 3px #ddd; } #search a:hover { background: #f5faff; color: #06b; } #search a.active { background: #568; padding-bottom: 20px; color: #fff; border: 1px solid #457; -moz-border-radius-topleft: 5px; -moz-border-radius-topright: 5px; -webkit-border-top-left-radius: 5px; -webkit-border-top-right-radius: 5px; } #search a.inactive { color: #999; } .frames #search { display: none; } .inheritanceTree, .toggleDefines { float: right; } #menu { font-size: 1.3em; color: #bbb; top: -5px; position: relative; } #menu .title, #menu a { font-size: 0.7em; } #menu .title a { font-size: 1em; } #menu .title { color: #555; } #menu a:link, #menu a:visited { color: #333; text-decoration: none; border-bottom: 1px dotted #bbd; } #menu a:hover { color: #05a; } #menu .noframes { display: none; } .frames #menu .noframes { display: inline; float: right; } #footer { margin-top: 15px; border-top: 1px solid #ccc; text-align: center; padding: 7px 0; color: #999; } #footer a:link, #footer a:visited { color: #444; text-decoration: none; border-bottom: 1px dotted #bbd; } #footer a:hover { color: #05a; } #listing ul.alpha { font-size: 1.1em; } #listing ul.alpha { margin: 0; padding: 0; padding-bottom: 10px; list-style: none; } #listing ul.alpha li.letter { font-size: 1.4em; padding-bottom: 10px; } #listing ul.alpha ul { margin: 0; padding-left: 15px; } #listing ul small { color: #666; font-size: 0.7em; } li.r1 { background: #f0f0f0; } li.r2 { background: #fafafa; } #search_frame { z-index: 9999; background: #fff; display: none; position: absolute; top: 36px; right: 18px; width: 500px; height: 80%; overflow-y: scroll; border: 1px solid #999; border-collapse: collapse; -webkit-box-shadow: -7px 5px 25px #aaa; -moz-box-shadow: -7px 5px 25px #aaa; -moz-border-radius: 2px; -webkit-border-radius: 2px; } #content ul.summary li.deprecated .summary_signature a:link, #content ul.summary li.deprecated .summary_signature a:visited { text-decoration: line-through; font-style: italic; } #toc { padding: 20px; padding-right: 30px; border: 1px solid #ddd; float: right; background: #fff; margin-left: 20px; margin-bottom: 20px; max-width: 300px; -webkit-box-shadow: -2px 2px 6px #bbb; -moz-box-shadow: -2px 2px 6px #bbb; z-index: 5000; position: relative; } #toc.nofloat { float: none; max-width: none; border: none; padding: 0; margin: 20px 0; -webkit-box-shadow: none; -moz-box-shadow: none; } #toc.nofloat.hidden { padding: 0; background: 0; margin-bottom: 5px; } #toc .title { margin: 0; } #toc ol { padding-left: 1.8em; } #toc li { font-size: 1.1em; line-height: 1.7em; } #toc > ol > li { font-size: 1.1em; font-weight: bold; } #toc ol > ol { font-size: 0.9em; } #toc ol ol > ol { padding-left: 2.3em; } #toc ol + li { margin-top: 0.3em; } #toc.hidden { padding: 10px; background: #f6f6f6; -webkit-box-shadow: none; -moz-box-shadow: none; } #filecontents h1 + #toc.nofloat { margin-top: 0; } /* syntax highlighting */ .source_code { display: none; padding: 3px 8px; border-left: 8px solid #ddd; margin-top: 5px; } #filecontents pre.code, .docstring pre.code, .source_code pre { font-family: monospace; } #filecontents pre.code, .docstring pre.code { display: block; } .source_code .lines { padding-right: 12px; color: #555; text-align: right; } #filecontents pre.code, .docstring pre.code, .tags pre.example { padding: 5px 12px; margin-top: 4px; border: 1px solid #eef; background: #f5f5ff; } pre.code { color: #000; } pre.code .info.file { color: #555; } pre.code .val { color: #036A07; } pre.code .tstring_content, pre.code .heredoc_beg, pre.code .heredoc_end, pre.code .qwords_beg, pre.code .qwords_end, pre.code .tstring, pre.code .dstring { color: #036A07; } pre.code .fid, pre.code .rubyid_new, pre.code .rubyid_to_s, pre.code .rubyid_to_sym, pre.code .rubyid_to_f, pre.code .dot + pre.code .id, pre.code .rubyid_to_i pre.code .rubyid_each { color: #0085FF; } pre.code .comment { color: #0066FF; } pre.code .const, pre.code .constant { color: #585CF6; } pre.code .symbol { color: #C5060B; } pre.code .kw, pre.code .label, pre.code .rubyid_require, pre.code .rubyid_extend, pre.code .rubyid_include { color: #0000FF; } pre.code .ivar { color: #318495; } pre.code .gvar, pre.code .rubyid_backref, pre.code .rubyid_nth_ref { color: #6D79DE; } pre.code .regexp, .dregexp { color: #036A07; } pre.code a { border-bottom: 1px dotted #bbf; } state-machine-1.2.0/examples/doc/class_list.html0000644000175000017500000000420112305405267021224 0ustar boutilboutil

          Class List

          state-machine-1.2.0/examples/doc/AutoShop.html0000644000175000017500000016754312305405267020651 0ustar boutilboutil Class: AutoShop — Documentation by YARD 0.7.5

          Class: AutoShop

          Inherits:
          Object
          • Object
          show all
          Defined in:
          auto_shop.rb

          State Machines

          This class contains 1 state machine(s).

          state

          State machine diagram for state

          Instance Attribute Summary (collapse)

          Class Method Summary (collapse)

          Instance Method Summary (collapse)

          Instance Attribute Details

          - (Object) state

          Gets the current attribute value for the machine

          Returns:

          • The attribute value

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          Class Method Details

          + (String) human_state_event_name(event)

          Gets the humanized name for the given event.

          Parameters:

          • event (Symbol)

            The event to look up

          Returns:

          • (String)

            The human event name

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          + (String) human_state_name(state)

          Gets the humanized name for the given state.

          Parameters:

          • state (Symbol)

            The state to look up

          Returns:

          • (String)

            The human state name

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          Instance Method Details

          - (Boolean) available?

          Checks whether :available is the current state.

          Returns:

          • (Boolean)

            true if this is the current state, otherwise false

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (Boolean) busy?

          Checks whether :busy is the current state.

          Returns:

          • (Boolean)

            true if this is the current state, otherwise false

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (Boolean) can_fix_vehicle?(requirements = {})

          Checks whether :fix_vehicle can be fired.

          Parameters:

          • requirements (Hash) (defaults to: {})

            The transition requirements to test against

          Options Hash (requirements):

          • :from (Symbol) — default: the current state

            One or more initial states

          • :to (Symbol)

            One or more target states

          • :guard (Boolean)

            Whether to guard transitions with conditionals

          Returns:

          • (Boolean)

            true if :fix_vehicle can be fired, otherwise false

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (Boolean) can_tow_vehicle?(requirements = {})

          Checks whether :tow_vehicle can be fired.

          Parameters:

          • requirements (Hash) (defaults to: {})

            The transition requirements to test against

          Options Hash (requirements):

          • :from (Symbol) — default: the current state

            One or more initial states

          • :to (Symbol)

            One or more target states

          • :guard (Boolean)

            Whether to guard transitions with conditionals

          Returns:

          • (Boolean)

            true if :tow_vehicle can be fired, otherwise false

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (Boolean) fire_state_event(event, *args)

          Fires an arbitrary state event with the given argument list

          Parameters:

          • event (Symbol)

            The name of the event to fire

          • args

            Optional arguments to include in the transition

          Returns:

          • (Boolean)

            true if the event succeeds, otherwise false

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (Boolean) fix_vehicle(*args)

          Fires the :fix_vehicle event.

          Parameters:

          • args (Array)

            Optional arguments to include in transition callbacks

          Returns:

          • (Boolean)

            true if the transition succeeds, otherwise false

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (Boolean) fix_vehicle!(*args)

          Fires the :fix_vehicle event, raising an exception if it fails.

          Parameters:

          • args (Array)

            Optional arguments to include in transition callbacks

          Returns:

          • (Boolean)

            true if the transition succeeds

          Raises:

          • (StateMachine::InvalidTransition)

            If the transition fails

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (StateMachine::Transition) fix_vehicle_transition(requirements = {})

          Gets the next transition that would be performed if :fix_vehicle were to be fired.

          Parameters:

          • requirements (Hash) (defaults to: {})

            The transition requirements to test against

          Options Hash (requirements):

          • :from (Symbol) — default: the current state

            One or more initial states

          • :to (Symbol)

            One or more target states

          • :guard (Boolean)

            Whether to guard transitions with conditionals

          Returns:

          • (StateMachine::Transition)

            The transition that would be performed or nil

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (String) human_state_name

          Gets the human-readable name of the state for the current value.

          Returns:

          • (String)

            The human-readable state name

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (Boolean) state?(state_name)

          Checks the given state name against the current state.

          Parameters:

          • state_name (Symbol)

            The name of the state to check

          Returns:

          • (Boolean)

            True if they are the same state, otherwise false

          Raises:

          • (IndexError)

            If the state name is invalid

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (Array<Symbol>) state_events(requirements = {})

          Gets the list of events that can be fired on the current state (uses the unqualified event names)

          Parameters:

          • requirements (Hash) (defaults to: {})

            The transition requirements to test against

          Options Hash (requirements):

          • :from (Symbol) — default: the current state

            One or more initial states

          • :to (Symbol)

            One or more target states

          • :on (Symbol)

            One or more events that fire the transition

          • :guard (Boolean)

            Whether to guard transitions with conditionals

          Returns:

          • (Array<Symbol>)

            The list of event names

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (Symbol) state_name

          Gets the internal name of the state for the current value.

          Returns:

          • (Symbol)

            The internal name of the state

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (StateMachine::PathCollection) state_paths(requirements = {})

          Gets the list of sequences of transitions that can be run for the current state

          Parameters:

          • requirements (Hash) (defaults to: {})

            The transition requirements to test against

          Options Hash (requirements):

          • :from (Symbol) — default: the current state

            The initial state

          • :to (Symbol)

            The target state

          • :deep (Boolean)

            Whether to enable deep searches for the target state

          • :guard (Boolean)

            Whether to guard transitions with conditionals

          Returns:

          • (StateMachine::PathCollection)

            The collection of paths

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (Array<StateMachine::Transition>) state_transitions(requirements = {})

          Gets the list of transitions that can be made for the current state

          Parameters:

          • requirements (Hash) (defaults to: {})

            The transition requirements to test against

          Options Hash (requirements):

          • :from (Symbol) — default: the current state

            One or more initial states

          • :to (Symbol)

            One or more target states

          • :on (Symbol)

            One or more events that fire the transition

          • :guard (Boolean)

            Whether to guard transitions with conditionals

          Returns:

          • (Array<StateMachine::Transition>)

            The available transitions

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (Boolean) tow_vehicle(*args)

          Fires the :tow_vehicle event.

          Parameters:

          • args (Array)

            Optional arguments to include in transition callbacks

          Returns:

          • (Boolean)

            true if the transition succeeds, otherwise false

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (Boolean) tow_vehicle!(*args)

          Fires the :tow_vehicle event, raising an exception if it fails.

          Parameters:

          • args (Array)

            Optional arguments to include in transition callbacks

          Returns:

          • (Boolean)

            true if the transition succeeds

          Raises:

          • (StateMachine::InvalidTransition)

            If the transition fails

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end

          - (StateMachine::Transition) tow_vehicle_transition(requirements = {})

          Gets the next transition that would be performed if :tow_vehicle were to be fired.

          Parameters:

          • requirements (Hash) (defaults to: {})

            The transition requirements to test against

          Options Hash (requirements):

          • :from (Symbol) — default: the current state

            One or more initial states

          • :to (Symbol)

            One or more target states

          • :guard (Boolean)

            Whether to guard transitions with conditionals

          Returns:

          • (StateMachine::Transition)

            The transition that would be performed or nil

          
          
          4
          5
          6
          7
          8
          9
          10
          11
          12
          # File 'auto_shop.rb', line 4
          
          state_machine :initial => :available do
            event :tow_vehicle do
              transition :available => :busy
            end
            
            event :fix_vehicle do
              transition :busy => :available 
            end
          end
          state-machine-1.2.0/examples/traffic_light.rb0000644000175000017500000000030312305405267020562 0ustar boutilboutilrequire 'state_machine' class TrafficLight state_machine :initial => :stop do event :cycle do transition :stop => :proceed, :proceed => :caution, :caution => :stop end end end state-machine-1.2.0/examples/Gemfile.lock0000644000175000017500000000027412305405267017661 0ustar boutilboutilGEM remote: http://www.rubygems.org/ specs: ruby-graphviz (1.0.0) state_machine (1.2.0) yard (0.7.5) PLATFORMS ruby DEPENDENCIES ruby-graphviz state_machine yard state-machine-1.2.0/examples/Car_state.png0000644000175000017500000031543712305405267020064 0ustar boutilboutilPNG  IHDR%O"bKGD IDATxwTg>ka J lR_,%Kb4v#5jł *ݿ?e̖s\ڙg1cL`jB`10cLNp)a1Ƙ\Rc1 :cΝ;شi^| kkkL6 Bb1!oǏe˖Gaa!b1pI1Ƙ7U5k֠"ҥK'c1jI!)1ƪAϞ=Qft"P"?\J^^^pppbD"hذcL~@WƪIII /_ohܸБcLƪ6m \Hc\Jc1&0cL.p)a1Ƙ\Rc11crK c1c.%1 \Jc1&0cL.p)a1Ƙ\Rc11crK c1c.%1 \Jc1&0cL.p)a1Ƙ\Rc11crK c1c.%1 \Jc1&0cL.p)a1Ƙ\Rc11crK c1c.%1 \Jc1&0cL.JMMř3g,|2?,\WW l1&DDDB`LAGGR72Nc/0&#?~<444>q!c7.%رcQXXujԨ}VS"_\J޽{f͚1cTl c);.%ɐX, 455}...՜1teLѵkr_333ӧOƟc2&c;wFݺuYWWW.$1!c2&ʽ b1×o7n@۶m,Jcϔ0V ڴi&MHĉ crK cdĉK8;vcLƪѤIڶmGH1&WL c[L0A41&L cRRPP#..HHH@bb",000Qvm4mVVVBfP~}1ƪƪQQQ Ehh(Ñ 5554j͚5C&M5kBOOEEEعs'M,dgg#;;/^ݻw@:u'''8::R#f1RG(,,SNhҤ УGnVVVҪpoF˖-+|=%%z*Ξ=#==1b&LvcLP\J(`޽@>}0zh 4龋`"..mڴ 3331ƪ ԩSXr%Ο?/)cǎ-wreٳ{ENN&M\L1& \J{ ȑ#pн{w;vē'OXYY 1ƪo f oF^0bԭ[QQQ8q҂'uVDFFM6X`cK ckkkdff8t섎Ab&L[O?a˖-hٲ%8 t4(\J CVyfYW\Ax899aԨQ>|cw\J*))w}''' ..ӦMbo~:ڵk0c1)o_ƪ(99 e˰vZFhBݻ#99YH1&oһ|2Q8r!55={.Ξ= ]]]#1n߾={_~H$:x z&Mѣ<1&8.%Lix:t <uuu#ɝDtG@@qc*K SJ۷/^Zz5<<<Sa<Е)+V8ppppƍ1k, 1L S:gϞE~p 0@8 c„ |2xƘ 0͛cXjqJNNйsgl۶M81ĥ)3f "".]X,:¹y&lmmΝ; 1b0 +ldi ATT;XR”K.Ɔ вЪU+̞=3f:cLp)aJaǎXp!add$twAL4 ]qc*K SxEEEhڴ)k̚5K8J=zcՆ)a oϞ=ԩST-ZM6!%%E(1)Z f͂qʐ!C`iiu 1"0/_K(JG$a…_!tƘ Rچ ƒ[edȑF``Qc*K SX/&t%1n8޽[(1)={ BGQjnnnݻwSr\J $ՠ}hӦ Sr\JBz*,t 1且0tiXZZR(*w޸{.}*tƘRNBB6m*t cee<_ cLf0͛ JD"D"Bƈ6cM\JIHH@f̈́/t ƘRJff&?.Wf͐ t ƘRJjj*VZ'QMx1cJK S(_Kmc/rss1i$nnnx; FFFDF0c dee佷/ϝ;Ƙ;wdYVV|||`ii ---yyyŋsLu;&3%e1ƤIDD$@bJ;#++ zzzRf\_.3V%77D~~>2d]VJ9n377Ѿ}{2]]]V)))mmm#'':::u$gO~y\ cL& S(heGQ,+k׮I&3gv܉[nU7 ɛfΜ ]]],YR~+!ߥ⡡mTׯer1.%LɐE)tR/\hѢdɓ'ĉJkڵXt)ƢDEEI^377}Ge|Wx N8k7n܀!ޙW^Ezz:\Wʕ+³gϠ͛:t@Ю];hjjV11`|wBGQ+\g߾}9s&RSS%}h'Oʔ(ddd@KK ڵC`oo.]q296Ƙ|RҜ9s,L6]0\xHMMt.][n/`BƘbRƥK}QQQذa\]] ڵcR=QRR۷o#,,  ãG:K.ڵ+v ccc1&[\J\{!g$$$E0aƍe˖ѣG1HJJBz޽{ B? . "l]vE3332*ɝ ! aaa0553Ə[[[z.\=zu XOÇ8!""Br'** hݺ5z޽{{AƘp)ar#""[nb :ǏG~Dƍ jC^ dgg… 8}4N>7n@CC;v:&T`۶muڷo 044WX@ܺuK>+֭[h޼q˗8s挤<~ptt+++c2RjGD8wmۆ@SScǎŔ)S`ggQJOOGFc|2JF "Ÿ)tPdddAҫW/Ԯ][蘌)5.%ڤ`ǎغu+޽ |Wpvv .DHH^ZıC֭qUo^8ժQQQr%M60`Ν;ƤK X/ػw/455憩S~ҼoJJJ90p@lǏGZZ=*tG9u?xO>:‰Çqa ѯ_? 1ťU!88 OcB[[[<=z@-yj߿2x5Zh:%%,, ӧFCryf-\JG+((}zjܼy}ł d~2n޼ |8}Llٲ֭CRRƍ9sH.iYb֭[j|v؁ӧ#22m۶:RƱc(((#FpAa*K gϞaݺuK"SN_ |߿?rssq9dX 7oބ֭[)SGH ʱcPPPGGG=_|j֬)tDƪ Vjժ]h/_5ƍȵ t  :J%  $X-ƪ{aʕسgׯ___ASSSh%44})tTPPӧz*?1Wdee ĩS/...ٳ'?)%.%L">>+Wľ}`aa___(叝;wbʔ)صk\\\#W쌈?M4:@RR"22uرc1¥֭[Xr%+++YiXj.]#G_~BǑ^^^ػw/n2ؽ{w^">>ڵW_}qXx}.%*X|9>>C 6m۶QaaбdKAHCC-ZD԰aCڵ+Ӕ)S(77W2L7oޤ֭[S ֭[BGbՠN:Ecƌ! U͝;޽+t4*ĥDЪUȈjժEWV?Ŵ}vUb$xbQ=9B&&&Ԯ];se$XLc0x;277'HD}PAA+K*,,_ԩCxb:̄R֭IMM$_~v6>;>ٳG#l4iRIrrбرc4dRWWuҒ%K2D|H-:uꐆ9;;SddбR$nݺEz"HDcǎWH2F$%F*]6:uɓ'+se˖C˖-SAߧ!CrqqgϞիWNEAARuؑ~wD%''bK. Iv"RA/֭[?|I__4iBwV/_瓎hтΞ=[455%gttt9 IDATeŋ4j(T~}󣔔c1¥DA瓿?Q:uhT\\,tjsQ֮ RSS#ԤJoÇ4adiiI7o|M=yfΜITvmZfMYw~6sÇiTfM%www:S\JPppR/~ZHr { tWi'wwwҢӲe(11QG񊋋)44&L ?SNNNyacn/_^"ʢ7RfHMMFɗLq)Q nݢ1bRgϒed餥E=}tc1%ĥD… ISS)44THrɓ'T~}/se']65lؐM&ѩS͍I]]itI=ӧO) &N()"x⏺UֶK]4xma($$zEޞ988Н;w0߿O4tJOUdTӾ111]MMbbb-ƍI]]]aS\%%%{nSժU6mTtlL4 'OƴipE4o\X !##Æ uuJo߾};vBp%%%/@T 0xu1DpuuE||<&LӧC:\J͛ñcp1ZYIpqqArr2?]]J/..wȑ#ePv˔ uuuhhh%F ooodffVK&Hm۶!;;_u쓱7 ѹsgxxx ##ChL>U,6oLԽ{wzqiiiQddGߟ6l(TcyI,,,hܸq/^,D&t1As0o>Ϩ^zt0R222$wXx1? O"v֭yzz U󔙙)YLR?8yc+ruu%4n8JJJ:2.% &&6mJfffti(XեߛAtQ$<233/R(ǩaÆdjj*7Nt*_бcG4j111իБNrr2^޽7cƍرcBBB 7obԨQpuuc1)R0a̜98y$̄p ؿ;wQ㱷1cƌ)Sn& a;wwEVgcOĥ#}wވБ֕+Wc̘1U΍7PTTTRêݺuӋ޽{밵|}}QXX(t,GQQ0c a޽ԋ+99_|rOVll,`ii)t2D"6mڄ۷ocBa&&&8x 6mڄ~ ]tABBбX%q)@FF ݻwСC;wБZqq1 wJO!_XmVJǰdɭ)S ::`cc]v Urܻw:uB\\"""0d#)+V 44AAAY'o/11͚5B2%ϧvژ={QPfoooL4 :{.%oDǎQF \|mڴ: ˱n:JeW=r]Jf!""B8UHCC~~~8|0~wt?:7>|z#Ν;H'OѣGS*,..Çz< ҄^C fϞ]mO/f +W //8sБX9M6/)SC[[[H ΨUl"|hРdH$H$>S>bbbd5k"55U&.OU~?# cմiS\t }E~DžZΨ|)!" ?֭[Ibcc}}}m􏵱ԶYÇL]fM?S?~<|||Z=Sسg֭[K?GffбQ鿾Ř2e V^}=):x ֮]͛7e˖Rvi)ys,pImo2ۗAIII矅XM>ΝCTT:wG AKIAA 88&be%&&bɘ:u*\\\)myahhlcTJz0w\\^:cֹsg\zpppիWT`8{,N> GGG#)Ҳ׸qcO2GVVSޘϟ ҂-~w-ƍӧPV-xzz)o҂9|}}Wc±c I&AOOpssyOPP`ddMMM4j3f̐zyɩ s΅q7otttVfff8<ѳgO9rDHTLzz:u֍ԩC7n:Y`ݻwe@z?]eeddPƍ%z&&&3eʔrQrssή{Mŕ:ƙ3gJ7ywennN-ZTqs 666$յܟٳ ׯ+y۶mAqqqZ1yRTTDޤN?qTJT%sssJLL:9{,eʔoF;<<ܹsT~ KIΝ)!!rrrחPZ*XPaa!ݺuvJh֭:Fk׮MOdnnNW>7RNNQhh( ##w2Rnnn2k,@ZJ%z6mhС~cfݺuN3f̠")%)))dccC͚5Ǐ G餤P hȑ2WeJI۶m Yk׮IzZ(e'&&ѣGqɒ%8pk!!!ZhQfyII EEE֭[ۛZjEH$966{KY&=}RYtI@^_Ezzz4l0:JQR+&++*e6rH_G}`PAKK9@D]aϗ,+))` )qO/Ԕ,{VR?DSo߿?VRcʕ+dffF>t}z\nݺBGR:;vP-s#%%[ o0MMJ>%%%}=##㣷)Ǜ5ƵkФI̙3;wĭ[*fEΜ9 ",Y䣳?bccgϞ*1yСC?߿ԥ$%%{F~~>BCCQN#)D̘1ͫJ'y_)iڴ)\Y?ɼ&o~}6/]T .Zh!YvI?&NXU9:k׮ҥKGETTGmժK,Z999~Efp<{ zYUҖ 4999 @aa!\\\`eee˖U~MMMsb=˗/Zҳ) e#F7(**‹/0o$XLfff婩4sLjԨb266Ç+}|oW^ѠAHCCիGͣ2맥ј1cHOOޞ/5zuDD+MԠA2s0ȒƆ7nxeDDS#5~~~N:R% _Q:[G}ӳt޽jNqΝ[EN"/0&5/_&M;S3%ņ 0tP("|ԩbooXr__~=֮] [[[B]]5;aiiY͉?Sr |/#tv]|7o% _~8rgQUDPSSe4ls; cM68q,X@8 OaINNFΝѰaC"IgϞFdd;Ӟ Ν;hٲ%N>^z G>s 00P(Rf͚aĈذaq'N`ذaXz5O.t $3v\Hdl͸tm&7ou{{{EQF cH>V\-[<)c˖-={6BCCx{{#&&qړ'O`̟?۷:;Ə(-add$t 0aڵkٳg 10aO1cBQH WJ֯_۷c޽hٲq_:uT(B.] 㨄BBǩYfxQիW*7S?bBGaLf֯_4,ZH(rKJIQQ`kk  Geܹ>>ӧOØx{{CSSk׮:\#F@~rۇ'O`ܹBGX WWW0`߿hjj͛B+׫W@%o ~ۜ9s`jj4w1V Z ~~~HJJ:B|ݺux N:%d CD1fXXXG&?~%K`׮]j499HHH@rr2^~ldgg#==zzzӃ>L|%zzzLi 6 Zʟ1tXXX`ƌ{Y A~ kkkHUVV/^ 6@RQ222ߴ4;wBBBRSSzzz]6%Ef͚ʒ4deeI>)iiiYfhݺ5z '''4iDjy#pw3VYΝÀ G0K_~ahh(D5|pddd 44T(RwQ :R>{ uԩ.]"44111ŰG`eef͚ u֭6333ܹs7oą   S$66vvv*tdo߾hԨn*tRSSАV\)UIMM/t9wX,}5m+bobTڻb뮋}v]ņ`AE+bl(UZH| Lw]^K8O69sfٳn4|^: ԩSϏp6DRhɒ%ԡCѡҥKӐ!C(00d2RU%C !sssJJJb&$$ٳg0ä(9s&)SYt_y{{S*UH"TTBɵ եK=j۶-D"Ty{{ӝ;wxy{=Z ڵk)55I!i٬pRjՊF:3%qqqTdIb/%%LLLgE^"[[\ mmmVZǏqJ1h >|F߿q_ “'O0`QTaڴiիq}Avpi?~dȆ5j`ɬpRtJž={XG {xyyٳgVvW\+ݻ7naСعs'vڅ={Ѯ];blٲ#IϟG6mX8Fpp0^: >RcǎK]q9$%%8p (*aƌqb]ahhm*td!SN0aR)8p{Ƶk3Q$ ڵrطo233ѷo_Q[d -[___>?e˖ӧݻ###u"Yd >|X&rŃ=,,,pQѣhڴO$TE۶mC׮]ajj: SwԩSetԉuU<|j9`kkQFaHLLdJ$G8x (PjQBD8v%a )\0x`,Z$aHΝ LuS={"44^bEZ!::% l۶ *Uص7 "!!򂷷78*^zؽ{7~7Sh߰j*DEEq մiS-['N`EZ9sVVVU2rdرc9 :Xr%(j &L޿:N 6 666aNbm~~~(R~xutqFݝu&pYu6tPTZgfj֬#GbΜ9HJJbYWFWZQr%8::*y.hڴ),,,XGD"Qիb1֮]#<u`ggOOOL6uB+S fΜe˖׬pB8;;#--M2SJQKƢqhùs琐P,uĢE0qD~M>ϟW_cƌA1k,Q8N!*Uʕ+#44uQJQr hkk~hþ}ТE TRu"!!ÇgET^?YG)4===`Ν cNJ)JnݺڵkC___sHOOǑ#GWL;FFFh3fѣ`ѴiSL2uSF͛c(Rp4h@MsyCrrrts)DEEa̘1hu}Xl(&tR%9׸qcDFFϬ(RǏۅXٷo-;v@.]`bb:߿?<[hݻcʔ)d~H!Jq]QBEIff&>} E7A"ɓM\\;///Q4Z׮]!paQdѢEx 6m: *U@__(‹ϟC"]x ܹ3(۷oJ.۳J(wwwܹu"ƨQ0o<\㲈bT^=bE)^<|HN< jժnΝӧtttXGxp޼y:J̚5 Xp!(CjԨ(1BEӧOQB~N8Q,GIqU>J"'''ٳ)f͚+V ::u+2~bbb`nnf}zzzpuueEp=vڬ+Y#.]3f` L2>0Nx /Jallf\=z...j+H$H$*s###QD 6,L֜Y%9su+mmm!>>uShQ TҥK+Y.8w:u: >D5\jԨ#:99K.>}:u+ccc^'!!xQ"`$''I/#%BLD Rd[,ɓ'̂ pmݻu+^/JsyԮ]خ kT\ubrʐH$x-(?nݺ߿?fϞ D:/J ()Uu=00eʔ{ɘ6m ̜9iiiP,QF\۶X,/ћ_{ed3glmm%Ky黠}Ĺs?y:T 8055Wˋ8p...066.Uq!996s7LMMݜɂO<==wA:""?3 aff#F|+7oSSS{tJwRR )ۼyklܸu+0H t/^P;5nܘD"׏RSS^mۖR)|rvCBBυj{&OҥK/L_%XLǏ/ Y MN Yfy^^^ߴz<ĉ 5mڔjժh"}rf#++oλSN[Я˔)M{C @y qӳg ҟƏO*UQ8@ܲ~J)J~_Tf͚ѭ[H"н{ɉЦMh߾}#F:tP^ҥKtJJJk? K:::~pEy/_N>MBfΜ)?Ȉڵk)%%ŋi3k թS>~oNSSSZvr~Ϝ9 ]v)00VZ䢤EI)))͛7O t ʕ+ Zp ۷d``@+V` ͍<<yd7oDzCgϞS-J 9rDM*Tϟ?q_xQ'N((j""}}]ʏ>}:ѣG ͝;HiϞ=100 |0}Zjʓs&%%RB^]E"ݺu@9Y%zzz"%Y4їb/>s?E{ iŬp\<<<4(QEJ(kL&СC!}v]!C2زe 3grX~Hxm۶-EZZZƌ7o&M֭[q޽/__+%RSSfQ8XRhQbhhۿ_~xxx ,, x-&O HͭÇGff&#FP{H$ºu됔 A嫈ٳpqq6b Cŵk Hp% >зo_1Y!-- aaa?..M8Zt~,YðaDP 6Li}osUd%Bޒ-eb̘1Xd [[s5k:²)jccC2iyΩҥK&33J.M֭آMN{{ly2Of^}e|2D"@ټys0a:9QF9}yNI~}&''ʫ e&+AǏTR: IS(իWZ}ƏOժU#mmm255nݺѝ;wr=~ܹ km{6oL7%JP%nݺ4mڴon.l_NѣG:(366HGGiɔظ8ݻ7R%iӦ+ucǎ̫ 6P֭W^Գg<ۂ-kBB1ʖ-KzzzNJ+Jܺ:p@233#jРܹP%uߕuKz]lh֬YdjjJ p\4()ZKDDׯf͚j˱xb(K2 |[(ŬYpqܾ}u^x XZZӧJoػw/߿X%1k,q8Yw8b)tN> %%Er 3*VZ&MRdI8HRYGƏ7nϱ~zT\Y wARRR7ncQ8XPi&&&(Q 8::B,V꧟~B@@ʅ PJSbŊ_d&T?fK..t IDATN M6~:ӕ]nݚu AM4 ϟ?ǁXG8|x-^x/_$98;;C&K8,effҥKhӦ (AϞ=_ťMSg /J*VhE7[C___ݨccchNbXFJJ \]]YGԩS.8PxQRR%>R`AAAh֬Yt/__oΝ;Ѷm[TPu5lڵ… YG8Ƌ5-Zݑ???Q4Zjj*8 _+86m4wJ)J>| E7],%''Çppp`E%sرc"##G֑4ұc@D֭(̴j ͛7%DڊnSB*$_yݻwݻ8qRSS'Noߎ=zАuN_~GڵY8Ëw XXX߿͛7J̔cgg---5RDDΜ97?ХKԪU +VƍY87%KDr٭8zj%Ǝ˗G6m퍭[͛6{ȀX,FjհvZ[ @3[n[.(̉D"1wDr(eyPkkk^(HhhhObff___DDDd/HrF[[FFF8< ]V/221c(*c?: i%VVVx2.V^~b3R"pQD|҂lllSbFSvΎuaddAaժUJpFQJQRV-<|PM+ׯ_v@ppp@=b1N}#FB 0H?>e˖իFmÆ o:=z4pQ8Nc((ݻTZD nݺM!°a0mڴﶵxbܸq[nUfd ̜9K,ARXQYmڴAڵzjQ8Nc((҂-߿担G!99(]HH0|̞=aaah߾=͛'_wDGG۷/@_axk ۷/8*M$aصkbccY84h5[ѫFchٲ%*VwbIFBҥþ} 8ڐ!CХKٓߍS3f˗/m6QԂyfQ8N#((iܸ1nܸ5۷Qn\UhCVZ8rvڅ3g*1ذaLMMW 6lD"Q]c?~+WĞ={P\9qԂ!aڵdԞҊ{{{yo޼QV-<< 6dC兞={SNxw/([l*U|}}w^~[k>"""~ -[dG=_ƩSXG8aÆŸy󦲺hhР zŋ8u6mcc>G$RJEQF0sL޽h(i;vw1jժՕq ()h|AcFJ0l0t[FDDڷo/XXt)cǎ ֯::tza۶m+A 4OFLL (֔iӦ Uf)<<"H#@4hGsNc4i .޿**HJ,CAWWu$յkW`pZSjQ˗/! )<<(Y$(E'5BDDw4S֢k;v/,=}NNNHMMř3gw9qEc˖-]SjQҲeK[ IDEE 7oqAC$aÆ ٙ%'O]QaÆw_ի1cŒ%KPzuDGGT6mÇ0h >|qqqpZRzQҪU+t]ZC4k áCCOOu|}2 R5ٳgYGRh`ժUزe 6mڤmmm~9 #%|{.J,UR ;v@&M```۷o[n#իW1i$̙3GСC;)a۶mhذ!>~Ǐcci4###Ӈ/;qEN:033C``G:uXWff&F`ȑ RBÇի\\\0{laرcكڵkc׮]j=wlЧOWsRFxx8f@EH$3.]4½{T~Xb8t-Z$DOIRxxxȇؿ^,ҥ ~W֭[ އ0qDA"XjO ~YA͚5CzeQ8N|c˖-HIHɝ;wЬY3|W\Q5Y̙K.ybbbիHIIpei ͛74i,--{n]/_FFM4СC1yd< ={ ==uS+%ZBLL ?~,Dwj+!!*;Rr!8::׮]CݺuYG*ǏW߶&Mڵk8y$:uJ|F+++?g0dȐo?!J1{lFi>} 11gΜa @*RrhʕBtBBBxuld2͝;D"3$ HE4p"=? ~gTbE0a+8ev>;ZbkRZZZmnݺ͛B{fP: R 4\]]N-mܸJ*:F6?ݻm޼u"35lؐ5jD)))?֋/5k[.y{{өS())IA/^Pff&]~-ZD?3萉 :.]D2md2rvvf͚T*Uh^.M6%&&i M-JDDL?rz}60a\zW\aGt ϟ?ѢE ֑$kA7oJa톆СCGXXD"5k{{{ڢFQTR6#|8?777 ƍx!>~000@r`dd$gllϟ?#99HHH@BB9HMMX=zBL4 ۷oǃ7JԵkWHR8quND:tc۶mBuV*W'bĉLsܹs۷GʕqIкy&?C?~HDFFݻwψa"TRF5PjU#)) xBoNNNF͚5 6(].;___xyy!&&fffpDS]ʖ-KBvD"VX>|Xmsx(-JK.8~ݪ(XXX0ٽuҥ;v,/_ B$ AQch֬8E Hk.(xxxۛ>$055%W%nnnwăqFUreL:>>> }[nѣGpJ(ݻ7_ViB,_Xjڏ*QuDׯd2L>]x{{3gTh(_<:: ǩ4_ `ѽʉǧO`cc#H˗/ǤIj*5J>iزe m%EILLD\\\OIIYڧ-Zm۶͛ mXΝqQQ8N1)J6m >ZH͊+0i$QܸqFѭ[7qӧD"ƍ~{ L:Ur_t aT2Zgp ʪǏCWWUVUj?֭ĉrJ=Z} !66=z@V0|q%УGD",Z.\ٳg.E۶mahhcǎq*iQ ⨎?~ .u13'ƌ~"J- %***[%_7F$ ={Ę1cойsgL6 2Lmwzzzh߾=_㾃YQbkk ;;;~ g`ii]}bС6mҜ9sjܞ">D޽AD>}:߿ǏWʎ>>>s_WC v튀$$$q*YQ|-ٿ?$ =\i+FEEsh۶-VZ>v`͚5gG<}wFٲe; +[:uп̞=J8ԩOfTӢO>S~ÇСg`РA\]]Ya?~T7nJ. Atݺumw"88u)rkDDѣGc i ӧOg-ӱqFu 111~:(RT(_>4bwRTQ={Ncdddw(Umۖ*r022oI (S -Z$xߚnݺ{p\*QСCqA|uA)(6lw^x00eJY$LݘիWӧcӦM|Dڵk3gΰq*Ee>}@OO۷ogEP/_ቛ7o޽{cTXQArJl޼ueG% V2e`…LD۷Gxx8޿: ǩ )J免7Md2޼yʕ+H;SLјIw!C0~xӇuQLyIzzz1c-Q֭[C[[ΝcT%K8>ĥKXG!HPR"1~xX[[?P`2vУG᯿bGTT_Ճ -Q###8::ϏuS*Uԭ[NNNfi::Rr>}7 "Bg۷O3.hItt4]v8{lTQ|yAfB*J \T ooot?C XQ9իWGtt4Ҙe},.-QbŊLg B]]]76l@rr28J"$&&bΜ95jWdz<==ѳgO?ueeeL/_FKʖ-+V0͡ D"\]]%K8غu+(JSd͚5HOOٳ{ꌌ fffXQiFFFX"DWWcǎņ F+W 11ucN%ccc 87233YQ$''cٲe3f LMML8&Lq!lll10|phiiURps*Yرcs:tu.tQn:2Gn׌߾}+S__LiӦ zzz̙3+WDFF8j vvv8{ucccZj7n\ SSSx{{+\9._u )Y$?>T|y6mZ@_'#+++Y:uͱ9NMM%{{o ڶmKRADtm200 &7nL"W<...._ݐ@?s/L'Ohҥ y-,+<111K6mbE͞=,--A}ްO>ɟ3k֬>WNΔQsׯ:Ͼ} ><[#F tСB_^J.MϏ)))֮]+@Qkw^uʐ!CVZ.J.](_<>})$$,,,͜9S###@k׮JKK/266 &S}Q犌%888/:=}PhhhH$dnnN&M*p9 :upBNܨ(@Z*P&LFݺu *PLLL!<<<͓A˗'JOO?VL277'DR+9 WWWZt)=y$sƍ"##ЁѣΝ[ʴgϞlЂ EΞS\\'OB׮]ёu /9/d!;;i߾=ikkիWD\,L{+JJSNG_͛رc\eqtt]ܹ#ogީS2Y5K֜ݽ{WFּ"Eɿ.%e-M-JT?YGQ7oޠ|:vʕhԨϾ}8]PP$ BCC1lذ|/<<<L}'OH$mpww)6oެ ḡ0b_kkkD"[IIIhР4h˂cJ%n ɓqqKૻ:uСCq5H$\t Ç??G~~~HKKCXX<==ߏ+p'NDz E9NXWEm6fr;v,9;;{ܻwHWWo^>/4jԨ7 HI||<լY3 s544{95Off|"75i/LׯL.]=s璭m^?H$Z*M_?{{luˀY#y_s>~eDE儧#%jSH$!C={=z{ҥKT~Ha(!!FAe˖%===rww]?UFdjjJݺu;w%gϞB+GQ̝;ŋeڼy35nܘJ(A%JuҴi(..m׮]K+ 7zoHGGiɔظ8ݻ7R%iӦK#Աclm׆ Fu;IN!4((_ٲe FHTVuO?N:Xfwkذ!zj)ν{ЬY3 |oڦʲI |_swwGff&::hٲ%;j_ԭ[^^^={6222X)=???ԨQC><ϟ`߿KfQ;p/s:u۷Zh+WqR׳R (UT;{,~g`,]7fXRr '00/_dE888۷x9('(JT1c`#8ʫ(#Ю]; ͛7ׯzQFSlYXX|QҡC)SvbE4i aE ̚5 :::jZ^Eɹs砥֭[3HU0R}ERi&q5+++| IGG}Ν;YGQ;hذ!+S*U .ƍU6\`={͛7T3w\\v y^aii *?Zҿ<|׮]cE4oW^e1E _&M0n8Q+ .ՕA9s |||b 4jԈubbϞ=cQvm>ZR}6RRRXG8AhTQ"b `)իWx%hxyyw6l8===TTIGJ/0ݻuL>U@fпx{{.!!ᛢ$$$:::*"j<SSSlذu+jQ?~䋁RʕQre>+64(, U0QF000`*of͛7o>,Yu+*rPFpQԎWYTPsŋ#,XUVŞ={&ϟ?p az&k֧N¢Eb ԯ__T\~բ(nݺȑ#j%*Ț_78ȢF KKK8::bܹHMM9%99wA-dJKKC:uW^_' $}VVVxZ&ۭ[7 44u> 22uS:,J`ooG!..RT=EIrr2/B*2+J8ϟػ&mgah a8*E[QZOZ[k}ZG먵jH>qNPr ف0$\8}@\o_} ///<4(^]An*ܻwOk=|׮]Cbb"BXZZB$A$[nLLLahh@}}}BGGEEED~~>*++QTTXgϞӧ'Opqe޽ AQQk\k'Oİaha( I4j۴j 'OD`` 녤+ ;;;Ç#77XhU ^/6l0lذHOOʕ+QZZ#$$/^oƁuraooH>}$jj3xRRQQ۷oӎ6`ccDQɨ_&ݻwC(~@HHA?C֫Cbƌ1co>ݻH$ɓUUUhժV\iӦ\ԟRSSiǨ7'''Gmh-x=ĉ铏=?gϞ?ն yP(ٳq$''cĉacc7o͛t̜9$jVZJ8<<

          vmnnnUU|mT(@`` ,Xqюü I&)R> R (-:177deeQN^͊FcDQr5?}(L#_|UVwL###@^^$]EɊ+GvM4 SNҲLP̪ٙ%Ơ^ܾ}'OĒ%KhGa@ ̙3߳՚- O;JCGG/_E8;;FcP/J6mڄAsδ0M 44>ĹshGi1lmm@pB!rssiP;;vDJJ $ ( `TR8p@hSSS_PLMMYQN:)))0LQ-JOHR5f ѣlk3с%B/^Co\.+a4բ񁞞L6l***M;Jakkv%A$q%FVHRDEEa"0ǟI;J )Jԩh`Vܾ}yyyxhE`QqE1Z u,JFJW7ԆZQre}J=P[>O<`S>ImE"7m糡!}~}3vӧO1zhBWWW$64d4$>>n437('iD"#O. ^&L@xx8 !35 rfee,TUUю0 B(IJJBNhݾB؂aҺukD"LD"T{֭[ѯ[ Fee%n݊FwsD"Avv6( Ԋɉ :tf4kkkP H J^ &gSeuD()**BQQgCc„ 000)BCCk}pa 8mڴ@ = IDAT͛Wyɓ'4i!Э[7ٳ9>SB|s|||v!,, %%%ծ3L8BѠ~[Wβ2C__B'Nijgϔ233t ~ڶm jj{dgg#$$ڵ.S\\ŋwҥK!,+ l2t000@^}ZWTT?033 Ss\61. }2 u͛J_@>eɫW~5Α8qlbnn^yvqoO> =z EEE#ȨƵCCC:*#u q_[[[ !?'J?QLΝɗ_~)͟u~~>skǖOOZ&RT἗.]"HXXX[ٳgיmhZjEvءcHihtTZJ ~7PFyy9bccaoo4[N~ؼy3JKK!{?~\~ޚ5k3xzzÇ(**Bhh(`ʕO> йsgDFFUVoۡC<|Xt) <<\ELL qyXYY)Qމ'rX~400`6#kkv߬[iiipttիWQ^^ .q H$w3g`Ν 畽vdJGEEEEEؼy3`ƍ5KܻwŘ;w.ѣuއk| gImǚZcڵkyaJH(33Skw'Nv@:w\xUU~:_Ȝ9sHΝ ptЁ W\׽ϟOmZPǻxp\nݺծwfm)9rHN>M;*uBٸq#PbMF  ͟5]q'NxgKyV)))߿y bbbB!CדZϗevXmڞ=گ1wwwg)XFhjK բ!]viii㥥֖"l!cUUU5&HYYY땔4kQ"높}_R'͛76m(xF1+V z***zgQ֮PpGdծGV\IJJJ/weW"hLNcE սo|7]|؜9sGGG?]p޽w^]ϟ===Blٲz=F ~Clnyyy@Sb[Vbd j\ZZZÐu*Bݻ7233sN[iee[xjJ^cBP֥aP)Jd}Ǖ ׫}|yRS^`ݺuD6گ1>(o&*EP(F8uT\z8}:.^Dk׮_Co DBB*++ `J:u*8lق"@&=CO5 ~F#3*//u"Ol```l77j&Lxkwdd$!gϞ33ZٳgO{C˗/zΛu8;;52O^:Jf޽{8ӳXE7=Z3)//'>|R&[s@@;^yG ̚5羟_:^y @lllN5F ccJ%baaA;/{CLMMɜ9sHAAAsG}Dѣ9pY|9@.?7==|GM6D rСZo_H%^/%88k׎hiig(*..&aaaȈaÆ:shiiKKKpBRXXԽezEϟߠk0177'6l \+(( aaaؘhkkqƑz=󉍍 D(QF۷o+9~8ٱcqww':::DGGt҅,^5Sm[߿>ON5FE :a۶mq{#iiiMvYvS<-ڴiuaԩ~mv^^^Ɔ hG{"00v&Ty!!!.]j2I6a޽4.jotR @ޟTdK9r?~t҅vE$7dFQ+JzxTTTЊ&N9s&B!|>:usX>GF}WBGG(ZՕ***`hhH;4O>Ÿq*iƍ===x<`HHH=Jƪndk0ߛ2aeegggDGGۛV 0|̟?5PӧG5ɵY[[(//WEjӔS~n1Rh+14#0 %%1b(-H$!DmZKtuuiGP[5VeuC(=z4X;rN;J#[@M]X幥ͅ T///oL۳g&L@;Fdbb)KJJhGP[999UeF]Q-J 88w~_z~:1i$QZ,uJJYQ¨=EɌ3 ӎ45k`̘1j;kH 6Dqeee(..f{K1jzQҦM̜9VjQ Gxx8>sQZ4uY@Y(j%11:t`^FPɢqQݻPa?~<ڵk͛7ӎZ[Ja``@;ILLd;p3Ce޽;6oތsb׮]0oرcq=zY¬E;[uִcD6|%88^Bhh(|>&NH;3%h6WYYY*'QӧOQQ|QG" 88}H-T*EPPΟ?h6N XZZxGSNԎ%tzA; 4 (J_TTT ((Ř2e H-Rii)&NhDGGfc5t%AP x<oz(++ ~~~x Μ9ÚՈP(Jv-((`]teҎ0Ffɒ%8x 6mKLJHH@Ϟ=QQQ8xzzҎ(JWu-((@۶miPyyywC; 4+J`رx"ѻwoю>~k׮HT}ZpNNhP (jY'^ >777ܹv$SXX|ᇘ>}:?Κ՘/5ˊ;w;wP(a%_>}:BCC/^Ў.^!CЎ0Jƚ5kp9ܼyݺuCDDXj .Ā۷oϏv,rKIee%ٻzz ޽CҎ0J~֭[>|8FaÆ!))v,A쌟۷oGxx8LLLhGc iG!77XM=:u ׯ( Ө4(֭[cΝ8w?̚5 9998x{{c„ ĬY_C$aٲe-~0t8tΝ;777ј&dll @E 뾩+W-h$-JdZj+Vǘ?>m;;;̚5EoBATT ̙31ffp`iiE!hGQyǏhGaFEP(ė_~t]QQQh߾=|||{n]655| ڷoÇC[[gϞӧOѾ}{\eee2@UWuΆ)j!<<c0Lh1EfΜh۶-OsssL4 gΜAUU RPP۷h߾=mۆc")) 0`:௾ ؿ?!3MIU,XXXЎݻ%jqE qA<ׯGJJ 333L0wFvv6DAbb"֬YA̓5N>Ǐ/1b|T*=z ..֗41U]$;;%333xyyю0M%fhhiӦ!&&Q\\ٳgpӎ $$$_~App0,,,-[ c&ï!7oބ7ƏǏ741  3331T^xx8F .f4:UTTȻtlllpmH$BnnnH$8NݿO>œ'Op}$$$ >>wEEEڴioooǧZKH}<~vhii`…Xx1ZjX_Cё#GB,C Ў#'0||'GQQQl%WAAA{RNҸ2@,9۷oX,Ɲ;wٳgߣ%ﭬ`mm HPmmm֭[CGG(((D"Aaa!b1P\\ϟx P(|||g *D" hTVVVD"^۶mի1ydMYYY,Ҏucvv6k)ycǎ a +JB"ƍ:::޽;w^휬,]+++)S믿Xad6={2E˗/!Hؘw?0bjbƊHLLDii)z]9ZZZ ӌǰa`bbϟyχ~'V9YcVV(rϞ=Vݻw+VЎ0My+W@(C4>3fԹhGq=5s:iiiD $Av;;;uh&OL; 49VA*Çԩ(b̙͛O?Æ S~aU(`E[رC0%uHMMEyykaÆx==3""aaa/((i$*Ud̙32e ( ,XQRdp8tؑvfre\~|1Ξ=|bJ1ژ j~GXYYϏvi(Crr2D"iGi63f̀g8y$PXF}իW*3T*Eff&D"(*իWصk̙Sch*V!))Ō']"22gΜAXXMXɦު|H$۷oL:ri>(ǏaooO;Jիߏ;w?Ql*t6}d-%UVVbƍF6mhafÊ:<{ c???ڵ Wƍia`ll >3p222N{ѣGS̝;viVϟ?g^aĉļy  U2D%ZJ=zkkk_PQ6l#H; 4+VB,ƴŋ#//!!!011H,--UbLIzz:&}Ù3gpUю0͎uB5)իbԨQz*8,,,M;oٳ'( XKI-^| lL;p8ܹEEE1b.]ћj $''ӎT9v q9\|1110 ]v>@V.Tadff}Ts7( C+JjQ\\ ӣD=ĉh۶-|ڑwPSSSTs/… ꫯhGajXQR T @@;Z122ɓ'aÆUJJJPXXH-CJJ \.+J|r 0}aaEI-K;ڱӧH$ڑ:qӜ2H?VabEI-X:vǏ̙3fѫ(<4VUUUXx1F ///q*6<O>Q\Ϟ=qQ iGb`ll @@-fٳgpAQ:RR vmCbΝXf +JT8-D,cٲe:u*N0`-%mU\\VZQN ښ-Gbͩӧ-dӦMe3n(E۶m(iy!;;&MhGbAsym-(~O?fff0J`7QNVZ`ٳ0 [[[*WK,AVh"QFe@}KMpuV|lbbbϟSwrr2_A7nΝ;n:6ۏaeFxB}ѣGXd 11Þ={Я_? >wܡţ9$)) :uro!3g ?vQ)(M% 1abϞ=èQN5g6 IDAT`xB!&N(T (uY ./=akkKB,x ˅Svpp˕w3zʤsjh8IIIu~5եKm6lڴ [aTajOx<9yd_p!PϓqٳkWCBBj֖)|ڤ+++ҫW/RRRe !&L RVVF<==k })))9r$***NNPZZ={֯_7ngϞHLLD"{ЧO9s;w̘1oU SzoT2RiwA׮]XiGaF"Ru-"~Ғ C !ׯ'^vM~,'' \.y=]Q<.ȑ#;}4@:vtCCC2j(RYYYr޼ynݺ$--ߟBHEE155%m۶%cFFFҒH$L}(r4h mՇYbEޓ;w@@~gQ -%(yK.C|ƏLܹ{Fii).] +++lݺȿ~<ϯ=RUU׋5޽{#""ǎÔ)S7g&X;m4p\޽PWW>gLMMuwbo֮];w_~Qj`:ô8"U7{lңGww5gDWWW~ L휷]8@^z%?v:={O<)T"##@ SLy˺:t HFFFK|>#:::_gTsF vZ">2۶m#-͛D M6юh RBM4 ׮]׫wpp-[PTTxriSzRMݾ};JJJp=`L6 W^D"KC}ѣسgfΜԀ^@`` PYYl,\aJxyyA,(z9ztE[ 1qD3gΤaHٓ|Վmݺ>???yhqӧo޻w51^)=zhiisyN]W^ggZ>zzz$!!򁨎5+r29h \mkMɴiӚ^4-Zm۶h 4%{n]9}ǎݝҥKxb/?{{{󉙙_Kqq1 #FFFDGG 6dff֙'''DKKXZZ B﫬G>Z?_B󉍍 D(QF۷ozW_}Ek6zoT2ٕu-$''7uҶm[qf-/_&<ٳvFijQ!Pspvv{W犭Fּ OcĉXp![q4ʋ/`jjFMZZ^z5h)((+<<˗/ݻ(/ ۷oǚ5k|rq41|~'GOJBy (Su!,, gΜQzPVeM<R/hGͲMBBaǎ8|0Ξ= PH;è%V? ooo޽'Oř2e ***0k,hkkc…#i f)JnܸOO& ܹstR׏vQ[(Q@=0{l,X=HM*aaa&'&&bĈM~&1~xxxx`ٲe0ZcEVZ .`ԨQr iGjq͛X`***tRʉԛͅGއ?x dciI@W!"" D"E7olق/98j̬&$$յI݋-[`ΝD0c-%JD8~8|||~ \.ӡSk׮U9IHHF,޹sӦMâE0rHqF#DI={ıcC _~aML<1b16n "''Iq t޽Iќ 0vXxyyaʕ0`o` ڑZ@S6eWWW ??CFFH*MKK FFFxe\juSPP???t7oa4+Jvڅ={`۶m۷/Ρ111Ӄ7޽K;J322jVD7ɵT*E@@JKK@@;h,V4 ǣ8rH-Ο?'''ϟIe4xD"|ŋ#""O>Ǐ ^uŸp;2LcEIrrr•+Wc"00Yg!0x` :{I%57ZZZ8qV^ X[[[n066ɓ'兊:ٲe ֯_;vua0%MLGG6m‰';_U 48sbĉlZ5@W///cAArrr )矘3gVZӎ0-+Jq]L8SL!CF;VpvZlܸ_}MJڱTFcөS'&Z!_z6m/^L;ô(iFZX<]t5kJh֬Y8z(ۇ#FLXoߺƏ~5^M%%%#FqFqEaE ={D||<.]˗sùspMO94y<֭[4/_bذagK3 (DKK K.ѽ{w3 @bb"h-B D={#Qծ]F}uxꫯ`ii٠{4 ?bǎH Ⱒ2H}!66b2eJo%vvvAnп:tv$j4|GGZx/^ ;;_EEEa ck0`E Xr%e˖WfcN];vY4ʴ`H$p\c̘1 ?H;08Oͽxׯ֭[v7o,,,(',UUUXlVZ3f~XHKKCJJ p)UV(--EAAPZZ6m@GGzzzhӦ tuuamm 888D\\z8x >C_muW^{H$nԴXQ eń pBtЁv4rQL4 ...8tiGRXNNbccK.w^ϼՅ>Zn-~~>b1ʐ2<})))HMMu266p !h~ܺu ÇT6Q+JU^^k׮#GO?7h#)) FBii)>,xN%$$È@rr2x<\\\лwoNNNpttDV|l֭[*cǎ|1 ߿?\\\_dV0* Xf УG̙3ƍcWhlڴ SND8pFjj*0vX 8^^^o틋/"22ǏGQQ?cǎId^g3`ԩSlVFijQhX2~xEȲeHVVXjO*KCf̘AiG"? ^^;)V%I%[vI"#Y2eK(K2f}& `0,mʮ0 JYnIEw:wctu^Lޝs|nݺݺuh"/dZZZ/%%%5>}ʌ9m\\\ 1$VXڶm KHH:;q`{/_d֭c;vd lҤI,..,(--eo߾ 6l b֋/X׮]Y޽YNN'+Z#2}Xf z*н{w̛7;vļy `jjD666ؽ{wnO(޽;~G|x 6l؀O>QX'O"%%:u=Fߏ/^ [BHQQ}8x 222#:: {nzώp)ޜ[na9s&Fb͚5yr9055Epp0"## sssxzz6hr >刎~#$&4*JZ0mmm,\wE\\LMM`o~:~#88HMMHXf   ܺu wʹRcHHH>} ""￰CTT$H*JGFF֯_ 8}Mufoq`nncǎ}T݃6l؀7"""J|x< %%9r$ϟO?á(-eTj0|ܾ}Ɔ ```;vL4(qqqpqq)Sh"ϡC`ffEEE$''c…kY_4hjmkkkhii!<???&Mgw}8 2=z?P_-q5c }oL8[nşOc۷o-Çaaahݺ5I !":ogaǎҥ >Sl۶ YYY\l6wD=Xr%*++k{% ׯ#&&...m|>ߏ>|8t .@SS누D!8|0N} 4zhouBG49===xyy!""Ϟ=Æ SBGGƍÞ={uFsNڵ AAA044:L5k0es@t4/_ӧP 2&Lĉѹsg#~k׮aС1k,ȔJ3HLLבi4z,ŋ 3g+ &L#)@333 6 ;v:LC~5kpFCE !DLL BBB'OO>kkk(**r|}}[nAMM82+<<HHHqiTL$''#$$!!!y&Zn1cG:kHLL.]aÆqG͟?QQQHJJ:!GE !?#*Pbcc#F`ر=zD2>VQEE#F`֭|Xaa!/V:!'E =}C.\Hdee񁑑z@YYY`:ߏW^aڵ G]]۷o=zNtȤ2̙38<߿VZGѣ~*++puuXJJJХK,_^^^ 0ÇG޽Eo&DV*JHc={ΝCTTJJJЯ_?Qbii yy }}};v Ms8# IDATNR1W$$$L;{g`aa͛7%pyyyذa֬YC G,,,0f\(z+%KMMř3gpĠ fff=z4>g/۲e ~7CN =BNCDЕBdT.]퍋/իW8qTkAB055ŋk|1;wbΜ9TplȐ!իvuBOKBޢ &s rMyy9rssaoojOɓ'pwwo<== BރBP6B1/4hRSSҽ &]vM|+ uB{PQBH-Jee%nݺ]bժU G#$ժU+L8: !=(!(//|>|>]׬Y,,,(-q!<<%%%\G!ԡ愴`pvv.ZjuuuB]]Z Ԡ BKK ***˗/n١ 3f q!Zxxx4 c ϟy1!---XXXŋTLB$(99=z4QH-ƌgrR*Jxє͔ӫKi>(!Dݻ={rԡݻw9NB %HН;w\<{ hҥK>%ڟ4Յ.%4좢 {nWWWXk1|> |r7((҂:u /MMM+BΉU}jjjֆ[9W듳F3g0@ux{ x<suue1Skؖ-[޻ qΝ;׹,))a浶9r$/&z4'...ťsBjGע➫~sB$䯿brrr ^BՕkjj2… L cmC˗/3@~G <%''rv]6tP޽[V]]`;v`ŬEFF2LKK/ UTT48µww qο>ڶmΟ?|2344dEm=WC.%H?l%7oެ֮cǎ 5j۲e KOO6ѧOV[~ܹ}W}Qiii \(k׮ݻwٓ`<Fw_.\`Xnnn k%U}?~ZPѣrqw?йKE !r8Eɻi>|֮]ˊ> qZ_TTT-j>xKGHHGA-J~Zb))){;Jj ѷ\S%|r_gD믑,^{}\"8󡤤Q}ܼ,WEAU}$rB$hݺ5Woś7oh"mK.*U ?YGEEŋEl޼3f̨VT=.- ԔX4GU}ѣhYcT"!\%;wǃ зo_$jWuEÇ ɓsEbb"q̝;Fۉ'\\\p TTT ++ >>>x uƍ>}o޼iP5mB8_9sXy=m4QF?W{~UȮ_ޠ!Ƙwywvzzz:u;caa!޽{UWͭ*qㆨ{<{_K,a!\{wLɻ9[ZZhknn^mHCt 144ǃ|۞?0`(++CYYz//j믿 (CGG۷/<bMMMc…ԩ GGG\rݾ};L555(++cРA8r{%Çڵ#9(**cǎADDDUU0 ׯvZ4'OFFFxqޫK.ҥKBHU29tGUuK9t 7jhhƮy8ѣG ~3!qQQBKQ̓6`jjHbժU\ TLQQB=uY}v/033*ѩS'xzzƍ066:{ݸq[Q!1%HPII ڴicҤI\!zѣ'!HrLIsBWJ XYY&"Gyy9BCCuBH(!Dƌ7r agguBH(!D>3dddΝ;\G!o9w:pR*JݻLJ3PjeB*Ji@YYQ{XFFf̘uB{PQBH#6mN8u`׮]pvvQ!AE !@]]:Js9s\G!|%4#..n:J ̄gϞ6lQ!@E !Gpuu˹bx۶m빎B*JiDWƥKui͚5077=Q!@E !SN;w.[8سg]%!DPQBH#[lܹ(-7Ǝ BI:===[_5lmmѮ];#ɼ **f%DЕB>̝;(2/## .͛a``uB(!cҤIspA#ɴٳgc={6Q!b74;w?GAAq}̟?C޽'s6l؀˗/x\!H:]vHJJ5̙> :L9v-[FmbT"aχ<==:x<---L4^'!)))pww6VZ˗/sT"A/^ 8#GB GieCVV&L 666߹F%H͛71h 11&M>Sa@YYY. ---`Ŋo_똄z 8y$`bbD <!!!طo͛G`cc>sAUUVXӧO2d?~mXBHPQBG`aݺuprr+.\mmz?rH;w.& ҥKhӦM6cƌk  駟ի$%BXjo???(**ȑ#qi矘2e !HIIСCZ *;wF\\hIppp&%B1bΟ?s᫯lmmhXYYӧJ*[1dy]8u 8;;_m1=yCEFF`gg'~ k׮1coawwwoׯ7,XFLLi*JÝ;w`ii EEEǣ[nqqq:t(F7_7n~ܹ۶mu}ѣصkP\\,ᴄAE !aÆk׮Fe;jjj8v~G|:t(߿(jѫW/ܿQQQy3"""pe|g˓@RB$PQBH=n݂@ll,1^X߄سg&M7ٶMMM___|߿?.\doj%%%X~=uׯҥKضm$ݻ#66_5233% Bx(!=1gZ ;<V\dt5j"X[[cڴi>}:TTT<Ӈ>ӧO####R8dQQBe,\8͛ BPP|8q8Y[oܸR(++sJzٳg1c:wp@WJH3є69ZC[ 5&)//Gbb"? KKKL0YYY\!D&PQBd^YYо}{ٳ8KNNƥKj}bcc9H%}q1HH=*J̛7opĉ֯__#刌lDKSSgϞ1k,"(!2B@@:cccp.-- ǏGyyy_g!!!֫(v]tcpaDtȬ'O૯7FufaDv_R?vvv矱|r?8H-*JL+ M֒x(QTTχ|QQQMN}7>}:rDe\~III\i<YYYx9Ξ=h :Oqi֨(!R… Ɔ #5=zDnݻQRR(4kTw} :8R%55&={0!AE jNBrr2֮]uFIرc1k,̝;^:!%DmذƍGn4-[@QQW: !%Dj]rW\… "u222PXXHs4VZaڵý{CHCE Z;v@1|58vϞ=MMM`ҥi*ccS߬\1coooPQB@ SYWWW… a``%%%t -B^^`ӦMxOw%x<=zr###x<9rD)))ttt兢!/^իW˗ :FfXxxxm|||Z?l"jWl5͟?Fw?ЇZ{xxئ!{{ygر5enw,??=~` @Y,Z |/M65={voߞyyy},H\\\!qTs) l .0@ ؎;DT133cweoaXj{[}Cܢm۶L ˗/3CCC-_mTux|/_0f5^lY||<,..Nt-[cO?eعsc1mmm&''TUUYQQc;ھ 2=|{XJJJ iy(!ظqc_ر#FŶlkmWիe/_d\ve(9~xv ѣGۈ/mǧgϞ yٳ5ѣªx"wc_~aOfϞG2AgϞj$껶c!ܜىq$HKGE !̈́qR IDAT>|{>|~qk׊RZ8!nQ-+..fR8x SPPSu۩򂂂}+++ڶ|cϞ=c<3/ fJJJlҤI1FXvvv}Ba1..xѣuS5-~~VrJFFF{RmU]N2ظq#1vXj*?ؾkHMME@@@m掊"u ?{`~~~(((@߾}E/{/[I**>|ؠ{AQQ޽ //:ϙ3(//Gll,Ν 6mZ뤧#..N*O~yzz"&&z*<==kutt̝; (//G||U 999U"1ck> jժz=RLL!ܹ3[~{xLcvkkW2===N:o58۶ܜԹ mmm6uT<>52O6޼yúvZ>vҥc666 ߿;wf؀>[^999LSS} Z4fBQQQ4[h]<==`PVV2z___߿Ѳ066{ǂe˖-B6m {{{u ѱcG ""Ssssk|Ry||>]]]|L<;wVKK ?>:vcr Zn])ST&'N;ttttRl޼YYY=Bcu#4h~( "{u 8P4ؕ4?%%%֭k-ӧ:qɢ+%DhhhR)55 ԩQHTTT? :!M"uڷo cKQRSSahhlJ܎Os&FHKBE :FFF!RSSѵkWcæMpiDGGs&CE :&&&x17kr>үEs}4u|dȑ#gǧE?i(!RW^(**Bzz:Q΃UQBoƍHJJǹBHH޽{CYY׮]:Ty5^~Ν;sS߾}1uTX'-%D())z*Qʃݺu8 ǚ5k?(4:*JTbJKK BйsgoЄsH*JTrpp@rr<TM7iʕΦԈ̣HUV :HKKY\Tǎ1|[^Gd%D*)**b :x!%RJJJuVh(!R /^Dvv6QBjj*=,tttxbl޼o޼:!"lmmÇsB~~>*弽Т_FId%DjB!qT+%RNCCK.ŶmېuB$"վ+<~/^:JUUUs|yA[[ׯ: !GE j?~<~g4k>DΝ[ve2.]ݻw#DPQBŋq%s٪Ȇ3gM64*J2dzj4[>A2vBff&q*JLXjBCCiZ0ƐF\e̬YMWKLKKK;/'qsӕSudΝ$Ti&ܾ}:JEt*J޽;K3^PWWGvB$LYY~-j TfLJ(O͞=[ƦMBGMMMl۶ :Nt֍(++~~~xq(TGGG̘1_:J5khA"(!2i MBtDƩ`ѢEGnn.qi0*JLjӦ :3g`ǎpttȑ#[ĤSO>@ +%-'}"m@Hc6lVXŋpvv1ԩS8eʐgϞ!33(**BYYYUTTz!99%%%h߾= W_}_PQQ:!b1!i,Ejj*^~JTTT@NN666tݻ;wΝ;w>}jЦMhhhZ|@bKKK+Qjjj000@ѥKzB޽š N:a͘7oqH#>}:СC',RBd@ bbb- B^^455$KYY^X$''#%%iii*LMMѣGڵ'|vA__m۶ǝ999=UW`RSSqIdgg:t^zO>:t(P9s&6oތ9s| !Mu899ѣGu999߿"$22111Gqq1LLLп={D>}`ll 99nxegg#%%)))sn߾7o{ammaÆM6f%u{ LLLo>_D*JLڷoݡPkA 7nN8!z NtU }СĶ؊pe ** W^Eee%뇉' =z:&ynݺ[nq4*J%%%@`` x<-W^}Ԡl >1c666h߾}nngĉѳgO899 }:"W SNaر\!Bɓ'1k,6<'OĄ 껢!!!Gdd$0vX8;;c-] ?~=B׮]1{l-?o޼All,QH#բ)!2kĉx&NPPPɓ'gFFV^ CCCL2jjjFvv6:'4h~'ƍ3f ֮] }}}|HLL:f닸8\r(])!-BPP̙WMZjW^Ix8u1sLxzzKb>|~~~HJJB1|AQQx- p +%H1gggܿƍjk<2\ʕ+`ii,8p/֮]KITUU1sL\~۷/Э[7ݻ΁D򼽽EcTCOOǏǑ#G)]II-k׮(**Bxx80uT())q_* 4{Ç1rHxzzG8x *++'>st۷o: !BE iq&Oc̘1Od`ѣGpttk\xqqq8tԩvڅ ٳ'Ξ=u49s`Ϟ=(,,:!DE iڶm`_|Wիp$$$`ԨQ'-FFF~aرprr³gϸ&<==!w^ATmڴi*vލ+W"! ۷ѣGlݺƛ4b۶m \!佨(!-Vaa!fΜ'1#MoƢE닁Ν;\ǒ9 .Dzz:N>uBދ"ݼyfff8w=3gɉX- V^۷oCSSĮ]%SLMM1j(lݺ(%ٵk,,,бcG$''ٙH@׮]xyyՕgJ7p=R'*JHQPPɓ'c޼yEXXڵku,yyyY/^Dxx8 7orK&5 ݺu?Q%E°a0Z \"u9r$+++rIx\R %D]všC AӧOڵk၈H%())a\G!*J(++㡬'Nt23gwrGꨫw: !PQBdƒ%K3;w\!~ѣL36̙3q]\z(PQBdɓ'@s4999ٳqӿ: !"TK̝;k׮%qHRWWDZcpq8q8R(--: !(!2`޼yڵ+/^u^z^zu₢"sT)w%9sojҥ VXu C )NVEEtRt҅8CAo;3BE ^;wD~~>|||B^zaƌXd Q=tuuqBE NXn.] eeefbŊEtt4Qh"5,PQByyyxxxp4#9s&֬Yu2c <|W\: i(!RG(b֭7oJjo[nqEj}E`` QH GE :x)f͚u u#Fu2uT;v B(H{bر: ihR0M<: i(!RgϞŔ)SBqơΝ:066q"U.^ s4cǹ"UN sPTO?\G!͜:Ԙ2e ^~p"U"""`ggu [[[xw:С %Dj!55fff\G!R[n׹"UL'OB p@Tq-0Я_?) ''>}лpB\t(HCKK ڵ: ݻw!Utuuaee`"5={}}C(bʕ֭|>444`aa={h[XX___˗/uO/.ЧOu 33nnnχk"α -D`2Wm#::mڴmO>ӧOnK6~x>}&R#M"%٨Q>֏-[ڕ0ssZۍ9UVVfeev޽vԴv{fU۴iSٳE޼yÌk;v :w\z_qw}Wgnnn51WWZnݠu[tsŅpCJ ޵k… (((;۷o۲e _#99{.K.! @~Bff&C`kVkw=#>>qqq044DJJ 6lP#onCc'Oyf]oSSSzթ>͛ےO>t 4="B2ر#FŶlkmקO=z4Y[[u֍` v=Vmŋֽ{w2ߕ$Ѳ/_2LNNNgϞ ٳg?JIV[~ܹscL(k׮ݻwZ<߼yA>}`OKbŊjߓy+%T!LEEEKOUU]Ukv>EmOYY`Ֆ3狖UmG  ϯς*J-))F fff[BU}qQQpׯ_guR Y-J Scj?nJR2*KhBcIbf0!Yf1cafe_'YB$C1 IRJa?i[xӹ|n $,bll۷ 54]ձh}RPPw?U}ٷ|25~kДs`ܺu Xd ك:_[O{dkkΝ;ĉv"1ErWOׯ#::˗/Gvv6>CaSSS@RRثj¶;wqtpj۫>gSt j<522}oDDDo~4\~֯_iӦU+|D̕bhiiB ˋҦ(!-۶mC^^z޽{>*1fɓrcٲenQ,^(,,!ѣs7PVV̙30r&ϤIz*&:a֨(!22RuM9WU#-ϟGqq1bcc+'>y:uiɨQ,QH{!O?1MM}:3xyy ۽|ѣv***,66V6--v޽v쭷ު]nXVVm7ӧF&Mjќ:~rܹsS6)++CB sJ(!ի KKKkQ?vbLII)))1+++b ]]VV[x1ҥ clݻ5|0ad Ύ>|F/^3###&''nj٢EXfffv-J{ܹsSTTd>>>,77ӧ3]]]&//z0%%jm{ل *SRRbcdY7٢EZO{fΜ;y%D2BSSgϞŰaxi?~ SSStԫزe M;ڰa{<~w˞DhN x뭷pMQpMvv6***csNذ#??}Eyxx 997(HWWW\xw S,|)S̓6`ii˗/COOk֬y8r%Ғwfmm CCCB*JDqssCddHn &u۲e 6m;;;@VV]tA@@bccaff;bBCCudH<<<(!m"QyٳHV!u},/^IIIؾ}{oooQ\QH5 %d8 IDAT'N@^^ÇE*ἣ)GE 8ӦMӧ; S{IZL=yEWWz•+WxG!R"q<<<`bb;wB_˗/c޼yHڵkc)GE 8|mF׸I [l lllxG*Έ9Ҫ(!iƌ`aݼ1ݻw#00w3x`:AUQQB$V\/EEE1_ NNNHCCC%Ҫ(!kyG!bΝ;_i&Q3^;bT%''_}/qG;w.aee;rrrBxx8***xG!R"цcN6oތG?E <yyy}6(DJQQB$?lZwݻwj*lݺ:::H5333 ""w"(!OSS 6oLkg3wvo߾H)*JTpvvG}S"%%w***lݺwvѼc)EE k֬A.]鉜qH+[` UUUqڍ~1*(!RC^^OFee%#??w, kڵ G%8튽=*++q-QQӧxeeeɈ۷| ~W 7oޤOD"RSSq :t׮]c `mmb̙ ,D<3f`ӦMݲIHGÇ 0PYY 5 YYY8pH-[` … yiw "(!b/66ǎáCyyy1VceIvʕ+>|8<<7[FF:t;ٳ4 88XqI3?pqq coСXֈF(PQBZnn.0{l̙3zzzΝ;ۤ~mllpMxyyHLLu|EEE 3gE`mm$%%B%Dl.\sw¯ƍkV:t@PP +++|(--!?ؼy3[DGG aii YYYܻww"E(!b~ѹsgܹs^/ꫯ5XO{+++ܹs8t yG#[oH*Jп\t !!!Xzuwc̘1"`jj#G &&*}'IIIG=ðaxG#-Э[7}u{#5JNzj$%%!88Fǎ1i$9rc "##tRt}Ehh(V^ٳѣ ʕ+9''bbb<|w"%h0x{{/^;/dee1f3/^I&A^^xw"WܹsFJJ 0}t;5Fm۶aƌIYYYH *JHڽ{7Ν ///PVVtuu1sL̜98q9C *Vsfp!,, 111o9sݻwotӦMß3fGݻw+'mܜ!"#`1!caŊXnVZO?wVUZZ7oʕ+z*QPPSSS ְEyHKKý{p]޽J2d mmf識x1'£ mmѢEu_;J طo$EE iu5kۇݻw ڛ2ի}6ݻAII ٳ'ahh###t &&&ׇ|3#%%iiix)RSSݻtVVVA0x`hjjeddo߾ #ͷn:t7Z֢.ߐVU\\'"44N.m> !!!HKKógP7@ 444 +++:䠦EEE(**Bqq1 ]>022!wVVV"/@jcǎXvm֞!-FE i5yyyxwqb#Ʀ3zIII9&\pSL;wD,#v?SRR(!-FcmD2220d"<< JLL"sNaƌ)nHIIH*JHeee vLMMyG*>DKK {ř3guVqHO<H*J`ذa(,,Dhh(yG:=B׮]ycXt)޽;i"ccc$''A%D$ tJa";;/_*VB~0iҤvT066ӧOy Rb5j?~K.K.#IGT,߻w/CcǎxD PQBZ1___ܻw.\>5Ç!###tLLLsNر'O4={;TYr%BBBpiٓw#CAAwV5vXL6 44۾}V{D=i=R{M6m}QH#tQBZ,QQQ1c;N#&MMM4@OO4ZBZd)))5jƎ@q i\>1o<<wR]]]bT&)**ȑ#aff]vA .TTT 99]%qF`ܹzt՜ i *JH|HIICnӧOQVV&wFMM v±cǰ~qHYDE i)*JH>|;v={`bb;NsFWWW̛7"^СCdggB$%Q>},XFݩZ-SN7@KK :#77w "(!2k,믿]JMM.yGBUUAAA8u:;%D(! ݻwCYYwv)==ݎT2dfΜ e1DE *JH233tR,X[000o222Xl( TQkŊPRRg};J###1w}ݻw˼PQBDRh޽֭8Zzz:gB@@y!%N}1i$Q=|S?gϞO?" TZ9saaat(++all;066W_} 6Ν;***T ///*={XMsA߾}1{l0xi444PRR;pTN<{aڵ .߼AFF?#qYq@s|HQQBjXv-F +++Q^' y1l0_wvOCCc GE &,, 7oʕ+yG!IOOxGK˗/Ǖ+W;JWTT;pTjv ۷@@Ьv)));v,444?pejiV^ҠߨtEeȐ!dž xGijDI(!B/_ѣG5;\#-- G-\xMHy_l9n )!-&;'NL0Y]/eee;ϟ?H>vXaƍغu+8풺:WsNC$3g :t 0tPʶmHyâE?ŋKUJ 9'!(//GHHmWYY@tPSSjmSBMM  yͽN3f@WWE~~~m_|ŋ ҥ >C֭[@T{]hh(:Tm{׮]!pA1ܹsQPPP#KFF$/**ӡ mmmL2ExKtC\9!ꏙ9s@OO ޽{z ׂMMMݢEw3ح[nٲe @6l6lذϟ_ݛ7T̬F?#G˗/ef,))`rrr,//Oڙ3g2[ٳg STTd¾tttj?k֬ D^Wg̘Qc,++Q[x1333cMJZĉu~ٳGخj-Ϗ1تU|)S I5yd6yd1DclL[[v ;<+))ayyyl֭>YTT.l5ϝ;Wm+WjݣGԜpyu]m˖- cc7n} Kmۏ?^?֒r^|)v:\tϞ=ˀW"ji] ;ydSR5c/--,H*i-J`ͅj!m6wݻ7@AAU>ЬO4 W v^x !""s9R222ؾ};`ĈOOOw}WϦ̄z%cǎ  >>sέٳ2\vMxΫ޿ƚ5kqf&MWu{~db֭[^;< ___׳D愈W5~n߾k^^^vh_)m[ǎۻt#|u}yҤI5zZ[n5nO:t(^u:ssZoaoKy[4U5Fё4uy%x޽vu}z6)I%#%8ШvbLII)))1+++b ]?Q~fff䘁ASM(''͝;1EEESmݐ׽x͟?1999fll-Z233k[KrնW~7x|um_d ׯcL2{~~>;w.aJJJlӧuy1bgFFFlٲe,77eRRRb999z=ik׮1lԨQLSS)((0;;;vjf&L`LII׏$Yff&y+Wɓ'HrNB$%`jjD1;6lqQ^S i *Jݻwyh4jv>$YVVXyoJAAǏZBxxU$%%!++wlSB~~~C\\(킺:DQQByG!y%jD988̌FKZY^^|""(!^͘0`.\; Oը%-#wE疈%Dh8<?UE Mtm9___>>4 <?~ǏCFFrrrXr%˅#WAAAWbYZZ8Rn &-F#%VGQQv;J`bbN:UVYYR @)S(5Ə#G%χ<DQQBjUVO?ENN8킫kRoІϸq㐜hQJAA=%NfBNdQ''zzWVVF@@@&Nx뭷paQJnn.SBZR'999ٳq3***j<.\556N%ƏǏ!Urrrh%^Xr%fΜIqZ[oU$ .lDkԨQx!xGT4?./^;:thU11}tsJ%}CCC{g$aqsq$&&0`8::CODW_}kĐɁ"yG!"^^^سgN v۷qq?~wޅ*lmmUVa000hRU;voPVVѮ#G/<\]]1zh~H}],]prrG"iDT(!-B̙3 Ik.;v III077ǘ1cuV߿ާ6Gm +++XYYaƌ/_"$$ǎҥK1g 8ޘ:u*DMXXXG8{,%DKQD$f͚~ _|.]Zn%Eii)8gggXYYٳ1c޽`ݺu4h Ԅߏ 9s={Ě5k`ddp(Fg!h5W"*T4i9s̑ۅ333zj`ԩŋcհN 6l~'`͈E߾}ѿ߿cOOO!99wDoPQBDjԨQ8s ~wL0#5Z~~>>3cǎ;w. C={6n߾pcʔ)ݻ7N8!Y2h tgΜE"H *Jȹ 44׮]?[7nD׮]i&,YXf yG\\z1c` M,(**͍f9%DT(!Bii)[nTK.W?>|իWK={ġC]]]cܸq,#WQ$΋/;TVcjjpX[[ǎI(;;pss5_|M̙3pOXZZb׮]' ;̤%UԩS={6}ѣGaiig"88ǎ4M憸8cΜ9pqqGx666t ^x}}}1:YYYlܸ;vugϞyr|ƈ#TTT~zܸqQh؛ 4RBDffΜ7n %%vvvvZ;==... MM6ۿ$GTT1j(ۇG"!!w T~1118p \\\7ߴwVgggt%Do?~믿ƿ'N@EEc۷oNJ+p=qZBCC?IeddPQBD"h"ܿ0a\]]q9|g 'bEII 8"ၬ," %*JDݻ:`ѢEȑ#yGk~bʕ%:wLJQPPbZb %D" 0VVVyiձw^?y9c["*T7b۶m43GGGL>]*ׅqwwGTTrrrxGK`"jTrJ;2d(g}c|G(++GdaooOJꐞeeeB%D,]gFyG!Yx1 cQD cgϞSNc)BE (~:-Z; yC㈌ Qγg"RTm65 ZL6 O>ӧyGAAYYFKjF#%D(!#-- yxG!uՅn;())aРAx"(bh!)*Jصk,,,; ܹs"2nnntb'55c)BE ?3LBs}􁵵5;ȸ -- 񼣈JD"߿bĈFxwq)1DZZZt 5x%Mt%"EE 'O yG!0b!99wСCikiBE Nѣy Էo_tQquuEXX***xG ϟ?)!"EE {ٸq; i$ >\.Ḻ"77ѼTD(!bۨ( q-1D{066pshjjBIIw"E(!b/..~۷}.uOc۵v¹U#Ǵ4Z%D5{ѣakk+D1z '-BQہIk8?pMuĉf1X_ijjD <.\8zhZZZP둘 DEEaaa{n3nӦMx!LMM\r5nذ111߿?n߾2ؽ{͛[nEaa!qeu]7)444prrBii)nܸ`oiiiGBB0k,ڵkk\7駟ϟGII Oj޲eK4{J^^h"z"Bum%$$4ej[ttp[ՈL?_^vjϜ9ӢT~VMLL svvݼyܹ-X@x mkײnݺ5‚Zvݻwg؍7mW߹n{edd0az~Cђ֑*JX gǏzyQQQ)**2%U*++{G[[[6555xIm 60f^\͜9 c57{Jzz:ddd!i9i]+"֪撈nݺ^Zm{ddH$?Gyy!g6mZ§E_fw())ip^IΝ^iiia~:|rdgg?l־SANNN}Ʃʂ"ZZT Lo4i W^EYYТ~}||sATT9sh;fɓrcٲen1:'ϟGqq1bcc+(oɑʢs033k`OOOŋB̟?@Xt֧9mۆ<[ࠂB>iiiN&m'??cF4@ `hk;v(^ޥKXUY>}j\4iR=z;W˗/lbccmܹsoS;M0E}+To4f``Py޻w]]纱l~^O:19::%K!r4RB^NPfffCII+W`ܹӃ"|||cǎ+W`Յ|~~~ U@@v[[[())AII VVVXb~RSSv`rrrD6oM{:Wlo?~ SSStԉ1 lڴ \rr2t˗/cȐ!)KKK?~)(햹9G"R4RBĞT=ԭԄQQQӧO 0rHQZ)LMM%\yGi={ر#$GE ^^^ mzh8'i}'+҂("GE Æ CQQQT?~Ce4dDEE/))Ih iCE pttĉ'xG! (//ǹsKK;ggg!**w6* I$tڕwVAE *O &sQbĉ]Ĥ%%&&wEE G2~gQH=n݊ӧC]]w63dȐv3$-- FFFck>BxSPPym۶&^KƟW$ci7nwVN qL]"Q\pwR[!CѼbdff!oHN:! 9( =޽{zjQڜLLL98U ~`ddmmmQZ%DYѣGyG!YbFGGGQhIOO)(..NjGI*Jի|vq _DDDɓoxGfȐ!ɴ4Z͕#*JC>*++eQڽ |xann;7NNN(,,DLL (&%%zzz]*..F\\;JH$EEE_>8ںu#Zwuji> GnBii);JH1c`јIEFFJ( @E p_yfZ-Zظq#(bcРAxQZEjjT._NE !K, ֵi&ߠ;ӧTTT=!prrUQQB_|gggxzzH'O⣏>ݻѷP4 IDATo_qĊ  ]KJJIsJ8 T PQB,###xyyw$I&?/8bQ*GJj\pnnnc:*JPQQSqQa"bwQ+V# W?UKHI+))+W;(!REOOΝÝ;w0|p$"##1tP 0kdeeNjtMۋ@aa!",,,p5$''#IK.Æ Ñ#G;XSSS%+'Ν:v;JH%sss\v pvvӧOyGHǏLj#0qDݻAj$i\D-==FI89vƌ;FH-cccAQQ}˗yGXf ƍ9sয়~,Xڻ󸚳ۮDLQJ%Rd aDK+odkBٓ%Xf[ٗ(K-nj@Eq|{ޝ9DSO?LO *JLk֬ѧOk׮6˗fرׯcKؠ 7n`El|GLMM%pޏ&HIݹr =z$ws5 "==6667n&st|r+WYGI ɮ| M.zzzسgallC7n:FV~z̙3wŬYP^=dL͕v8z(Ǝ:JtG۷ѻwo,Z­[XǫR;vL4_$""K.Ž{xbhjj(lmmcܿuo3tS<eeeŤJt[nœ'Ozj\z;wFǎpBIB͛7ȑ#ѠA#==fBFXǔ+PQQ*Jֶm?Y P7oDHHBCCVZapppeO HIIksΡvvvpvv3tuu4G077ǶmXGjG-:̻qlll===q%|4ɓEii)Zn=z{077G۶mѪU+3CFFqu@ q0d 4ZZZbxD\ϟp^lnnA~cE7/_ĩSXGaBQQQW"&&FZAYY[F۶mѺukhkkCMM hذ!Ԡ7oޠEEEx w޽{%066Ƌ/йsg_:tE$XXX\\\K|֬Y3,YӦMcEu8~8YaBjANNݻ{ G!''hذ!իQҬY3mm۶EvЮ];mŋGAAI|w8u:NBCCǏ382mҥFrrAE !R(%%8 "?aff$tؑuUTT֭[ƍc)dbb`Tn"QF%HѣG#88%%%/F\\T_UVPSScEfc9s\.%H)WWWyᬣ/Baa!Qj,##۷gC9r/^IXGaBTVЫW/ݻuhذ!nܸ:J|tS˱b L>]jWg%H &ɓx9(3`ii)E Ԯ#Gǘ5k(B 455gQX[[K]Ql"˱rJx{{qƬH*JbՃ%^ 7oބP(dZ233(֭N:ġP^=L<[n^%YGt!Sn:XH*JSN˗/q!QH:vMMMzFJpsscE"QQBh޼9 6B(5|1q_AA~VBdȴiӐ(QHpu1(##***hݺ5(28x{{={dGbQQB ֭[: |>^z:g|CQQuo>b͚5H4*J1-ӧ: 8CLL (}}}1d«W0|,X-[dGQQB߿?v+VB*ѴiSH>O4jgEQQB Zt)pmQH%a^Iff&F ^ݻwc۶m(!D 8Xr%(7Q+//gR999ˣoTZZ ///ޞu@E !2aѢE8vRRRX!B^^RSSYG4Rm֬YgϞ5daɒ%OC]]]bddd@]]H$Xk׮EӦMYǑT"x<|}}WC>]J>vځ㱎"JJJ0vX888ÃuBE !2O>pttܹsq8VVV=RBnފ+Cܹ f"88u+++"//u h9׻y&VZ͛7EH*Jqfff޽{:7x2󛧵5qMQ*{.|"75j8RBslݺuIܼ'O@ HW_l۶uEE !ryX`~W<}uIW\Sظq#:Ԣ91o<|w1c(?VVVI)))FfXGرc1dq% 555رc8yd#7M6 :uڼ&M|{epp0ѨQ#M61cMyy9,Y###~Ʈ]*͔cǢ~YPTTTw=+++z YYYĄu 2k,BlذuBVZq_l5oޜP+ @1 qqܢE*}.n̘1ϛ7v֭pWh7m4IuSQQ8:HϞ=)S!5)**r.\`E&H !rXh۾&k׮ĉ[&&&(,,q֭(,,Dqq1.]رw!??_4)wӦMΓ;w@ `jBMM fffΝ;4RRM)))lSӍ>,H0009s;wTx+}:LW8̜9>Daa!MWmߏ pss=k>x<mۆ|榨|˔hڵDLvs:v:ѧƠ7uĺ*"U\\̙s\iiG={ѩtn``pwwr/ٳq_mTu\Z̛7377g6n:D*//\\\8===իW,)!DΩ0w܏1rHg]tѣGž7mڄ#GBCCjjjÇl2ݻwjjjPSSCǎヽ{5KKKK$''Y/^ 77WaժU8u9---qd!wѣI&#W߿mٳ' .]=>}͛3 0tPl߾]O4RB ŋ[zzzh֬bcceHII$HJJѣ1c *H%e˖aȐ!9r$233YǑ+L'&''Ԕ%QNN^zחu@E !DaϞ=hӦ :֭ӑDtܙ%MII РA:tvl#TB>ǏpqqAII HrwEnnn oߦ?3 #7(!Tcǎ!66R}y/%(**E[Z GÇa``:\R)+++ѣ_XǑyM4>$!!4'pBlܸnJ%*WƶmXǑyݺuc2֭[033c~C@n޼ 777L6 ^^^%*J!8q82UQ I2331h ?dGnQQB"L4 FBLL 82999އ#11]tsJ 0ڵi CTBeӦMӧ:LR+D~~Nr˃#pI$ר(!T"<:|>u$SS:s-())݁KJJₜ?M6eIQQB6uuu:u zzzppp@VVH27Q1իWg8s tuuYG"RC 4ٳgѼysy`iix磼\KHHK78~8BCCaff:%kذ!Ν;&M?fIft AAAX~=РAZ.11QUVD޽Y!qU#!+B˖-YG:HLLD||I9ûw>$!!!pwwٳ믿C>BXhkk#22zzzCbb"HRcӫ0)--7 U WWW_ri5 SNŚ5kX!_@E !Dl6ltvHRAYY[n %ڵCiii+**#)Ο?~ ƍÆ X!@E !Dq }ŹsXG +|8XGXjjjشihV'IT=شigĉ>>h۶g!88Ç'钍Rgq5#I-[ IJ=}}}BAAM6f!((:u*nJloBTqu899G8r~ֱ$N興^x@Ѐ455ѨQ#hjjFFF044D}}}ѥ _۽{70w\Zu"$DQQ<<<,\t tc Sy_/R233C]]ݺuCtt4x<JJJv͛1}t,[ K,a %f8ŋcĉشiSɺDرcx!`gg~}&M|uHNNFTT! Q~}8::~ٲeX|9,Xu"FTB Ø1c`aa`hkkT={ ")) :tرcѯ_?tVB=pE:t/^\]]1~xtҥ-۷cYG"bFE !D"$''cȐ!8ԩH޽{Xv-Q~}bر֭LO>Ło>$%%-B>}eTII Ǝ08pCeZ}C;vDLL `cc֑*-- Ǐ!ñqF<~6mbZ@-0w\ܾ}QQQPUUE߾}aee'N0 ٳgq*Hd%7Æ ܹsEb@ ܹsaff7o" |>&MmHKKc˗pppLRKBD vÇCGGu;z(fϞ8qb 111:u*1{l,^Ζ?|(..a``P'%H׿BpwwGLL ={ \ruj?#GO>क़U DIDAT򒺂w7o~~~011")) PQQkר /"7:v숸8XYYׯ^r;wFjj*.__Mooosΰիk8|]ve ;ԩSŴiXGuqqq1b8CDD ?d\->6rFJ!R УG/} @tt4 ]">>mڴ-bcc{^wƊ+ S4RB:Xv--ZGGGiӦu#;; ׯ:y֊1zh?ǎC~jׯ_ F߾}k))TB֍7ꊲ2߿vvvuv@UUhٲe[BL:{'Xeee OY-'%.Bnݺݻ˖-fkӧ||899AMM /_wu1}t &&ωq *H*J!RQF8v6n܈5k֠gϞʪc`ffM+FJJJ쌗/_ܹs:k֬СC䄌*ڵ C޽-ZaJ"ɨ(!ȄS"..ܹ3>z^^^x¯:'pYe6xo>AQQu5f\|<`H*J!?I  bHZE! Oցcb͚5x!ׯzASS7>ڷoVZIMt%mSLYGy044DqPPP/_"//ӧڴi89MTB>ХKqBUE`nn.222󑒒(y۷Hsd %`ΝX`rssi:l9smB!pE;wW\<<<0}t4k֬FE !pssCAABCCYGAAAÇسglقo-Z}_!@dd$Xǐ+(((@LLLۺuk,Y<5kw^"$$BE !D=zٰ`Eh-[D||Woooddd?MP(ƤPQB{cccIO푖4j~~~ ޽{J"Ҡƍ3ːUxxU~/.oСC(L>j5|_#*J!rȈґgn]vĉbTB^zz:ڷoaaa͚!374RB{2Rc@xx8޽{|lݺi&h| 6n݊BҥK'OV;úuݻ#11slmmqݻ000@LL ޽{hܾ}ޅkР?`ll 㐚Z#++ zzzz*޽{(Vٟ|> pB+m[g[D0,!D"x}FJ9\pp8{ 9wG9sLk||Xnn.SPPЮ?yCE !Dihhpb\zD?չ~+((]e?>}uҥڗWոqcQ[UUUWXXQZ+J;u8{9tR}*wމ/jyp\>yCo!rOSSV&sɓ'h_WW~~~}7a``9s wܩq>';e5Y^Ml2=~вe*|z?J}NM>yCE !DVQZZZ7obx5fϞEDD]___?:y ~ھ߫qƍjk/Jׯ_+šiӦh׮]޿k׮}tŭ&"455kS}}}x!H޽{sSL{~~~U#>9Ø1cpyI&5zmݻbEB䞱1>|H[|3V!^zPSNӟ-Hׯ/kBгg"TB^Q^^ QχqsSN8w\mݻ[[[:u aaaڵg=fԩSѸqc()).]BӦMtү{QQQB{WXG+ʂiJKKo> /^Th[RR+WB7mڄףK.PWW"ڴi///$$$T.SB!===jH틈 ---@AAvڅիW#;;<Ɔ P^ ڵk b-&EuB{F@@r%22;wrssyflذB@V߿4߿;v۷onݚ bD#%w-ƃ[TUUc}q7S555EѬY:JLj% M4ƍ1n8qd޵k`cc{Tw|޾]Zt@II NNN aEaРAkqyy9ͩ Q4RB!hkk# mmm8::"** xx<>;rӒBE !G(u?L6uciѰ \˗/… 坒Q3g`u>|C!QTTѣo>Qdځ'S^˖-ꊭ[#''Ǐ?tItٳg,b:@#%򁔔!!!Ȝr .sq5hiiҲ(!OOPQQYG90a߿FC$ %xXZZ"55 `aagH *J!YGN+߿OH(!J\v ?Ѿ}{q^yy9w;;;C$%R777z gϞeEرK.Ejj*%!UB ###޽Æ cGjCذaY!B7bڵHMM&8Ri„ ĥKX!B P]{aG8qÇGBBLMMY!vt%PTTD@@>L;ЃၥKRABFJ!0gؘuWRR޽{A8s w`eTBH5")) ׮]CÆ YǑhsCf͚CRM;v@ii)\\\>k-ئMqF8p R#TBH5OOOB֑$1sLٳvvv)CE !ԀΝ;SNۛu OOOopsscH!%!D|  6Dγgb6m|||X!RJBWDZcǰ{n@ 2ǏڐoBE !|A… Hunݺu?~<.]͛7ӎВ`BF)))ׯ6mÇАuZWXXӧcϞ=ؾ};~g֑ BF&&&~:ХK߿uZݻ#44'O %"Z˗1qD3^^^(**bK삂Э[7hjj"!! `*J!DL"44ǎCǎq)ֱ"++  رc1uT\|[f*J!D$t NNNpvvƃX*X|9p}\t W2hDQQB!y8t.\4`…aZʰw^aڵ_HZEo!`ƍXv- 0qD̝;-[dٳVǏ1zh\9@E !ԑBر֭CNNF26++ Ν;OOO̟?zzzLsBE !Աݻ~:Zn1c uիWF`` ^͛cر6mDGE !0c߾}xڴi{{{W-v߸t.^DԫW...3f 홏FE !H㐐HDFF@ @-`ddCCCџ}444 MMMhiiA o޼A^^޽ >4޽{ ܹիY|BPQB!111Czz:|>|>?~\>6lCCCV-&'QQB!Rϟ?@ }y---ԯ_M6eB!f4B!D"PQB!@E !B$ bIENDB`state-machine-1.2.0/examples/rails-rest/0000755000175000017500000000000012305405267017521 5ustar boutilboutilstate-machine-1.2.0/examples/rails-rest/migration.rb0000644000175000017500000000022612305405267022037 0ustar boutilboutilclass CreateUsers < ActiveRecord::Migration def change create_table :users do |t| t.string :name, :state, :access_state end end end state-machine-1.2.0/examples/rails-rest/view__form.html.erb0000644000175000017500000000166312305405267023320 0ustar boutilboutil<%= form_for(@user) do |f| %> <% if @user.errors.any? %>

          <%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:

            <% @user.errors.full_messages.each do |msg| %>
          • <%= msg %>
          • <% end %>
          <% end %>
          <%= f.label :name %>
          <%= f.text_field :name %>
          <% unless @user.new_record? %>
          <%= f.label :state %>
          <%= f.collection_select :state_event, @user.state_transitions, :event, :human_to_name, :include_blank => @user.human_state_name %>
          <%= f.label :access_state %>
          <%= f.collection_select :access_state_event, @user.access_state_transitions, :event, :human_event, :include_blank => "don't change" %>
          <% end %>
          <%= f.submit %>
          <% end %> state-machine-1.2.0/examples/rails-rest/model.rb0000644000175000017500000000076312305405267021154 0ustar boutilboutilclass User < ActiveRecord::Base validates_presence_of :name, :state, :access_state state_machine :initial => :unregistered do event :register do transition :unregistered => :registered end event :unregister do transition :registered => :unregistered end end state_machine :access_state, :initial => :enabled do event :enable do transition all => :enabled end event :disable do transition all => :disabled end end end state-machine-1.2.0/examples/rails-rest/controller.rb0000644000175000017500000000125012305405267022227 0ustar boutilboutilclass UsersController < ApplicationController # GET /users def index @users = User.all end # GET /users/1 def show @user = User.find(params[:id]) end # GET /users/new def new @user = User.new end # GET /users/1/edit def edit @user = User.find(params[:id]) end # POST /users def create @user = User.new(params[:user]) if @user.save redirect_to(@user) else render :action => 'new' end end # PUT /users/1 def update @user = User.find(params[:id]) if @user.update_attributes(params[:user]) redirect_to(@user) else render :action => 'edit' end end end state-machine-1.2.0/examples/rails-rest/view_new.html.erb0000644000175000017500000000011312305405267022774 0ustar boutilboutil

          New user

          <%= render 'form' %> <%= link_to 'Back', users_path %> state-machine-1.2.0/examples/rails-rest/view_edit.html.erb0000644000175000017500000000015612305405267023137 0ustar boutilboutil

          Editing user

          <%= render 'form' %> <%= link_to 'Show', @user %> | <%= link_to 'Back', users_path %> state-machine-1.2.0/examples/rails-rest/view_show.html.erb0000644000175000017500000000044212305405267023170 0ustar boutilboutil

          <%= notice %>

          Name: <%= @user.name %>

          State: <%= @user.human_state_name %>

          Access State: <%= @user.human_access_state_name %>

          <%= link_to 'Edit', edit_user_path(@user) %> | <%= link_to 'Back', users_path %> state-machine-1.2.0/examples/rails-rest/view_index.html.erb0000644000175000017500000000073012305405267023317 0ustar boutilboutil

          Listing users

          <% @users.each do |user| %> <% end %>
          Name State Access State
          <%= user.name %> <%= user.human_state_name %> <%= user.human_access_state_name %> <%= link_to 'Show', user %> <%= link_to 'Edit', edit_user_path(user) %>

          <%= link_to 'New User', new_user_path %> state-machine-1.2.0/examples/Vehicle_state.png0000644000175000017500000021234712305405267020732 0ustar boutilboutilPNG  IHDR{kbKGD IDATxy\M׽[i%(*Čalcm3c|K2Y6Qt.ӹ>s0!h4!BʞB=!hQE鈌ta )͛pqqAVVh޼9d?!$'''B&D"ڵk+WpNF' QJ, ͅ.d|7ҲڵkSFeOH)=D"D"VZ;!q)ǡCb _~rNDȇ';wrNBȇaBT !D PB'-@eO!ZʞB=!h*{BT !D PB'-@eO!ZʞB=!h*{BT !D PB'-@eO!ZʞB=!h*{BT !D PB'-@eO!ZʞB=!h*{BT !D P@ (_᯿**0𖒒uVjgϞJ^tdO+++B$UH$+=QKToE1___%&"0!Dڵ!JX,ajjd|8:'?pwwNݝ-*{B LVfL&!h7999% -- aȞ7 bXL,ˋ5*{BJ.!T*7D|8!BԮ]Zj~pK#{BJс7tuu ooo*z )ǰaPPP 6wB>xFvv6233l߾G! ѰaC4mM6E58'h̞hB\rgϞExx8޽Ǐ1X +++yתU IIIdddRRR J!`cc{{{:t!F{)كSN!<<M6E׮]aoo{{{888࣏>*1-[,\*"11wA||<ݻ . >>FFFڵ+zaÆZiϕ'G"ضmBCCaaaOOOG +O"448~81c`022R~ *{1?~+W?T*E닾}9jW4TǏcΝ8rb10k,4h@Y>;رuEPP|||Teعs'V\bܹhܸ1hDKRSS矣Yfp~WܿSLQ^5eܿ+Ο?{{{|HMMh :'jΝ5j`Ŋ1bB8vdصkfϞ ToƍSD=ѿ.Vn޼Ν;c=z4޽ ooo*JHܻw>>>4it[nF4!Z~cv֬Y3\vZDGGC 'XDCQ'H̜9aaae澫VZq D;04fOTÇtl߾{IN< #88# AGDeƢK.0775wވ)tXޑ'*ٳ޽;pIXYY4Ù3gO>aaa# @eOTαc///H*)''H$x"tuuyG"j>%͜9iiiطo[`xx!jʞpo>l޼wFZxQy믿qF8pwF 7IIIϱl2eR]t%K0n8}BBB0x`$&&N:EGD֬Y-ZPW#www8::bڵFGDiШQ#ر G8pcƌARR@EGDi֭[>E 4666yG!*ʞ(E~~>֯_@SX~=y!*ʞ(ő#GPPP!C |;vwh̞(Eajjm۶|||ÇBT {9N8QF|}}q BT =Qݻwnnnh={ {*{p'N@޽!?7EDݻ7BBBxG!*Jpz;pssÅ PXX; Q!TD H'5+DEEBT=QP-AٳgyG!*ʞ(Txx8:w;qqqADDDP{.5k;qpp;wx *ʞ(LNN6`nn: AAA%9s`kk ===4j+w<%%cǎjԨooY¶M#DAf͚ڷo_c\ըQ#._we)-Ͷm2@|||J,/0gggc,77999={2L&|FF-^~:899ٳgW6#{08/#""5£Gzj:?`Æ A^^DG٦#rrr}2? Ě5kмysȟիqUt111Ju\\\pimZ >\|ƍ366FVVBMwF_G/ɓ k֬YEEE,**m޼M:5oޜ`6cccW@@jժŞ>}ZbVZ1Ç%'$$0{e>sLurd߿6rj&Q_:<`P{.񽋋 eϞ=ç~2gܳ޾}ϟZ⩍89!!ЩSt֭Rh(L͚56S8K:u*agg3gb˖-uV/wy@@  oE^|)sL7EVB͚5m~(r6ȥKJ|y(qipEZ cƌ)QRRZv-.\XWlԨѣG`z2M4)˗+`llmCeOFe?~x\rE~ &F!_'N@^^]ooo322*@l_˗40rH\v HIIY JL,C*"22U}BeOJa|Wf 4m2tRf ͕SK,$$6+W/2@pck׮ڴiSf#F(z걵kV6' g mť<==X,fcfb^*nFF6l344dٙٳ}7 (͊&ʗfccttt8p qFm|M8YZZ2===6tPիj/[m$MX97n@֭q gTU }۷oy戋C-x!*A(Ÿ8P((DEP100@ 9wlll*NJ=Qf͚8;ҨBUPۅK.cBeOt]u%z1zf}BoM ڷo; Q!TDb1\\\; EnJ\'*{py0d2N:wwwQ' 7l0ӼhSN!==C:(EQfrE7q!Q#{8x &Op!BT=QCOOEc۷ׯ(DQ pssի((**ի1eCT=QK.]v8v>|H p!>gϞ332͝;Xv-e͚5J/yG!*q#$$g… 9#$88Ç۷ѠA%%,?FfͰw^xxxCT= ?m۶*]C}J8v޲ݞ>} kkk!%++ ƍàAпVf֭ŋrJL7w&IEDD?Ʊcp!l޼UNÆ cxj&;;; //m۶Ŕ)S;QqTd2,Xݻ7n>h^^^Lj#^MI5Wjj*|||0x`@vv66oތVZ[nEnn.Dј=/^`ȑǏ?W۶ йsg000mkg}V!GGG 'te :b\HNNFNжm[8pTL&]K.~HHH@fPXXXf}X T;b2d9$'*?cc ׍7X͚5?+**RIQQϙqF2/PȄB!311a&Mbw,oT\y{{3HĖ.]x"300`ӧOg~]|322ie_kQT{-Z0KKKv)رcL__=IR_U0___ώ;Vz? o-yH&LDPٓ?LLLXǎٓ'O8w355ecr"H;355eΝ{R1HTnыb "*{"~z&yaʊuؑ%''4ɬCnݺz+,6mh&)bĉʕ+yf֭[#""hӦ N<;…M6ŋѪUJ=={X,/ŰH$!6,֧OfddĎ9;NبQP(dsq|TʾK&ؘ1cH͛{HLMMٮ]>`^R@zY홵5睶l XǎYll,8&&&uЁ-[|ж&L ‰/|2b-[d>DQkM2#q*͛SNLGGM>|1rfff&6ma;wfnm>`۶m+󳤤$ڵkx# ځ^ ]~YYY:Tqm޼YXXu/]zwJJ?due7 ۫mDQk(fffݙD"烤2;;;ڲ~M%fU$??oqLGGM0)=L&cfb͛7O-+"UGeE.\j֬ɼT+ǧ?s6l֭[ǥD+~֠AƏlѬw`TZԩSy{{5S~Q~c=yM:1===6x`v!.E:ļ322bSNzZyBBB۷/(8{,300`Ǐg2wʜc+D"a;v`}a"QF?%%%),[RR۲e efffL$};vIMQQQN:},%%w tc >}`ĈشiZ511=z@rrrko߾#G,1̙3 CVV4i.]ppp;O$;q`bbΝ;ݻw[nݻ7 kkj۷/c8qxG"Ռ^EFFw8p -Qtt4W^ɋ؍7вewnW^Ehh(q]$%%:::]6`ll SSS$ 233d2械B666h֬t777899AGGG!xHJJ±cо}{ޑH5Pqqq֭郝;w@o 4R̍:D"rssK\&*{!99H$###ԪU +VرZBӦMaONdgg>Å w^ۗw$R]8!IJJbgWK L$Ux _;;;̔~P>yJlLWW8pwRMzR/^GWvڥC 2 EEEe~. Ѷm[dyŊe4lق>Î;xG"@}O?bGUwd27[n}zb-ZPJ .@WWHII]0j(@GFvv6y"^C00b<}.]%H-55iii^ewCm(((PZٟ>}^eֳ*c066ĉ!H0sLޑ=Dǜ9s*Otsrrb1+UsW\q0| >wj' HEDsƍ2eN*,,Ddddn5_|۷c͚5 Tw?4h GZ<`zzzl1222̙36`d޽;{xx(%*ٻw/D,((wRtRd5kÇGLL n޼Yĩ$%%G ݿT*1@ @\\7ofϞ=Ɨ_~e˖C*f㨩%K… آ?۷vvvJ~իW+,zl+V`۶m ϢJ A,o _-H՝ƍGa:p\xksA&UsHL5k~Pb2P|㡫ٳgD*@ef&NP~w),,Ĝ9s0dtؑ["8qrX>66654i!jQPP)S@,cƌ#rP٫;v`޽8ujժ42 IDAT;l޼#++ ׯGz`eezܹs=z4ݻ54iR)f̘S]Q٫Obԩ>}g𔓓%K`ĉhܸ1,5kĔ)SJ,S )бDyG"oԩSaaa˗P֭D"W_}; y_}Ə#F ,,w:LQơCp)01c Ԯ]w֯_W^apB"FU\VVL___"D"kHHH^}իyGQgϞaݺu;w.LLLx!HWWFz/^Uؽ{a|G,YsssL4wRMpQ^|9vWagφƎ;B=x.\џIh#KKK9s)))6l[OP#EeBCCq^Z)ihܸ1F; Q ǴixZ4G1wwwՋwݻ{nîZl ^^^hѢ&N;֡]*ɓyGQo-[Đ!CxG===V|rL:vvv jU޽{k䘘ԩƏ;F#{k.fff(Ԃ еkWQ<'++sУG,[ ӦMCtt48^Eɓ1b[+o|ٳgЮ];޽̺šW^044&NrQI$̙3CF0o*^nn.srr*={d2ݺuc|=5v2oԨKOO; ommۖ Sk0gggU"WRR^@*/335nܘ"q4;v,ٳ;+]L|#""X~~>;w_~e߹sgrrrؼyfaaQ>JcK.eXXLL J֭[Ņ`7o.ԩS S a,""5jԈ`͓kdd 6Ξ=0SS2a>3f0y,--Lⲿp;VF dQ4a~;+]Zbɓ'Kw K;::Z,55`B}W}a ޽{;wf}ys|s/ɓ k֬YEEE,**m޼M:5oޜ`6cccW@@jժŞ>}Zn/^P+D";u(^+W޹ncJ2Xvvv/_VTTr/kԨ֡#33\|}Fa|?ڵkWaүU)7Wff&+]bӧOWy==zGaѻ344ڶm+_V>>o}!!!Uz/^7sА^I Xvv6(jʞ'O0,""RWX)))ϏYXX0XZnͶo0}}>奿ߵke:::J<==0ٍ7c[lɆZLӓbV^=6k,իfddaÆ1CCCϜٞ={R=<<c߿o(oY&xYYYɓ'9;wRRRu=bG}Tۭ}1P؊YԬY矼chǏ3PȎ?;Z8=}]{= 222 pvZq+1ŋchѢ DWTwwwL8cǎEZZ8jʞO@&M333gϞ%.\Xq+СC_eD|055_|;ڢ,99{ǯ_k׮Ev```HڵkƴÒ%K0h "Ql߾ǎömxQKt={^x:uE"P>ѣG-[6DJqrr¼y0sLxzzܜw$BGzJnQ8jʞ/_FcTǏ#**‹i X;owAeٛWoTwK,'xGQ8/333^+V;wxQ Tiį7._G^z;An0az*{t%Ko߾pvvh_~QQQ4W[Psfhh;;}4"""樞ƍc7o$ 8*ʞ5j ==wh":uEi4CuM===ZwFeϙRSSyxogϞExxVͫ-qkG—>-[UVٳg,*{ԩ/^-Z=zŅw)((2rH888P[еq8ϑO"%%O>P(Ĉ#x{0(&M+WD^͛r,)MJJ v܉$"11HMML&+ѣ ZI=z@QQ֝nll7bȑR>S!88wCGJdaa!P(DQQQ B*3Z#44TPa+V@֭qуwBcJ"¢"nwEk׮OxG!DcǎٳLQ+ٸq`dduj׮#uE>}]T&-ZxٳwBed1cttAQ_?/^ ϩP Xt)ݿʞɓ',u4UsZ5M#J1co>QT=?~|-,,T_x1:u޽{٫6333L<K.ʞ3g+f͚5ѬY3-** ǏWi^͜98p(*ʞ bȑ%B!w@1Y/^ gggۗwn377DŽ b QT=G_~e1`H룣G066朄TF@@_sA˙N> T uϞ=Cvv6^|,dgg#''044!aiiY4`?mXihҤ 8F4;vwS&ξ+x}#֭[RSSqyܼywE||<㑕Um׭[hڴ)6m ggg8;;W8z(>\igi^}̚5 [۷;7td:tH|puuEhh(⠣[[[888M6`jj ###|FF Dǽ{p=#99pqq+z聶mB(|hpU 7OOOZ ]tM2jժZjugϞ!44ظq#̙ bԨQCܸq^L<Æ Ê+`ff;(ݓ'Oٳ5[nښ(5ǝ;w_6l;M6|:C a[fEEEJͦn x U$ɘ [r%(P+QBB?~>>1c&N;ŁS?}}}K pwwGpp0(JAe_ 2 >>>yQy5k޽{qlܸwjF.5'BCC;QW²epuڵ ::tsptt?3fhcj憼8je̘1L;KIIE E߂1I&aرG-x9~Q>أG4v}'8w Ge7Yl(jk֬_-?~ A+"##;BQW ==gƊ+h^:t(ڷoYf LG|2( Ee_%Kq3f (ƞ={??B:@ETT( Ee_7nĂ xM4#tRQˣG5TvpU1ʾk׮#]}W8qbbbxGǏCOOΞPtdm222a|Wh{{{ j׮>}`۶mTʽ{ JѢE Q9::Ν;c( Μ9|駼hAѣjq[nA,I&EjjtΞ= '''ܐhQ۰X,(Pя?D1s5j}Ξ=;;ݺu ͛7(HKKC\\\]]yG*nnnjSc322&;wD"t;VOpHRQ*: 4@rr2 AeGnM4A5xG*ZBNN>|;JQXXHe%,,,T޽W)W7vvv B7oބ씾ouUGfffC!w c`` Yn݂=h^K!==w fuv$c 1/*}dunݚw $T*%%h><BW\Ax JbllL8lZj)eæ`ر@5 DRm^z8q"K<ٳgЮ];޽[ĵjBffW</бcG'77~~~044|}}ٳ2طo`jj ]]]`%~ss333U%LEGG3,!!A)|322|yW~ʬ[l/Kָq2,˖-cM4Qb׮]FL*ʗ>cǎ-5jԈם?~ommۖ Sby@9;;,%gÆ f͚c(aqUCʕ+GXXnܸ,y|ڵx5jܹs_?266Vk\|mڴ)` ??hԨ=zիW6l@NN'=z6۷Y`` ֬Y͛#$$ۿWMӯyv1I$֚7o3gΔX/88죣RSS& Zj'OǕ~defhhU3 ,ٿ'OdXfJ,/**bQQQllԩo@Pf+ `jbO>gI޴sW4֗}VTT.S===X/++>??_}gvv~Ν%OU2]]]wˋ_(999 Օ/Xv9TV}鿏 gKܹSIq ^IVj \aQWWW}Zz寢cEx9,,,ʸ}6ѫW 9|0<==!Q^=̚5 %^cذa044>g|7WEN0~xttON_\ԫWgyG*))I~)e]vsر#֬YUd@(,y}JeO4})^^^[.~'Q4c ˗/ǴiTT=yG!DaK ;w.~GnwTFbb]Jp6/ȑ#allM6Q-[&ҦixT… wU>b{EBBxG)hРxG!D+0j(4m_}(jO"`ƌXx1,--y)aϞ=GFǎy)wVK6HT`bb;wbڵGL0fI Azz: Ae_ ݻw… 1zh<~wcسgx)c۶mpssCyG!D)+iܹhӦ qTڕ+W0m4hժ8eرc4C }%D"lٲޑTRBB>S 2_|8ڽ{7tuuYU+֭[puuņ cǂ1;JINN+:v͛7S۷ 4T޽{ nnn8z(<ٳg2zɏUQ||<._LC8D?~=z{@eEpUgVmj]ڟNmUQG, .TM@>?,2@8ɣ9͹?###رc۷/RRRi&\^z={B*ɓ022bDvB:u: )?Xt&&&8s LLLdu 'OLIٹ><<<`ll@!lرcǎ /Arr2z ]]]\pիW/Ҧ{tѳgO4-((۷Gƍq%.y1n8Q8Nx/Fvv6D\pVVV%mժ lR+!"Y]tAϞ=qI0___֬pb_'"44.Ӈn4h7nXf^{ 4fҥKg}36H>qZ-[FtqוJ|r 4p@\tlllƆ^:\&MD'T: KZ}>~~~;w.֯_˽@ Y[nqXnrrr*!aԨQҥ \\\r}JZZvލy9'###1cBȠs璾>5oޜBBBү2I$Zn-;vurٺu+RBB(gݻw4h<<<|rihhKΝ;P_$ vGGG̘1'N1`C-*Z_sss_@ Ojذ!Ο?'OݻwpwwGn(Bff&6n܈c„ С|Kbٲe}]yao߾Ettt?H 6l؀իcĈpb?e`ĉ(UPPPgR˗/W9 KNNƦM߫ŝ8N4؟>}ϟǚ5kdoTj@YNDx> UT*: ǩ$ӧOȑ#JrEdgg@ ҥKb]'N)84o޼/_IJeXGQ$}XGQyiii w/-[z+'.W^X,F^݇@ (Ӥ`ŵabbCCC̙3Q<%55_?eʳnEEDDɓш 8=p5ښ { "&JjժQ.- : ǩ-/|q;$$yZ:vDyV~!8~8;9z'""-Z`}D􄮮.D",IHHr[h7o~qjMmD"WРAb_JAÆ a``U۷o/}jj*KTZ5kĴi e7fGGGGac%WRbG11jԨb2/2}qۊǘ1c`aaCCCxyyiii={6a``;;;̛7sܹs78E 5c@ž>sLPcժUvyzYݤI+JZ36Çַ7~D/_߾}K~XL]RnnbРAԼysJrqwrܪ=9N:žuVٳgTlڴ ~"߽{HaG}7MDGeԩS8s pXjD"ABBB7^r%0dee! w)5ǪUp !""h׮.\;wʵ_8v~~Vq7ME8pA[[[޽;Zm>]&[@HGGH-m}p"hrWa$TZGׯ_cΝ@FF͛ڵk׷H{ʌ+77UbŸJ]]]ٲ(fyۤBOef!o%.Y ,@۶m+a9 Rb_Zq333=vf͚DJHX>y)))ew-իW < 4nX,.."Uկ_@yBCCKmgg{)=GNNN}@PPΝ;ŋ=qež4Ճ@ ͛f͚Yf}}Jn^~qַl߾鈌ĉKl",, AAA29RիW%_:ǏG`` $ ]ǗA_|Ñx̜9;w.ANܞ2b7T1˗/'_-qv|cjCDdee%[nkk+~?H޶G$emҥKI...E9rd#))5jT622R… >nԩS֖B!Ν;emR=Ԗ*&{LR/Ј[GpQY|>|8,XPٱ̙3ݻ7BCC:FKm J:{lllX(a۷//WIv˼icccaff8MgeM$$$ ## [QN87nƍpR3q%{}YA`qNm썍Qzu|uO32]zÇq޽uhj{fNNN>*ѣG[}Bxt1άpFS3{pvvKurrbX{ӧOq Q8N3nݺ:JJl/e4pWyԺػ!99Sh:DI&}vbpVPb䄪Un#H$OOOǒ%K0aJ4Z{]]]iW\aE%EFFB(x^VVXt̛7uj]SNp*޽{hРJ]+Wbܹd㴆={x(*'22RgXXX`ʔ)pVQb߼ysXYYٳU?l2D"q8N}A>}pQTX,ƳgT̞0m4bĈpQU9r$z/^_t}̙}0&8N1<==aii}2߿HQ~ ƍ+84 B 6 gEeܺu e-Bff&~WQ8NkiD#F|bUVc޽{Xn~WT^uZS۶m [[[ٳurrr6m0!J1~xhƍc㴝{@/;v@vv68Lݾ}pwwgcӦM~:mV8Q?'NDRR֏݇Ԕ4 ^ܹs1{l㴑FZjaذaXv-(L׳q„ ]6TF{ Q c:{n={۶mSyy8Ni\oӦ ڴi 6ijgtԉ_z)S`ʔ)`㸢4ipQ<{u;{,Tv)}DѣG>sb?x`bŬ(ӧѵkW)}ׯGPP/>ǩ,B>>>ؽ{7}*͛7@rrD022) QNԫWՃamm-ƅ Э[7xBpssCժUS4p\y X5bB,XFܹs &?oʪҶ ((7oDVVFݨQ#ժU?11HLLX,FLL N<(,--:ɓ'hܸ?"&&s3{YL^WW[lرcp>|Ǐǃf͚hРPJ o+>>QQQ}6\`|"2d 5j(`JvA1G+u[W4޽Ku!]]] {R=p5kիWP hܹtϡ IDATJKKS6*&&hԨQdjjJԩS'ڸq#yFۋ&4i8KZ5sh׮]d``@B@{QJJJΦS6m5lؐ~g}233ɓW_)З_~I7oTHԢE rqqLqbFө_~@@_ښB! :+iO'33vI͚5#ԡC:|0斻ϯ)**JI9-^ȝ;w +eA˗/'SSS277Yf˗/+yO*˗iC͛73gIWW\r*^͈b駟HGGaddd!\?ƆTBϧT%I剌ѣi ק%KTrBS>^THHիWԩSŮ{-jѢ B7n*9r\|HGG[JNN.Wf͚4h JJLqʡ^#?AwŔ)S \GXEu֨Z*n߾m۶VZʌ4:uիW{n;v M6ťKKKKC޽aii]v1>r`FYήYǡ WWW2445kT LuI /t4''Oj^Ǖ?_,?11W\޽{ѲeK!""SNպ[YYYþ}кuk<}f… pIԩSuLA֮] X[[C(8[Č30j(L:AAAhذ!l1wEjкuk9s[nhǕV<:te%\~{e.~HUNVX0COO7fI?3M/^`ʕcqWZY߽{nݺ!;;[6\:ʚ1c4hCj*֑8wޡK.˗eB6mڔq2ֿ;v T*իF'%%pJ^ ǎàAˇt8Nh2d/Wׯ_-[q\iM:u*ܹ'OҒuֳgOlܸ'O˗Y8 bg˖-غu+Ο?;;;q4¸qp= >W^EzXG84''ƍw}dX|9(C#cҥh*U`Xb޼y:qh\X`ϟχowpuuor ҸbaD"~ #˖-Ö-[: qhTONN˱h"鱎ѻwoq\>UoߎjժaĈh~ Nj/XG8?S[l~uU5m۶m: qјxExGf0~xܹ999p *[lPfMQ8HMMYG8R?|Ǐc̘1pV ?u!ٳӓ݅S ŋ!YG8ܹs􄁁(\>y7u788ԾΜ9^zbjj www↓pSboߢgϞpݻ7N::i=/!!!SQ»wXG8hҤ \ 6pvSb޽r <&&&044Ĝ9s  1""BKKKXZZbq},wdff"..N送E W4i҄{cL}||<>|''rT 999cwՄT۷oZ_"<==!lXYY7h91>OII?.#{^x>y@@WΝ;R)|||аaCjժpwwۋFE èQ`nnl/ϴ4̞=000͛">|;w)akk)S --kq#@r #ZhAh̙VZm#99իW)յ6]v\Y۟FUcΝdll,zl{-ϩ:P3{TREu)߸˓Tlڴ ~rm8k֬ATTpdeek.vժUqDHk.\Ν;em׮] شi222)ɓ]V>}Pǎ ,JtuڶmԤI@H/gJJJ*w |f{_Aח؋G׍hҥ^6a``PӋ?OqsssYXjٲeCC%<?^>8N4ث0NjՐJߖH$*|Ĉx5v ddd`޼y]6|}}}*4TܛR}$''?͛pppӱk׮O^ _x呒}} q\Ũ}J333=vf͚D F3y ?;;;eE`L!!!Xr%P.KU(?Nj}9իW7oFjj*5kf͚e?~\ 6 0aA" 44&L(vРA/A||/׷YYYɖʝ1--5jTK/JlkddDᲶޟ̙3y̘1\]]+)WQvvv000GcǎhѢD"D"0{l矲v;! ނ11zhXXX@OO͚5]!!!:u*lmm! ann"44...ׯall H֭[XpuEy14h8H0мysK.eE^x;;;ԭ[Ѭ|R1f̙3u$???xyy)t~,UgϨXjU{Ń0zh@' DGG{pcj_읜Ԫ5S5 wB|2,--`S@*bq}o޼9 \CI֯_5k֠e˖022.lmm1~xޞuO ֭: i5Ũ^:O :uKG<8 Ǖ٫0CCCo^!!NuH$;w}a㴞{ݻ7Μ9Yhh(RRRЭ[7Q8NiDѣbccTӧ>uzQ5j-[b޽pJؿ?F: qАb|DΝ;: 믿f8hP9r$pQQ8|א!C}3xKc}*U])ssy5?u1&M`ܾ}uqF4i:t`hToܸ11oWmmm1a̚5u۷zSAW`ܹwXG*ӦMC߾}: qY VVV_?sΨY&Hﯿ?3ǩ(<>޼ybo)Vll,N+WƆu㊡^ `x~\oЪU+|7pWSn]\&MB:Y|9\;wq3<~-z聁ݻwh'NƎ; ǩ8/LMM1tP>Qܽ{:qbFFF8~8=z'TP||< ^zapW=f_ڵq1t"6lcΝ;vǐԄ{pss1`HRlڴ+9"OHǕV{ڵk ~šs044Qzu֑8VֵkW>=JSN%777 $ k׮ez\ <}ǴiKԕ&{< .ļyРAH$¦MG!00W\;0w\ppp[F J*V`dd$bddd )) xdO">>zzzhٲ%ڵk9s}NBVVV~w`000(WDZ! m޽{ŋ\9%^<|@Y[n_͚5C֭ahh(W>|Æ +1P(;N8SSSlT4nvCpY].]KlSfM >>+WĜ9s`ii~srr:u(Oudff={ϏuJ~077ǔ)So\\rssxKs!11u>AcӧOsN,^*,&&xУG"00u>Ac/K}~4n033CӦMqeQ8Hطo[)wJNNFj4TѱcG~fq*N#.\&M`СZZTR)}#OOOܾ}s Ӹbm:t-N/uDϳq\ 4/X-[D+mdff֭[bq*L_'NԩS $ɼҿhqQ?>>>pwwG^*u;iiiZ 2*]LL  bΜ9sT~͛7/wt|ʚ"wsf={.]m8sF@@y\\Ǐ0gggXZZ" J߾cMc ЩS'xzzVYiWBBB/^Dǎ!H`hhXiʣ}+u;z|8*r>,, /^,pfWٲ-H$>^(Tv)tiy84b~ٲe@=##ϥR)|||аaCjժpww/}/QjUԬYӦM+p?rS+i=z4annQF!..N֮UVxy'3f ,,,`hh///6)) SN akk+V@ M6ֻpܽ{ݺu1,,,0qD>t%;^::w,{--- gφ= `ggyk?7y>Ν;T3eʔo%}oRs#@@'OT6gϞMM6=9s&(j*Ye={,nҤIE~^C^%WnjSdvvv""JJJ"t̙ILL${{"۷HIIIXv)%%?NH(Rjjlq2dlٛ7oPJJիo{߸ݻmѢ &""XL~vJsCD?};jԨ"}~^8gqԾ{{{SӦMI**ms%gggs@gϞ,JMMM6ɾy0-[HJKKɓ'ϊ˯!oQ9sʕ+dggGh޼y666S3o<@FYYY@666E;w. {{{ , sQ6m>}ܜtttȈӉĉS`_ڶmK? Y> r#WPbMbX%KQDDI$vڶms>N>vEe.e%gϦZj9 TgϞ +k׮ѬY|AU @IIIeׯ_/1ϥK i>9g֭.'o8QFΝ;Wٳg @_ׯ'?ڲe ^kז/+VižaÆ?^j_%G⓼> Yxe%dbbR@e"tQ""6m"???@&&&]Gʵ5j rJJKK{Ҡ(44)00PF+dm:D(''GL]Oޛq666@&Ͽٳg7h 7}tY۸8(&&>|(;;,\PxWҊ}׹m۶tMH$G3f )k+MժU _$͛ԬY3Yo޼)gmZ~,]̊)mذ|}}_l0~"y_Xx`Lx"wɖ$"GYd{ԠAb~.#"$R`yzZΕOi>))IW\ᇑs].K$S͚5i6mɓewA-Z HD"hٔ(kbo>'PX꠲JKK'RI$Q^%IHH>}Y[[̙3)%%@AAA^xQ`:"d8q"YZZ 6u%$$ФIښB!ծ]LB߿/o/XwŋKݿO-ʧbOS- B277ҝ;w &11N$utZp!޽{6B jBTTlll7n={/2ˠ,y+˷ʭ[/^0p\y˫L륑Fn 6` 3޽4*ʛ&55qS"""˗/{ϟ?8 /u7!H$+BDZVg֭ :u: \]]! :JIIITVqSb`ĉ8efpUQdez߲+幓WeqQ%)[l1YGi׮RWWafjQsrruV3F>ԥKܽ{ >q*H-ѣGuڷo@QTFrrJpSbqF 0喅Ņ|޾}5kq\!*GsαR]СCc'Ou Q3?666ҥ (۷/QT“'OаaC18+D}NNvލ ::VVV8~8(I$DEE3{SAYA"66GfD:::իQFNN7n: qt?бcGԭ[uO6lBBB: SO<@ gTؿ'NP<=z􀵵5v: SwAݺuadd: ql߳gD" :Jtuu_?Dnn.8\rڵcblߵk6gcƌA\\oQJ ŞTJܾ}_5(eVn] >/T*-*MTݻ$^9NEdߵk5jm۲"غulٵk0d 6a 4jԈu㊡rJ8t&M:7niӦŋؼy36n܈HP+**88r 8Qbxxck׮D")0cjj0Y1gQ8+GIm>r={`͈|1HHHHYG8*UR)9ou2;sL! =7>z(\\\`oo: q%P7hCCCk|笣و#SXX,VR"J1x`Q8*FF:\.\#F@(,L%&R0bq*Ne=ȑ#jƬ@ mBOO6YYYJNFƍ: q2ƍxZ 䧯'N^z%d%\سg: qPb!ԯ_͛7gLLLpy; U9z(>|cDzq\)Tj#G`СcTXڵqyC*־">55111ׯlA"[V-ԪU kF͚5_L7oF~Pvrqʡ>::ݻ7( }b{ܹs{.?~xz v"իW W߼yS"''x}_B5j@͚5QN8:: NNNpttAY߿@8R)J+W@$u֬(L~a|we߿@۸wVVVpvvFF);S>3T^Brrr 66VCLL .^C,C(pqqAкuk֭[p*Q샃 H:BM8/_ϟǡCpeDFFBWW...hڴ)'''8;;ʪRs BX[[ںsss;w޽{wv؁sF߾}!H= qHmҤ e˖PܜFbȑ(((cǰi&=4i&M-[a%ϝ;V+,,DXXУGܾ}֭CJJ V^]iolD"|}}~=Į]pt׮];è=ފիWaaaQ͸ L> ,@xx8ڶm[?FµkТE t+ߑFgHi8q9B!oy,--q ̟?ӦMøq H0j={9oooի۷//9 B|8y$N>޽{#--X vx)y&/{۶m;#ð<Sw}Ett4퍇a />!!EEEիWcXx1~g6mf-ZgϞ;è ^իWa`` O> tReǏG.]ЧODFFa/>.....f+Wݻ1ydNdGOO탟|}}w$Qy;wcǎ}v,Z7oƻɘikkcӧ d#1J߽{ڵS8Nĉ_# @1 رcڴi!C ++H 8/RpttT8׮]È#0o<,X@c1ǿ ڵkٳgaT /{ʚOѮ];̚5K)3O(bpi0e^W9XQMm۶Ehh(fϞb0J*xJb IЩS'ͨ? ; èN۷ ɓt.\~P|W(((;b]]]lR~/>Cۻgp\쓓aeescbbhHSbڵ "0 8/ZRh?3|||>5hI&ѣG8y$QWj]R)vڅ`h͛cȐ!غu+QW[[[+C>3zh8pR( Y݋A@a}2g((('0 + ѽ{wVFb/{_tO!i2@ٯ_?:u -111066ҥ ܹDwg>11"͚5SHqqqpuum{Lt~:Q+۷vdFlM4߲aΊaaaǣK.#D"M6UhPC$-B~~~Ə ۶m.33!!!hݺ5tuuaccyUxhl=>>1m4k'zzz@NNNLjj*`ffHܹcuV{A~`bb"s)Y6FDD6mڠc4HJJFG}UXFFFyz~үիW呧g @%%%iiiԢEJnٲE.33:tPi;gggΖ-oڴi&M {{ m &ʢ6mT[mx* $(00^AD4p@8qb_h۷wEqVo6+?}zؘѣG$ [W_}E[ntU***&qFyywD"I&rpp[p! {{{:wZpa@=zwT*E277-sppK.QAAEDDP֭T-[&&ϟ:s Y[[W.АкuH*R~~>>}I z7A}0{ҥ }G +//݇ Zz5=xv...>|Xn GёЅ }N8Qnc999ɗɊ]LL|Yzz: ---; /CTer>\ߺl+"Rr mܸf͚%_@PaꕿqѰaXW{{{ZrBRD߱cɋ>-_rss˵+ۦ/333y[]]]@Վ- Ir˥R) H$_&@BD)HTeW[ߺlOGSC7t{f\9;@ +/X fօ?RRRyfR-56l oWZZZm?e34PvՍ[8>[GGVjfe˶5kbbb>[lABBBB*I& a'žM6UXFFF ~)q9\|| 2220oۦemm&oAڵ]۶mիWozsy~~>Uoر ɩr.*00>9RG&MhӦM LzƌC&&&K{ i̙deeEdmmMs̡/_kWbOiӦD"=z4egg7`Qpp0Ҷmrmk222h̘1d``@bvJT!CTuL \\D8={0zhCKK13Gaaa7o233qx>;N>Ç#++ |aTԟ|f1'shҤ =.ٸ{d*222@II nݺ%d^xNX;/_+LI˃Btvvگk6_>}:̠:Ӱg}کSسFb ===)0`ïG`2&иb+W>&Of*JNNرc1g >8 r8+ʺ2b;wđ#G0w\e+ 2NNN[gs|pImtaiiPNg!Jz߷o;aIl77oᅬVZ! r`رHII ~z/è3N='B$aԨQ|Gb|"""pYX[[aT{ EVVvZL>HLHR38p0*b\_͛c̙x9-[k~^zaÆѣG@Ν0jb4k x֮].W#IIIxQXXϫoFUpvT*jj7"--HL-?~ùsXg:ث^}Y>>>8{,p#1U())ҥK1x`̙3h޼9߱FpR PPPPֹsgĠҥK2ULJJ իWc?dMs5T?ߢ{.߱wRSSq%L3Qz1cW\AN0jb{e9;;ʕ+Xr%{ĉ|x999B|pvvFrr2"##i&6m0 4WF[[!!!u:w %Zd >#`ժUXlbccw4({H0 cmmݻwv:v`<|h8p{<X`ttt0b/r|Сq6oތ &M“'O֎?/// 6 ;w9h&Φq JB!pMGv˗/Om` A`ffK.СCxw  cϟ?;2hvmmmL0w p5t  S+UͣGVZ!88666¡CХKȑ#AD>|ؽ{7FpvQ u`ŋر#fΜ +++ &&C'̙Dܹƒ @ wQQ1zh5 /^zFpR P֭~7$''cѢE8v<==aoo ŋR{쁿?7oѣGo>>AAA8r`kksĉ=KYRRR`ԨQq㐝iii8||>&^{"deeaԨQ3f ^|Ua' v=,X3g4n޽{#..B]tA>}ЧOS.JJJ™3gܿ߿?F ___֫OںOB:::(** ??T?D@@F~"عs'n޼T˗/qYy~:йsg8;;SNhт׬%%%w044D=[owڵ΋Dlllh޽{qFF='600и)jڴ){8{,Ο?x8q^O{yzz-[ZhkkkXZZ6HKKCrr2RSSO"99n͛78::SN>}:ѥKݲzѸ~:*|O6|r,X)aI744DVVC SSSW,##'OŋѮ];ܿΝCrrrܺ033>b1X }}}D"H$#;;%%%Bii)233+y_͛7G˖-ѪU+࣏>BNM}]|'kkk7wY${###>@(wF~rss$# ?T械ՅѤIBy水DVмys=A۶mѶm[ܻwOLKK BÇgabߤI ??b!˗/? ''')S0|p7J˗C b ={0j9{lYZdd$w5сD"Qv$/"""`EGGcpqq)FpvQ6SӧOյ֧/.<$6mRSS]i_G1byL0ꉓb/+^삘9s}U&Mhdu֡iӦ B "k{/nٳ@q [j/4r:ػw/Ξ=%Ka g{lϾgΜzU&&&OXjN>wQ̌ݽgΜAΝabbRTr*`Ĉ?~<2220jbߴiS6Ss_PZZiӦag‚MTyfž1433o]v;èUN 5?ذaqFeqZٞ}Eϟ?ǭ[\쭬 H4quձcG|Xp!RRR0*̙3 묬ϒ%KЬY3aTgžymUG6_oddT׵l}uffflڢ" 3gd7c7pVe8'==nݪ-+ckk$Ro=$ 0J*r5ʋ@ |ܹTYf/?s Ί@ %+eDDDյySvp]RSL-/^wQ{=ٳgtY6VX;w"&&8 8-Bdffڵk:8+Lv7J1ݺuChh(QF%pgϦq^;wgs}%`bb7o*0իO>Dwže˖l"##4#憫W* fZlp)0 8-VVVJrXMt Huaxy%QQQ(..VHwwwG||333CLc@$H 5JٳgD"eggjڴi'MTusPѣG effV޼yyzzݻwI"ФI988@'_nhhHhݺu$J)??N>MDؘѣG$ [N^ Ã(''fϞMe˖u^b244 65sVXu(vY]Ե7oޜ9Bty%hѢr5jׯB꺝-Z$tPDDnݺB .$dooOQQQTPP@Νo Q >| ̌H__rsshZn]zAw%T*gnn^u+ tm@qqqH… !{SUGii)]r6nHf͢;6VVV|||hjǹ||Yzz: --ڮ\LL :M Z[ o߾}[I/СCn߾='Nk{1@NNNDDf@/חlk׮]D'%x111V(??ASC*WODScj;HOOO\__/_.cys7ؘJJJښ;vA"uUb_vʁH*-|ߴuVD> .I6sȑrϜ9So'''@Ǐ/VvQ짟~"h }喗ic^V&L~rف=zГ'O(77f̘![cddDoϟA/eظq#dum;wk]7oNᆪqyzzV.^H)?@VٳgE|nP֭)"" ҥK_e %fG򶩩(99;& =~Fb`+W$;; SSSR(m6yo^ف:.RRRH бc-b͚5/L2B޲;9v٩ٗgoooV픟Onnn2;xԮ]Jױm۶NS%"۷/pM6TĊjeGB܁Gј1cĄtuuÃv]MUo 3f X,]_E˖-#4dy۰0rww'XLb:uD励"߼ ,;5ѥK .LZ"c'mmmzдiӨiӦ$iRet:t(萕͟?믿&KKrq;bڴidaaA"F]i̙deeEdmmMs̡/_VWpɒ%>5_uU&{8v 8;;#!!:tzx^m۶LEYv-,YT+u,U 괮oWqqqpuuUF4F Ip~S|[^uQ\\hxyy)},HRn\]]ѴiS>}( ^X,MyPv\\Ri(Cf0hР )ₖz-{\m'FsRkxN?fffMYx9'㩫}"""=ix+fŋ֭g{c044ƍ96Oѯ_?dgg#::^Wem';99| ϩ/r2#iӦL}hѢgފ}vŋJ-IcNΖ5k,|㪛={r/s4~ ڵ+h>~r::ŋ0J[oݺ5WNDEESN066|s֭[8pc=zŋd@gz:`ĈXl; 8D"LMM!`ii Psr=*l4i҄8v=888^tuu_a}}}>|zB~ cIII bbbpE\xQQQx Ѻuky۷/ЪU+b@$@~6D"Ann. |$%%ѣGx! :T?2E>[vW\;F$&&jL:u AVVߑ_|9B"`͚5HMMEDDΝiJ۶m1o>|1Ç4|277']]]8c5z۷o'+ R{˗R//_DnnF888 <<2d$I'''M6ؾ}Rƿ}6F OOOܿaaaHJJʕ+agg1| i&ܻwnnn3f {(<*Ub1\\\DЈi׮N:`СdeeOU={6R| [na߾}|25@H$BPP\ݻw#!!:u| ?1"Tݻ'O 4r^ }6L^zS\\+W}8{,lقk׮@#Gؼy3Μ9'''|(..; GzӖ-[АRg?YZZ׮]6mI(Իhܹ3ъ+PCAA-_b1Sll,ߑ6g!///@5L길 88Gpb|u ,@abb8|QTl "..ڵ+BCCљzSb߮];#::(u)T۱dQa۶mu]bb"z_֭éSЮ];EFVK8s ~'_oŨ!+ݺuùsRgO>>dƑ#GCaa!ߑx=N{iii jwwE055Ŝ9sp]DFFރ!> %%%=z4Ο?SNۛjwowTE}llB/WOyzۑUVׯ_\ppww9ĩSYfQQ*[콽Q\\6y)Zlw affy޽{8ulmm{nٳ۷;qqq}+WQA*[7oOMME-rRSSݻw;ׯ6oތEa޽|aTJ=Mt}jj*ڴiw s]L<˗/ԩS &Օ]ȩ=_pA-:i#448Fhh(|||ȩ|FLL QjĊ}E9>}_~(ƍ/(Pb舖-[̙3|GV^^؜}gϞ7|fff|itoaŊj3(J{x뭷Tا+S\\)S $$;Nտ̞=SNe9zsΩ555@z: rss/00/_K";;8 T;88Zr^zSSF,X; ?hժk0tӦM9s&Q*̞=E^^QEG֭Uv*{?H$?Q*=E^ݫC322Ck׮ŤIw bSN@mq9؋}ll,nܸ &A@@^ׯ|G@vecu!899~'''k/QM3RAc/СCq!c0Sb9q1)**BNNN-O>Ett4|}}СCq<( Ԫ0ׯ_dzg"=zٳ'QZ!9wCjU]]]e8::pBdqqqWZZJW\7ҬYcǎ Zz5=xq._,_NHKKYp!خ듧bmr:ԠbB>|B>,Snym~ս?Օ.\ؠ>4 +*hɒ%ޫT]v͡K(??v_7ڞ>}J5Nرi唛[8om!7oyyyخuOD!H$ *~/77Be}Ϸ+///7o^4\СC#&)uG,Y|Gزe *)))ؼy3z TE6l^$)7ϛۋwz2:M~+VGW̛۟[WYYY022jPHSWbߥK~>Y]\UVZ@%~1@TT;L0\)^SSSܹs|2>ddd`޼y2ƵZomYnŋoTTTolmm?_PX޶.?_EʂBTw044;Rm҂/7*3d@HH!J1sLylѣGll,ߗFצM_[(IR޳ݻwMcǎL2(**˗1eʔ+{ԩSq%ŋ:uj.`ܸqEqq10|rM(u>mVөÑ#ؾ};D"H$e8{,ۦe۶m*O vȑ#DDaÆ*8NUߴ{nPj֬|:2Va;As999TB&33ʶ+o[۟oCڻwo4?#hтJ>==-?yV=zDcƌ!%ڽ{w6U2gddИ1cb1uڕ/C #www$SNJ5Sb"[Z{Y;v {{{&KK:m'z}Ni‚D"=\0(88IGG\]]i۶mbq^!mmm233#???~zv*'%%:{l4_|9::C)D}=zcǎظq#/ ? /"=~8w=yÇSGbȐ! >c={.];©휽/3>WVuRM34k֌JžcǎhӦ 8]jQjhB~ k֬! acc)S 66|G+KJJB֭j_`ذaؿ?zKM퍄dffި߫^O݅ ::(..ǏaX[[65{ѻwo$0F#;#x<~e{RUA!TL͎= ===Ӈ(*E*"=={U ccc^W^q>*0`W23w 0޷TviCojj/_2*:t(=05(**‘#Gy\׭ۭ)TR$ZIPF4f&a0c؆NYI%(%$Q {={W~:|"t ;DDDdaQVV;888ݻwb: (--ѣ"teeeH{,8еkW899LJ(W߿..."{zaK<~p(bCܹs> @nn.Qzdgg# c:P0p@c{ /_Fyyy۱cG/Æ >ݻb{E~Y8HHH!0"U읝Q\\VpB۷Ol9fؽ{7ݫG^^^xA uuuT2 ZuLaYYYٳ(gvRԧ޽ ###H{Tŋ[4@g{{{cڵmZQ_-JHH@=tbVNɓ'CGGׯg: ?ׯ&Mt${^zϟo1;w .9†ba˖-AFFq^zz:|||e˖&^$$$ؘ%rwШ򃚚ŋ2NePUUfΜ'ܜ8Bӧx1 tbV/m߾YYYظq#Q3?LGj!!!PQQ QJ$УGx:t4?*8x 6l xv:0GƆw9Q%&LӧOxt3GƼy0c z<ȑ##jjj*Pb?edffΝ;2]*ސ تTTT f:KHH7oCQd1tttpVk׮t222pj!HOOǥK9p5K@Hd=a߿U :-޽;q9X8"kŊ8wAHHXbP={Nq`nn???_ضmqDΖ-[_Li^|(L*jv\:M'''ڵ /dѺutR޽NNNLi3N:555XYY1Ut*F㨩Çzj,[~8!?3֯_#G`Μ9LGjSN8SBBBDĥ$<<\㤥ݻGTCΝK*++TVVsiiiry9b*izzz0`rM?8vF<#/_?K.aܸqLGjsN8>}J>'0sTUU l 999ٳgCԌ9naddX# h 77c:Rt L2J,ɓk tݻɓ'Cw܁ sN:_BmۆÇ ׯӱڤxdddՕ(J,}=`jjSN t---ZAQQΝڵk;;;dff2KhdddK.ƍqYɆܹ#GD=ҪĢrzɾi&_~(((oN *lڴ x-bcctR.} ɓb:Jb?yd{!!!CSSYYY_Ν;Xb~WEq&::fffXv-VZx_5޽:::bslmbS544`aa!еr2!j*$%%AQQC رct4KLL#,--$Xl6hm^EE|||0oHFOOׯ_իWKaʔ)HKKc:ߥb011A^^BBB]]] LGaX' AAA_SSAZ>9r$pY|yت*DDD… r 233fa``cРAӃ6TUU#??YYYHKKí[ :::5jammMϪa1x`lٲ(byL0O>hkkC[[ddd)))Aqq1\.QRRdee!++ >djjPP ”)Sctԉ8b_QQ555,Y _pQZZvo-XΝãGtjdgg ǏݻwrWedd999hhh@[[=z}`t &"ߙ8,bߔwS[ӧO۷c:%Ξ=9s 33***LaXzY̙386KuA]]gf: %\..]_bojj޽{ȑ#|WJJ ={ɫ|8Q 8wżyPRR">woǏǓ'OH#%%VW"www\|_[x=]q_zN߿ǂ 믿B_/...p8|]wwodtJHX,ę{999|gϞ͛7qUlذ(q_BZZ8BI ,, vvvx!z͗>'Mj?qfmm III3B%%%߿?&O ooo-߳Ç[n8z(444{|pUܼySZt)vZ5ZHHHǎ۝"YTYcƌQ(!t+h3g";;ׯ_K нƭ[zjPBט={6VX###=:g_ˠA˷%a̟?/ oߞQm!Gtt4]:}-3gD@@ҟ)ܹ×իWK:y{{#&&Nhe騩'ҟ)bccҗYnF sssPBƍp!hkk3͠žcҤIؿ?_4h['.]oLG̫W0m4pvvf:NB?+++$$$O^^:w1O E%tJTWWcȑ(//M=XZZB__/{:uik׮!::S_XbG }3b_9sSĠAχTaݺuB cǎa8z(ut6:- oC*(:WO}͛={6o{{{YtξSNENNZOLL  )h,BBBB G'Nq4g_9s ::žѣGQ^^ޢ D}_HWO%%%#ZIonn{OD˚5k`kk KKKPBয়~™3gHs- PSSØ1cZ%y&֬YtbX`Ν8<]*h OOO\~=jvPQQ`a͚5{vڅ_GW -_1zhbpMܸqS… cL28"ĬY닪fcii7n1Y۷n:X[[cСLGt|7X~={,ZyyyxbFNN233튎FXXW곳UUUp8{Kq:tsgΜ ڷoݻDII MMM ~7AZZ >fddDX,quu%jժ:@x/]v7obQF}~ $Ο?O8Yd Q-tybHFFFȈ[nի [x1@LLLHzz:)..&Xp]]]yLJ.K_N)))$88bC--/166&< %%%d…QSS+$Ξ=K8矙"6ho*I/^>֯_O455mrpp j;===6cNJJ⹚OG,X@KkN#G͛7lj=VPP@ ~?DJJX(b&ظq#QVV&eee͛@DEږ6cWO!/^ N|Gdeeyk׎lܸ9y>ɓ'$YjQ mW^C8Ь\.%sɉ4t&LYd 9tyA߼yC6l؀ÇcLG_LڴE h7PB#,4z\ҥK:ۏ=kz]]]띯@\B!dqrr8=.\.8q"#/_f:أ?Ͱw^"##C'牋)S&mELBڷoO8166&)S999"##CȩSȚ5k2f^###"##CdddH~IaaWǡK޽#C֭[Lǡ!PZZ ,_˖-kÇ:$tÇ000ɓ'1i$P3;o߾իWt$ tf;vލ&o?l0DDD?ڸq#z &08CRRѴ ZӧOq&okmm'OӧH&\q)\MÇ|=bbb>E(شiv튙3g2 qqqD5-|0p@ 6 ۷oovvvvў?bt1~sθs,,,D5-|&ٹsgT7TUUt0e̝;-BXXtt,h瓱cǢ[nŁl2Wݺu DTTBCCazŞO$$$?СCxm9r$Db鄿 ʘ3gQfM0tPIII>|8ӱ(>Ş>7z!C]vm~*իWؽ{7.] Pscشi.]UUUcQ|B=ofŽ;PUUըm1lذ6t͛s2j'N999¢ExwD-|pBfȑ Cuu5 EeeM~~>vڅKBVV8T~'O+\]]cQ@o^"(**jԪUUU LtܿФ /ʢ!AAAơC`kkt$JI~:@x9sxzzccc"--MIIIp>DAALGݻwo%,o2jtG `۶m/O<}ۃFEE [={CMU[n?L(I\~! gΜ#Gt,50i#K8 #DJJ^YYI􈤤'{IKK_~olF"< DIIlذxWWWWL:zHT+WI!;w!he`صklll :::RSSy b^󃗗8.]#Q 8|ro>aܸq> >|8\\\ %%Ug_BU쓓yw DMM rs&O ^NN1c L0<^b'ɐŷ~[㩔+((a[ٓA_|0UVVbٳ'9"wj+m6F1{>100.k&V\Y#PWWfw'k˗/ɓ'r2X`ٲeBRRt,JbG[nżyzW^tRtmw.͕͆.VLE}k|*n:zC=ܹӦMdϳl4`׮]LX,[Y/_yIII 6 ܹs+&!>.]Ǐڵke:%dh3Çc̘1uNHJJ"??`ooϛ_f̘ ˷b**>>ӑS2RKę3g`ooO!|Q`ǎ۷/~~pHMMEZZӑR999(++CNNrrr֭tuu===6Bd:w[b֭[GjEXj|}}1tPaLǢ-paÆ!))W𫫫yhkի,YoooTUUK.x"qu$''BOO";;ؼy3\.X,aÆCm?|c , , /;>rT}***elܸ***8vNJ?ha.[ҧOOǏym$%% &C !+W$$++TWW7;Kuu5"/_&+W$fIII2h ?yeСC?%--M.\T"++K6l@ʘD1ط<+ӧ#" "V\\Lȷ~K;v,9}4)//'L6MHllsQőaÆEMF>}t$ždgg]DIIhhh|R`\.?>#\'<ݺu#;o^CC1M<~L6X,baaA?`žedd gNHv툗0M.\[YAA))XEo8χ/t̞=:u*tVS^^GFFF\\\paߟhzbVÇÇ3qNNN رcѩS'YH;ڵ ۷oǛ7o .xJ0E[C$%% up$>>>LGϟ(**EEEl2 cQThog6MtŋLGᛔ2{lpٴi)**b:E}-͐At~zD?~t!DBB.t,j4!0=Ԗ8|ɐa:7 Ν;̛7fj&пG?4~gYK.ƦMׯ_?&L ˋzJ=F@qezT GϞ=[}$ܹ'NY0o<իճPTkžcǎ1E$L6 %%% l\.Ξ=={ 22}?777˷Jb-_kkkC[[8"!33INN޽{q cر |("޽ŋwpI߿ՅtܙcQT[B}`bbC__8"%%%HHHa޽{qTWWc„ 􄕕@}&Mg8h8q"$%%qԩfmcǎa߾}#zxx͍MCQž/^&`mmt;;;dggk׮ڦW^ő#GpE`ڴi3g.z}=?nݺaСLGYCN8նoƂ '''z wFnn.vE =E}-8v\]]|IHH`ƌ >C__ Bhh(.\'Oƍo ++ʩ)m8uHJJN/T...PWW={{!++ ***6mabbt\jhòex BP^^7odddrA$ +++ 'N!44LӦM" eee(++ý{p)̙3;wƁ`nnUUU`Ϟ=G?SXX;Br Ǝo|8bbbg۷ 6Z?]]]c]]]ܻw`aaG_jh%??o޼a|ѳ9XO.k~D(((ۙ5ZZZy&1zh~^KD}-,FFFϔyJJ TPP  ++EגEEE3… ͤnݺANNS.JJJӧqu!>1}-AYYbC{zzzpwwǺu7ERRREEE]SS~ zzz44~ؠ}p8޽;~G4)GII /_=z@ZZZZZXr%\m_| tؑwcSk %%% HOOs>=77nnnPQQ4qz$''cĈCǎߣv_@jBxyy tR7o&:#UVջoIّj^BңG/9::~/ <,ZBHMM ر#"QQٳgq]ihERtϾ {Q^^bvZs۶m \.7 7oƝ;w`nnw<%BCCqA^ۿ?n߾rܸq_ ^)((lϞbA___y)E-(** , vBqq1 ahh/ Ebb"f̘{QƏ>}:QUU/_bҥ`Xᵝ6m`ܹy&*++s6obz9`ذa_<7ydNϼ}6*++q-|w|)EsGjΝK.{:99uԉx !68{B߾}K]v$11ז咁~nڴiWUU%v{UUUАXRRRR״?)E-|>&&&L+qqq077Ǔ'Oн{wӧ̙37n\})Ǐ-E1ע N5MZZdeeѭ[}?vǏ+֯_`WPPO),,Duu5RRR0k,D-HJJBGG{W^G2-- gφ:\]]mW^^nnn駟|vss̛7***`ӧ_UUU^yE 6Mt :/#[=`z 0aWޱctuuq15jVZ f0}@޽EZQUUQVV&`beeE))/X}kxx8'ĉӧO[۠(Fq>ckk7oaJ ݻwQXX[[[TUUèQ ӱw^?455[۠(Fq>MMMaL ޽;\ooo.Bj*RHHH;wYkE6zYo b:+,,^x 4}$i: <<.D%`)))=z4?^kB߾}iFžFtֻwo̚5 8w/^ cccHJJfP}+JJHH 775RTFq`ܻwU)1o{DXXQQQJBbC1ZsssdffB[[8"m\y&"""26%Jho 60E$\AAAHJJjvոw2331i$(B}N<O<۷ƞ={xS%8m&ON:LJ(";wĉBQbHHH`غu+ʘ#2JJJuVX(PPPtcoӧOg: E :g.\+RSSt6-;;}􁟟CQbFrttN:t6mҤI BQbFB߾}qy98mիW@KK8%V}#ikkcŊׯ`Μ9Xr%-g@׮]qzm>E#t$;tϾ 88mƟ>}zbݳogbڴiqڍ70b>}Lǡ(Ea„ 7oqaS}3UWWӑJNN ###֨(ط@YYP\\p2I(cСԩ\v1--TTT+++HJJʕ+ҥ ӑQFHZ(E :gBJJJz*jjj0di߾=BBB`nnaÆLGj5Ǐ ,--3bG'0w\p\c LYY̙777̛7a:EQuhŋN:޽{3RRR0ydppp`:EQ {Dk֬b߿իaddyyy$&&BOQm-H_7oFqec5[PP-[Gdd$455EQT#b/`l6K,Ajj*7MGGG8::iiiXhl6(j$Z[:N>|&&&5j"##VH5 (((kpI1&ž1oFpp0\. kkkx())/0tPp\\v n݂(j&z6"##sNBBBpssmMTUU!44ǎC@@ϟ++V@Q`b/$G"22򰶶accCB?޽{qM 3gĉR%bhB999GXXÑAWWEn //yyyo>Lż}%%%())Avv6ӑ4ݺuakk ;;;:OQ"6 ==ѼbǏQKIIGݻ7zpr-mTUU^|wW^^ʐtBO(1G=EQ^REZ)-EQbERGyIENDB`state-machine-1.2.0/examples/vehicle.rb0000644000175000017500000000135212305405267017401 0ustar boutilboutilrequire 'state_machine' class Vehicle state_machine :initial => :parked do event :park do transition [:idling, :first_gear] => :parked end event :ignite do transition :stalled => same, :parked => :idling end event :idle do transition :first_gear => :idling end event :shift_up do transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear end event :shift_down do transition :third_gear => :second_gear, :second_gear => :first_gear end event :crash do transition [:first_gear, :second_gear, :third_gear] => :stalled end event :repair do transition :stalled => :parked end end end state-machine-1.2.0/examples/merb-rest/0000755000175000017500000000000012305405267017334 5ustar boutilboutilstate-machine-1.2.0/examples/merb-rest/model.rb0000644000175000017500000000105712305405267020764 0ustar boutilboutilclass User include DataMapper::Resource property :id, Serial property :name, String validates_present :name, :state, :access_state state_machine :initial => :unregistered do event :register do transition :unregistered => :registered end event :unregister do transition :registered => :unregistered end end state_machine :access_state, :initial => :enabled do event :enable do transition all => :enabled end event :disable do transition all => :disabled end end end state-machine-1.2.0/examples/merb-rest/controller.rb0000644000175000017500000000166612305405267022055 0ustar boutilboutilclass Users < Application # GET /users def index @users = User.all display @users end # GET /users/1 def show(id) @user = User.get(id) raise NotFound unless @user display @user end # GET /users/new def new only_provides :html @user = User.new display @user end # GET /users/1/edit def edit(id) only_provides :html @user = User.get(id) raise NotFound unless @user display @user end # POST /users def create(user) @user = User.new(user) if @user.save redirect resource(@user), :message => {:notice => "User was successfully created"} else message[:error] = "User failed to be created" render :new end end # PUT /users/1 def update(id, user) @user = User.get(id) raise NotFound unless @user if @user.update_attributes(user) redirect resource(@user) else display @user, :edit end end end state-machine-1.2.0/examples/merb-rest/view_new.html.erb0000644000175000017500000000036612305405267022621 0ustar boutilboutil

          New User

          <%= form_for @user, :action => resource(:users) do %> <%= error_messages %>

          <%= text_field :name, :label => 'Name' %>

          <%= submit 'Create' %>

          <% end =%> <%= link_to 'Back', resource(:users) %> state-machine-1.2.0/examples/merb-rest/view_edit.html.erb0000644000175000017500000000144012305405267022747 0ustar boutilboutil

          Editing User

          <%= form_for @user, :action => resource(@user) do %> <%= error_messages %>

          <%= text_field :name, :label => 'Name' %>

          <%= label :state %>
          <%= select :state_event, :selected => @user.state_event.to_s, :collection => @user.state_transitions, :value_method => :event, :text_method => :human_to_name, :prompt => @user.human_state_name %>

          <%= label :access_state %>
          <%= select :access_state_event, :selected => @user.access_state_event.to_s, :collection => @user.access_state_transitions, :value_method => :event, :text_method => :human_event, :prompt => "don't change" %>

          <%= submit 'Update' %>

          <% end =%> <%= link_to 'Show', resource(@user) %> | <%= link_to 'Back', resource(:users) %> state-machine-1.2.0/examples/merb-rest/view_show.html.erb0000644000175000017500000000041212305405267023000 0ustar boutilboutil

          Name: <%=h @user.name %>

          State: <%=h @user.human_state_name %>

          Access State: <%=h @user.human_access_state_name %>

          <%= link_to 'Edit', resource(@user, :edit) %> | <%= link_to 'Back', resource(:users) %> state-machine-1.2.0/examples/merb-rest/view_index.html.erb0000644000175000017500000000072312305405267023134 0ustar boutilboutil

          Listing Users

          <% @users.each do |user| %> <% end %>
          Name State Access State
          <%=h user.name %> <%=h user.human_state_name %> <%=h user.human_access_state_name %> <%= link_to 'Show', resource(user) %> <%= link_to 'Edit', resource(user, :edit) %>

          <%= link_to 'New User', resource(:users, :new) %> state-machine-1.2.0/Gemfile0000644000175000017500000000005212305405267015106 0ustar boutilboutilsource "http://www.rubygems.org" gemspec state-machine-1.2.0/metadata.yml0000644000175000017500000003475312305405267016135 0ustar boutilboutil--- !ruby/object:Gem::Specification name: state_machine version: !ruby/object:Gem::Version hash: 31 prerelease: segments: - 1 - 2 - 0 version: 1.2.0 platform: ruby authors: - Aaron Pfeifer autorequire: bindir: bin cert_chain: [] date: 2013-03-30 00:00:00 Z dependencies: - !ruby/object:Gem::Dependency name: rake prerelease: false requirement: &id001 !ruby/object:Gem::Requirement none: false requirements: - - ">=" - !ruby/object:Gem::Version hash: 3 segments: - 0 version: "0" type: :development version_requirements: *id001 - !ruby/object:Gem::Dependency name: simplecov prerelease: false requirement: &id002 !ruby/object:Gem::Requirement none: false requirements: - - ">=" - !ruby/object:Gem::Version hash: 3 segments: - 0 version: "0" type: :development version_requirements: *id002 - !ruby/object:Gem::Dependency name: appraisal prerelease: false requirement: &id003 !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version hash: 15 segments: - 0 - 4 - 0 version: 0.4.0 type: :development version_requirements: *id003 description: Adds support for creating state machines for attributes on any Ruby class email: aaron@pluginaweek.org executables: [] extensions: [] extra_rdoc_files: - README.md - CHANGELOG.md - LICENSE files: - .travis.yml - .gitignore - .yardopts - state_machine.gemspec - Gemfile - init.rb - lib/state_machine.rb - lib/state_machine/integrations.rb - lib/state_machine/state_context.rb - lib/state_machine/path.rb - lib/state_machine/error.rb - lib/state_machine/core.rb - lib/state_machine/yard.rb - lib/state_machine/integrations/base.rb - lib/state_machine/integrations/data_mapper/observer.rb - lib/state_machine/integrations/data_mapper/versions.rb - lib/state_machine/integrations/mongo_mapper.rb - lib/state_machine/integrations/mongoid.rb - lib/state_machine/integrations/data_mapper.rb - lib/state_machine/integrations/active_model.rb - lib/state_machine/integrations/active_record.rb - lib/state_machine/integrations/active_record/locale.rb - lib/state_machine/integrations/active_record/versions.rb - lib/state_machine/integrations/mongo_mapper/locale.rb - lib/state_machine/integrations/mongo_mapper/versions.rb - lib/state_machine/integrations/sequel/versions.rb - lib/state_machine/integrations/sequel.rb - lib/state_machine/integrations/mongoid/locale.rb - lib/state_machine/integrations/mongoid/versions.rb - lib/state_machine/integrations/active_model/observer_update.rb - lib/state_machine/integrations/active_model/locale.rb - lib/state_machine/integrations/active_model/observer.rb - lib/state_machine/integrations/active_model/versions.rb - lib/state_machine/state_collection.rb - lib/state_machine/helper_module.rb - lib/state_machine/node_collection.rb - lib/state_machine/extensions.rb - lib/state_machine/initializers.rb - lib/state_machine/macro_methods.rb - lib/state_machine/machine.rb - lib/state_machine/matcher.rb - lib/state_machine/assertions.rb - lib/state_machine/event.rb - lib/state_machine/branch.rb - lib/state_machine/core_ext.rb - lib/state_machine/callback.rb - lib/state_machine/transition_collection.rb - lib/state_machine/graph.rb - lib/state_machine/yard/templates/default/class/html/state_machines.erb - lib/state_machine/yard/templates/default/class/html/setup.rb - lib/state_machine/yard/handlers/base.rb - lib/state_machine/yard/handlers/machine.rb - lib/state_machine/yard/handlers/event.rb - lib/state_machine/yard/handlers/state.rb - lib/state_machine/yard/handlers/transition.rb - lib/state_machine/yard/handlers.rb - lib/state_machine/yard/templates.rb - lib/state_machine/path_collection.rb - lib/state_machine/state.rb - lib/state_machine/transition.rb - lib/state_machine/core_ext/class/state_machine.rb - lib/state_machine/initializers/merb.rb - lib/state_machine/initializers/rails.rb - lib/state_machine/event_collection.rb - lib/state_machine/eval_helpers.rb - lib/state_machine/machine_collection.rb - lib/state_machine/matcher_helpers.rb - lib/state_machine/version.rb - lib/yard-state_machine.rb - lib/tasks/state_machine.rake - lib/tasks/state_machine.rb - README.md - examples/Gemfile - examples/rails-rest/view_show.html.erb - examples/rails-rest/controller.rb - examples/rails-rest/view_new.html.erb - examples/rails-rest/migration.rb - examples/rails-rest/model.rb - examples/rails-rest/view_edit.html.erb - examples/rails-rest/view__form.html.erb - examples/rails-rest/view_index.html.erb - examples/TrafficLight_state.png - examples/vehicle.rb - examples/merb-rest/view_show.html.erb - examples/merb-rest/controller.rb - examples/merb-rest/view_new.html.erb - examples/merb-rest/model.rb - examples/merb-rest/view_edit.html.erb - examples/merb-rest/view_index.html.erb - examples/Gemfile.lock - examples/AutoShop_state.png - examples/auto_shop.rb - examples/Car_state.png - examples/doc/AutoShop.html - examples/doc/method_list.html - examples/doc/TrafficLight_state.png - examples/doc/class_list.html - examples/doc/css/style.css - examples/doc/css/common.css - examples/doc/css/full_list.css - examples/doc/index.html - examples/doc/Vehicle.html - examples/doc/top-level-namespace.html - examples/doc/AutoShop_state.png - examples/doc/js/full_list.js - examples/doc/js/app.js - examples/doc/js/jquery.js - examples/doc/Car_state.png - examples/doc/TrafficLight.html - examples/doc/_index.html - examples/doc/file_list.html - examples/doc/Car.html - examples/doc/Vehicle_state.png - examples/doc/frames.html - examples/Vehicle_state.png - examples/traffic_light.rb - examples/car.rb - LICENSE - CHANGELOG.md - gemfiles/mongoid-3.1.0.gemfile.lock - gemfiles/mongo_mapper-0.7.0.gemfile - gemfiles/sequel-3.4.0.gemfile - gemfiles/mongoid-2.4.10.gemfile - gemfiles/mongo_mapper-0.5.8.gemfile.lock - gemfiles/mongoid-2.3.3.gemfile.lock - gemfiles/active_record-2.1.0.gemfile.lock - gemfiles/mongoid-3.0.0.gemfile.lock - gemfiles/sequel-2.11.0.gemfile.lock - gemfiles/graphviz-1.0.0.gemfile - gemfiles/data_mapper-0.9.11.gemfile.lock - gemfiles/mongo_mapper-0.12.0.gemfile - gemfiles/graphviz-1.0.8.gemfile.lock - gemfiles/mongoid-2.0.0.gemfile.lock - gemfiles/graphviz-1.0.0.gemfile.lock - gemfiles/active_model-3.1.1.gemfile - gemfiles/active_record-2.0.0.gemfile.lock - gemfiles/mongo_mapper-0.9.0.gemfile.lock - gemfiles/data_mapper-0.9.4.gemfile - gemfiles/graphviz-0.9.21.gemfile.lock - gemfiles/active_record-3.0.0.gemfile.lock - gemfiles/mongo_mapper-0.6.10.gemfile.lock - gemfiles/sequel-3.10.0.gemfile - gemfiles/active_record-3.2.12.gemfile - gemfiles/active_model-4.0.0.gemfile - gemfiles/data_mapper-1.1.0.gemfile - gemfiles/sequel-2.11.0.gemfile - gemfiles/mongo_mapper-0.10.0.gemfile.lock - gemfiles/default.gemfile - gemfiles/active_model-3.2.13.rc1.gemfile - gemfiles/sequel-3.44.0.gemfile.lock - gemfiles/data_mapper-0.9.11.gemfile - gemfiles/graphviz-1.0.3.gemfile.lock - gemfiles/active_record-4.0.0.gemfile - gemfiles/sequel-3.24.0.gemfile - gemfiles/active_record-3.2.13.rc1.gemfile.lock - gemfiles/active_record-4.0.0.gemfile.lock - gemfiles/active_record-3.2.12.gemfile.lock - gemfiles/mongo_mapper-0.11.2.gemfile.lock - gemfiles/mongoid-2.2.4.gemfile.lock - gemfiles/mongo_mapper-0.6.0.gemfile - gemfiles/mongo_mapper-0.8.0.gemfile.lock - gemfiles/sequel-3.35.0.gemfile - gemfiles/sequel-2.8.0.gemfile - gemfiles/active_model-3.0.0.gemfile.lock - gemfiles/active_record-2.0.5.gemfile - gemfiles/sequel-3.29.0.gemfile - gemfiles/active_record-2.1.2.gemfile - gemfiles/data_mapper-0.9.4.gemfile.lock - gemfiles/active_record-2.2.3.gemfile.lock - gemfiles/data_mapper-1.0.0.gemfile - gemfiles/mongo_mapper-0.8.4.gemfile.lock - gemfiles/sequel-3.0.0.gemfile.lock - gemfiles/mongo_mapper-0.8.6.gemfile - gemfiles/data_mapper-1.0.2.gemfile.lock - gemfiles/active_record-3.0.5.gemfile.lock - gemfiles/mongoid-2.4.0.gemfile.lock - gemfiles/mongoid-2.4.10.gemfile.lock - gemfiles/active_model-4.0.0.gemfile.lock - gemfiles/mongo_mapper-0.8.3.gemfile - gemfiles/sequel-3.14.0.gemfile.lock - gemfiles/mongoid-2.5.2.gemfile - gemfiles/data_mapper-0.10.2.gemfile.lock - gemfiles/data_mapper-0.10.2.gemfile - gemfiles/mongoid-2.3.3.gemfile - gemfiles/mongo_mapper-0.5.5.gemfile - gemfiles/mongo_mapper-0.11.1.gemfile.lock - gemfiles/active_record-2.3.5.gemfile - gemfiles/sequel-3.13.0.gemfile.lock - gemfiles/active_record-2.3.5.gemfile.lock - gemfiles/active_record-2.3.12.gemfile - gemfiles/active_model-3.0.5.gemfile - gemfiles/graphviz-0.9.21.gemfile - gemfiles/data_mapper-1.2.0.gemfile.lock - gemfiles/active_record-2.2.3.gemfile - gemfiles/sequel-3.34.0.gemfile - gemfiles/sequel-3.23.0.gemfile.lock - gemfiles/graphviz-1.0.8.gemfile - gemfiles/active_model-3.2.12.gemfile - gemfiles/sequel-3.13.0.gemfile - gemfiles/mongoid-2.4.0.gemfile - gemfiles/mongoid-3.1.0.gemfile - gemfiles/data_mapper-1.0.2.gemfile - gemfiles/active_record-3.1.1.gemfile - gemfiles/mongo_mapper-0.11.2.gemfile - gemfiles/active_record-3.0.5.gemfile - gemfiles/mongoid-3.0.22.gemfile - gemfiles/sequel-3.10.0.gemfile.lock - gemfiles/data_mapper-1.1.0.gemfile.lock - gemfiles/mongoid-3.0.0.gemfile - gemfiles/data_mapper-1.0.1.gemfile.lock - gemfiles/mongo_mapper-0.7.5.gemfile - gemfiles/sequel-3.0.0.gemfile - gemfiles/sequel-3.34.0.gemfile.lock - gemfiles/default.gemfile.lock - gemfiles/mongo_mapper-0.11.1.gemfile - gemfiles/sequel-3.35.0.gemfile.lock - gemfiles/mongo_mapper-0.5.5.gemfile.lock - gemfiles/graphviz-1.0.3.gemfile - gemfiles/mongo_mapper-0.8.6.gemfile.lock - gemfiles/mongo_mapper-0.8.0.gemfile - gemfiles/active_record-2.1.0.gemfile - gemfiles/graphviz-0.9.17.gemfile.lock - gemfiles/mongo_mapper-0.7.0.gemfile.lock - gemfiles/mongo_mapper-0.10.0.gemfile - gemfiles/sequel-3.24.0.gemfile.lock - gemfiles/active_model-3.0.0.gemfile - gemfiles/sequel-3.14.0.gemfile - gemfiles/mongo_mapper-0.7.5.gemfile.lock - gemfiles/active_record-3.2.13.rc1.gemfile - gemfiles/mongoid-2.2.4.gemfile - gemfiles/active_model-3.2.13.rc1.gemfile.lock - gemfiles/mongoid-2.6.0.gemfile.lock - gemfiles/mongo_mapper-0.6.0.gemfile.lock - gemfiles/sequel-3.29.0.gemfile.lock - gemfiles/sequel-2.8.0.gemfile.lock - gemfiles/sequel-3.4.0.gemfile.lock - gemfiles/sequel-3.44.0.gemfile - gemfiles/data_mapper-0.9.7.gemfile.lock - gemfiles/mongoid-2.0.0.gemfile - gemfiles/sequel-2.12.0.gemfile.lock - gemfiles/mongoid-2.6.0.gemfile - gemfiles/mongo_mapper-0.9.0.gemfile - gemfiles/data_mapper-1.0.1.gemfile - gemfiles/active_record-2.3.12.gemfile.lock - gemfiles/graphviz-0.9.17.gemfile - gemfiles/mongoid-3.0.22.gemfile.lock - gemfiles/active_model-3.2.1.gemfile - gemfiles/data_mapper-1.2.0.gemfile - gemfiles/active_record-2.1.2.gemfile.lock - gemfiles/mongo_mapper-0.12.0.gemfile.lock - gemfiles/mongoid-2.1.4.gemfile.lock - gemfiles/mongoid-2.5.2.gemfile.lock - gemfiles/active_record-2.0.5.gemfile.lock - gemfiles/sequel-2.12.0.gemfile - gemfiles/mongoid-2.1.4.gemfile - gemfiles/mongo_mapper-0.8.3.gemfile.lock - gemfiles/data_mapper-1.0.0.gemfile.lock - gemfiles/mongo_mapper-0.5.8.gemfile - gemfiles/active_model-3.0.5.gemfile.lock - gemfiles/active_model-3.1.1.gemfile.lock - gemfiles/mongo_mapper-0.6.10.gemfile - gemfiles/mongo_mapper-0.8.4.gemfile - gemfiles/sequel-3.23.0.gemfile - gemfiles/active_record-2.0.0.gemfile - gemfiles/active_record-3.1.1.gemfile.lock - gemfiles/active_model-3.2.12.gemfile.lock - gemfiles/data_mapper-0.9.7.gemfile - gemfiles/active_record-3.0.0.gemfile - Appraisals - Rakefile - test/files/switch.rb - test/files/en.yml - test/test_helper.rb - test/unit/matcher_helpers_test.rb - test/unit/machine_collection_test.rb - test/unit/machine_test.rb - test/unit/helper_module_test.rb - test/unit/state_collection_test.rb - test/unit/transition_collection_test.rb - test/unit/state_test.rb - test/unit/branch_test.rb - test/unit/state_context_test.rb - test/unit/integrations/mongoid_test.rb - test/unit/integrations/active_model_test.rb - test/unit/integrations/mongo_mapper_test.rb - test/unit/integrations/sequel_test.rb - test/unit/integrations/active_record_test.rb - test/unit/integrations/data_mapper_test.rb - test/unit/integrations/base_test.rb - test/unit/transition_test.rb - test/unit/graph_test.rb - test/unit/invalid_transition_test.rb - test/unit/event_collection_test.rb - test/unit/path_test.rb - test/unit/callback_test.rb - test/unit/state_machine_test.rb - test/unit/matcher_test.rb - test/unit/node_collection_test.rb - test/unit/invalid_event_test.rb - test/unit/event_test.rb - test/unit/invalid_parallel_transition_test.rb - test/unit/eval_helpers_test.rb - test/unit/integrations_test.rb - test/unit/assertions_test.rb - test/unit/error_test.rb - test/unit/path_collection_test.rb - test/functional/state_machine_test.rb homepage: http://www.pluginaweek.org licenses: [] post_install_message: rdoc_options: - --line-numbers - --inline-source - --title - state_machine - --main - README.md require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ">=" - !ruby/object:Gem::Version hash: 3 segments: - 0 version: "0" required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ">=" - !ruby/object:Gem::Version hash: 3 segments: - 0 version: "0" requirements: [] rubyforge_project: rubygems_version: 1.8.24 signing_key: specification_version: 3 summary: State machines for attributes test_files: - test/files/switch.rb - test/files/en.yml - test/test_helper.rb - test/unit/matcher_helpers_test.rb - test/unit/machine_collection_test.rb - test/unit/machine_test.rb - test/unit/helper_module_test.rb - test/unit/state_collection_test.rb - test/unit/transition_collection_test.rb - test/unit/state_test.rb - test/unit/branch_test.rb - test/unit/state_context_test.rb - test/unit/integrations/mongoid_test.rb - test/unit/integrations/active_model_test.rb - test/unit/integrations/mongo_mapper_test.rb - test/unit/integrations/sequel_test.rb - test/unit/integrations/active_record_test.rb - test/unit/integrations/data_mapper_test.rb - test/unit/integrations/base_test.rb - test/unit/transition_test.rb - test/unit/graph_test.rb - test/unit/invalid_transition_test.rb - test/unit/event_collection_test.rb - test/unit/path_test.rb - test/unit/callback_test.rb - test/unit/state_machine_test.rb - test/unit/matcher_test.rb - test/unit/node_collection_test.rb - test/unit/invalid_event_test.rb - test/unit/event_test.rb - test/unit/invalid_parallel_transition_test.rb - test/unit/eval_helpers_test.rb - test/unit/integrations_test.rb - test/unit/assertions_test.rb - test/unit/error_test.rb - test/unit/path_collection_test.rb - test/functional/state_machine_test.rb state-machine-1.2.0/test/0000755000175000017500000000000012305405267014575 5ustar boutilboutilstate-machine-1.2.0/test/files/0000755000175000017500000000000012305405267015677 5ustar boutilboutilstate-machine-1.2.0/test/files/switch.rb0000644000175000017500000000034612305405267017530 0ustar boutilboutilclass Switch def self.name @name ||= "Switch_#{rand(1000000)}" end state_machine do event :turn_on do transition all => :on end event :turn_off do transition all => :off end end end state-machine-1.2.0/test/files/en.yml0000644000175000017500000000055412305405267017030 0ustar boutilboutilen: activerecord: errors: messages: invalid_transition: "cannot transition" activemodel: errors: messages: invalid_transition: "cannot %{event}" mongoid: errors: messages: invalid_transition: "cannot transition" mongo_mapper: errors: messages: invalid_transition: "cannot transition" state-machine-1.2.0/test/unit/0000755000175000017500000000000012305405267015554 5ustar boutilboutilstate-machine-1.2.0/test/unit/error_test.rb0000644000175000017500000000265612305405267020302 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class ErrorByDefaultTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(@machine) end def test_should_not_have_any_nodes assert_equal 0, @collection.length end def test_should_have_a_machine assert_equal @machine, @collection.machine end def test_should_index_by_name @collection << object = Struct.new(:name).new(:parked) assert_equal object, @collection[:parked] end end class ErrorWithMessageTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(@machine) end def test_should_raise_exception_if_invalid_option_specified exception = assert_raise(ArgumentError) { StateMachine::NodeCollection.new(@machine, :invalid => true) } assert_equal 'Invalid key(s): invalid', exception.message end def test_should_raise_exception_on_lookup_if_invalid_index_specified exception = assert_raise(ArgumentError) { @collection[:something, :invalid] } assert_equal 'Invalid index: :invalid', exception.message end def test_should_raise_exception_on_fetch_if_invalid_index_specified exception = assert_raise(ArgumentError) { @collection.fetch(:something, :invalid) } assert_equal 'Invalid index: :invalid', exception.message end end state-machine-1.2.0/test/unit/matcher_test.rb0000644000175000017500000000755512305405267020577 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class MatcherByDefaultTest < Test::Unit::TestCase def setup @matcher = StateMachine::Matcher.new end def test_should_have_no_values assert_equal [], @matcher.values end def test_should_filter_all_values assert_equal [], @matcher.filter([:parked, :idling]) end end class MatcherWithValueTest < Test::Unit::TestCase def setup @matcher = StateMachine::Matcher.new(nil) end def test_should_have_values assert_equal [nil], @matcher.values end def test_should_filter_unknown_values assert_equal [nil], @matcher.filter([nil, :parked]) end end class MatcherWithMultipleValuesTest < Test::Unit::TestCase def setup @matcher = StateMachine::Matcher.new([:parked, :idling]) end def test_should_have_values assert_equal [:parked, :idling], @matcher.values end def test_should_filter_unknown_values assert_equal [:parked], @matcher.filter([:parked, :first_gear]) end end class AllMatcherTest < Test::Unit::TestCase def setup @matcher = StateMachine::AllMatcher.instance end def test_should_have_no_values assert_equal [], @matcher.values end def test_should_always_match [nil, :parked, :idling].each {|value| assert @matcher.matches?(value)} end def test_should_not_filter_any_values assert_equal [:parked, :idling], @matcher.filter([:parked, :idling]) end def test_should_generate_blacklist_matcher_after_subtraction matcher = @matcher - [:parked, :idling] assert_instance_of StateMachine::BlacklistMatcher, matcher assert_equal [:parked, :idling], matcher.values end def test_should_have_a_description assert_equal 'all', @matcher.description end end class WhitelistMatcherTest < Test::Unit::TestCase def setup @matcher = StateMachine::WhitelistMatcher.new([:parked, :idling]) end def test_should_have_values assert_equal [:parked, :idling], @matcher.values end def test_should_filter_unknown_values assert_equal [:parked, :idling], @matcher.filter([:parked, :idling, :first_gear]) end def test_should_match_known_values assert @matcher.matches?(:parked) end def test_should_not_match_unknown_values assert !@matcher.matches?(:first_gear) end def test_should_have_a_description assert_equal '[:parked, :idling]', @matcher.description matcher = StateMachine::WhitelistMatcher.new([:parked]) assert_equal ':parked', matcher.description end end class BlacklistMatcherTest < Test::Unit::TestCase def setup @matcher = StateMachine::BlacklistMatcher.new([:parked, :idling]) end def test_should_have_values assert_equal [:parked, :idling], @matcher.values end def test_should_filter_known_values assert_equal [:first_gear], @matcher.filter([:parked, :idling, :first_gear]) end def test_should_match_unknown_values assert @matcher.matches?(:first_gear) end def test_should_not_match_known_values assert !@matcher.matches?(:parked) end def test_should_have_a_description assert_equal 'all - [:parked, :idling]', @matcher.description matcher = StateMachine::BlacklistMatcher.new([:parked]) assert_equal 'all - :parked', matcher.description end end class LoopbackMatcherTest < Test::Unit::TestCase def setup @matcher = StateMachine::LoopbackMatcher.instance end def test_should_have_no_values assert_equal [], @matcher.values end def test_should_filter_all_values assert_equal [], @matcher.filter([:parked, :idling]) end def test_should_match_if_from_context_is_same assert @matcher.matches?(:parked, :from => :parked) end def test_should_not_match_if_from_context_is_different assert !@matcher.matches?(:parked, :from => :idling) end def test_should_have_a_description assert_equal 'same', @matcher.description end end state-machine-1.2.0/test/unit/callback_test.rb0000644000175000017500000005035512305405267020704 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class CallbackTest < Test::Unit::TestCase def test_should_raise_exception_if_invalid_type_specified exception = assert_raise(ArgumentError) { StateMachine::Callback.new(:invalid) {} } assert_equal 'Type must be :before, :after, :around, or :failure', exception.message end def test_should_not_raise_exception_if_using_before_type assert_nothing_raised { StateMachine::Callback.new(:before) {} } end def test_should_not_raise_exception_if_using_after_type assert_nothing_raised { StateMachine::Callback.new(:after) {} } end def test_should_not_raise_exception_if_using_around_type assert_nothing_raised { StateMachine::Callback.new(:around) {} } end def test_should_not_raise_exception_if_using_failure_type assert_nothing_raised { StateMachine::Callback.new(:failure) {} } end def test_should_raise_exception_if_no_methods_specified exception = assert_raise(ArgumentError) { StateMachine::Callback.new(:before) } assert_equal 'Method(s) for callback must be specified', exception.message end def test_should_not_raise_exception_if_method_specified_in_do_option assert_nothing_raised { StateMachine::Callback.new(:before, :do => :run) } end def test_should_not_raise_exception_if_method_specified_as_argument assert_nothing_raised { StateMachine::Callback.new(:before, :run) } end def test_should_not_raise_exception_if_method_specified_as_block assert_nothing_raised { StateMachine::Callback.new(:before, :run) {} } end def test_should_not_raise_exception_if_implicit_option_specified assert_nothing_raised { StateMachine::Callback.new(:before, :do => :run, :invalid => :valid) } end def test_should_not_bind_to_objects assert !StateMachine::Callback.bind_to_object end def test_should_not_have_a_terminator assert_nil StateMachine::Callback.terminator end end class CallbackByDefaultTest < Test::Unit::TestCase def setup @callback = StateMachine::Callback.new(:before) {} end def test_should_have_type assert_equal :before, @callback.type end def test_should_not_have_a_terminator assert_nil @callback.terminator end def test_should_have_a_branch_with_all_matcher_requirements assert_equal StateMachine::AllMatcher.instance, @callback.branch.event_requirement assert_equal StateMachine::AllMatcher.instance, @callback.branch.state_requirements.first[:from] assert_equal StateMachine::AllMatcher.instance, @callback.branch.state_requirements.first[:to] end def test_should_not_have_any_known_states assert_equal [], @callback.known_states end end class CallbackWithMethodArgumentTest < Test::Unit::TestCase def setup @callback = StateMachine::Callback.new(:before, lambda {|*args| @args = args}) @object = Object.new @result = @callback.call(@object) end def test_should_be_successful assert @result end def test_should_call_with_empty_context assert_equal [@object], @args end end class CallbackWithMultipleMethodArgumentsTest < Test::Unit::TestCase def setup @callback = StateMachine::Callback.new(:before, :run_1, :run_2) class << @object = Object.new attr_accessor :callbacks def run_1 (@callbacks ||= []) << :run_1 end def run_2 (@callbacks ||= []) << :run_2 end end @result = @callback.call(@object) end def test_should_be_successful assert @result end def test_should_call_each_callback_in_order assert_equal [:run_1, :run_2], @object.callbacks end end class CallbackWithDoMethodTest < Test::Unit::TestCase def setup @callback = StateMachine::Callback.new(:before, :do => lambda {|*args| @args = args}) @object = Object.new @result = @callback.call(@object) end def test_should_be_successful assert @result end def test_should_call_with_empty_context assert_equal [@object], @args end end class CallbackWithMultipleDoMethodsTest < Test::Unit::TestCase def setup @callback = StateMachine::Callback.new(:before, :do => [:run_1, :run_2]) class << @object = Object.new attr_accessor :callbacks def run_1 (@callbacks ||= []) << :run_1 end def run_2 (@callbacks ||= []) << :run_2 end end @result = @callback.call(@object) end def test_should_be_successful assert @result end def test_should_call_each_callback_in_order assert_equal [:run_1, :run_2], @object.callbacks end end class CallbackWithBlockTest < Test::Unit::TestCase def setup @callback = StateMachine::Callback.new(:before) do |*args| @args = args end @object = Object.new @result = @callback.call(@object) end def test_should_be_successful assert @result end def test_should_call_with_empty_context assert_equal [@object], @args end end class CallbackWithMixedMethodsTest < Test::Unit::TestCase def setup @callback = StateMachine::Callback.new(:before, :run_argument, :do => :run_do) do |object| object.callbacks << :block end class << @object = Object.new attr_accessor :callbacks def run_argument (@callbacks ||= []) << :argument end def run_do (@callbacks ||= []) << :do end end @result = @callback.call(@object) end def test_should_be_successful assert @result end def test_should_call_each_callback_in_order assert_equal [:argument, :do, :block], @object.callbacks end end class CallbackWithExplicitRequirementsTest < Test::Unit::TestCase def setup @object = Object.new @callback = StateMachine::Callback.new(:before, :from => :parked, :to => :idling, :on => :ignite, :do => lambda {}) end def test_should_call_with_empty_context assert @callback.call(@object, {}) end def test_should_not_call_if_from_not_included assert !@callback.call(@object, :from => :idling) end def test_should_not_call_if_to_not_included assert !@callback.call(@object, :to => :parked) end def test_should_not_call_if_on_not_included assert !@callback.call(@object, :on => :park) end def test_should_call_if_all_requirements_met assert @callback.call(@object, :from => :parked, :to => :idling, :on => :ignite) end def test_should_include_in_known_states assert_equal [:parked, :idling], @callback.known_states end end class CallbackWithImplicitRequirementsTest < Test::Unit::TestCase def setup @object = Object.new @callback = StateMachine::Callback.new(:before, :parked => :idling, :on => :ignite, :do => lambda {}) end def test_should_call_with_empty_context assert @callback.call(@object, {}) end def test_should_not_call_if_from_not_included assert !@callback.call(@object, :from => :idling) end def test_should_not_call_if_to_not_included assert !@callback.call(@object, :to => :parked) end def test_should_not_call_if_on_not_included assert !@callback.call(@object, :on => :park) end def test_should_call_if_all_requirements_met assert @callback.call(@object, :from => :parked, :to => :idling, :on => :ignite) end def test_should_include_in_known_states assert_equal [:parked, :idling], @callback.known_states end end class CallbackWithIfConditionTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_call_if_true callback = StateMachine::Callback.new(:before, :if => lambda {true}, :do => lambda {}) assert callback.call(@object) end def test_should_not_call_if_false callback = StateMachine::Callback.new(:before, :if => lambda {false}, :do => lambda {}) assert !callback.call(@object) end end class CallbackWithUnlessConditionTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_call_if_false callback = StateMachine::Callback.new(:before, :unless => lambda {false}, :do => lambda {}) assert callback.call(@object) end def test_should_not_call_if_true callback = StateMachine::Callback.new(:before, :unless => lambda {true}, :do => lambda {}) assert !callback.call(@object) end end class CallbackWithoutTerminatorTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_not_halt_if_result_is_false callback = StateMachine::Callback.new(:before, :do => lambda {false}, :terminator => nil) assert_nothing_thrown { callback.call(@object) } end end class CallbackWithTerminatorTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_not_halt_if_terminator_does_not_match callback = StateMachine::Callback.new(:before, :do => lambda {false}, :terminator => lambda {|result| result == true}) assert_nothing_thrown { callback.call(@object) } end def test_should_halt_if_terminator_matches callback = StateMachine::Callback.new(:before, :do => lambda {false}, :terminator => lambda {|result| result == false}) assert_throws(:halt) { callback.call(@object) } end def test_should_halt_if_terminator_matches_any_method callback = StateMachine::Callback.new(:before, :do => [lambda {true}, lambda {false}], :terminator => lambda {|result| result == false}) assert_throws(:halt) { callback.call(@object) } end end class CallbackWithoutArgumentsTest < Test::Unit::TestCase def setup @callback = StateMachine::Callback.new(:before, :do => lambda {|object| @arg = object}) @object = Object.new @callback.call(@object, {}, 1, 2, 3) end def test_should_call_method_with_object_as_argument assert_equal @object, @arg end end class CallbackWithArgumentsTest < Test::Unit::TestCase def setup @callback = StateMachine::Callback.new(:before, :do => lambda {|*args| @args = args}) @object = Object.new @callback.call(@object, {}, 1, 2, 3) end def test_should_call_method_with_all_arguments assert_equal [@object, 1, 2, 3], @args end end class CallbackWithUnboundMethodTest < Test::Unit::TestCase def setup @callback = StateMachine::Callback.new(:before, :do => lambda {|*args| @context = args.unshift(self)}) @object = Object.new @callback.call(@object, {}, 1, 2, 3) end def test_should_call_method_outside_the_context_of_the_object assert_equal [self, @object, 1, 2, 3], @context end end class CallbackWithBoundMethodTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_call_method_within_the_context_of_the_object_for_block_methods context = nil callback = StateMachine::Callback.new(:before, :do => lambda {|*args| context = [self] + args}, :bind_to_object => true) callback.call(@object, {}, 1, 2, 3) assert_equal [@object, 1, 2, 3], context end def test_should_ignore_option_for_symbolic_methods class << @object attr_reader :context def after_ignite(*args) @context = args end end callback = StateMachine::Callback.new(:before, :do => :after_ignite, :bind_to_object => true) callback.call(@object) assert_equal [], @object.context end def test_should_ignore_option_for_string_methods callback = StateMachine::Callback.new(:before, :do => '[1, 2, 3]', :bind_to_object => true) assert callback.call(@object) end end class CallbackWithMultipleBoundMethodsTest < Test::Unit::TestCase def setup @object = Object.new first_context = nil second_context = nil @callback = StateMachine::Callback.new(:before, :do => [lambda {first_context = self}, lambda {second_context = self}], :bind_to_object => true) @callback.call(@object) @first_context = first_context @second_context = second_context end def test_should_call_each_method_within_the_context_of_the_object assert_equal @object, @first_context assert_equal @object, @second_context end end class CallbackWithApplicationBoundObjectTest < Test::Unit::TestCase def setup @original_bind_to_object = StateMachine::Callback.bind_to_object StateMachine::Callback.bind_to_object = true context = nil @callback = StateMachine::Callback.new(:before, :do => lambda {|*args| context = self}) @object = Object.new @callback.call(@object) @context = context end def test_should_call_method_within_the_context_of_the_object assert_equal @object, @context end def teardown StateMachine::Callback.bind_to_object = @original_bind_to_object end end class CallbackWithBoundMethodAndArgumentsTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_include_single_argument_if_specified context = nil callback = StateMachine::Callback.new(:before, :do => lambda {|arg1| context = [arg1]}, :bind_to_object => true) callback.call(@object, {}, 1) assert_equal [1], context end def test_should_include_multiple_arguments_if_specified context = nil callback = StateMachine::Callback.new(:before, :do => lambda {|arg1, arg2, arg3| context = [arg1, arg2, arg3]}, :bind_to_object => true) callback.call(@object, {}, 1, 2, 3) assert_equal [1, 2, 3], context end def test_should_include_arguments_if_splat_used context = nil callback = StateMachine::Callback.new(:before, :do => lambda {|*args| context = args}, :bind_to_object => true) callback.call(@object, {}, 1, 2, 3) assert_equal [1, 2, 3], context end end class CallbackWithApplicationTerminatorTest < Test::Unit::TestCase def setup @original_terminator = StateMachine::Callback.terminator StateMachine::Callback.terminator = lambda {|result| result == false} @object = Object.new end def test_should_not_halt_if_terminator_does_not_match callback = StateMachine::Callback.new(:before, :do => lambda {true}) assert_nothing_thrown { callback.call(@object) } end def test_should_halt_if_terminator_matches callback = StateMachine::Callback.new(:before, :do => lambda {false}) assert_throws(:halt) { callback.call(@object) } end def teardown StateMachine::Callback.terminator = @original_terminator end end class CallbackWithAroundTypeAndBlockTest < Test::Unit::TestCase def setup @object = Object.new @callbacks = [] end def test_should_evaluate_before_without_after callback = StateMachine::Callback.new(:around, lambda {|*args| block = args.pop; @args = args; block.call}) assert callback.call(@object) assert_equal [@object], @args end def test_should_evaluate_after_without_before callback = StateMachine::Callback.new(:around, lambda {|*args| block = args.pop; block.call; @args = args}) assert callback.call(@object) assert_equal [@object], @args end def test_should_halt_if_not_yielded callback = StateMachine::Callback.new(:around, lambda {|block|}) assert_throws(:halt) { callback.call(@object) } end def test_should_call_block_after_before callback = StateMachine::Callback.new(:around, lambda {|block| @callbacks << :before; block.call}) assert callback.call(@object) { @callbacks << :block } assert_equal [:before, :block], @callbacks end def test_should_call_block_before_after @callbacks = [] callback = StateMachine::Callback.new(:around, lambda {|block| block.call; @callbacks << :after}) assert callback.call(@object) { @callbacks << :block } assert_equal [:block, :after], @callbacks end def test_should_halt_if_block_halts callback = StateMachine::Callback.new(:around, lambda {|block| block.call; @callbacks << :after}) assert_throws(:halt) { callback.call(@object) { throw :halt } } assert_equal [], @callbacks end end class CallbackWithAroundTypeAndMultipleMethodsTest < Test::Unit::TestCase def setup @callback = StateMachine::Callback.new(:around, :run_1, :run_2) class << @object = Object.new attr_accessor :before_callbacks attr_accessor :after_callbacks def run_1 (@before_callbacks ||= []) << :run_1 yield (@after_callbacks ||= []) << :run_1 end def run_2 (@before_callbacks ||= []) << :run_2 yield (@after_callbacks ||= []) << :run_2 end end end def test_should_succeed assert @callback.call(@object) end def test_should_evaluate_before_callbacks_in_order @callback.call(@object) assert_equal [:run_1, :run_2], @object.before_callbacks end def test_should_evaluate_after_callbacks_in_reverse_order @callback.call(@object) assert_equal [:run_2, :run_1], @object.after_callbacks end def test_should_call_block_after_before_callbacks @callback.call(@object) { (@object.before_callbacks ||= []) << :block } assert_equal [:run_1, :run_2, :block], @object.before_callbacks end def test_should_call_block_before_after_callbacks @callback.call(@object) { (@object.after_callbacks ||= []) << :block } assert_equal [:block, :run_2, :run_1], @object.after_callbacks end def test_should_halt_if_first_doesnt_yield class << @object remove_method :run_1 def run_1 (@before_callbacks ||= []) << :run_1 end end catch(:halt) do @callback.call(@object) { (@object.before_callbacks ||= []) << :block } end assert_equal [:run_1], @object.before_callbacks assert_nil @object.after_callbacks end def test_should_halt_if_last_doesnt_yield class << @object remove_method :run_2 def run_2 (@before_callbacks ||= []) << :run_2 end end catch(:halt) { @callback.call(@object) } assert_equal [:run_1, :run_2], @object.before_callbacks assert_nil @object.after_callbacks end def test_should_not_evaluate_further_methods_if_after_halts class << @object remove_method :run_2 def run_2 (@before_callbacks ||= []) << :run_2 yield (@after_callbacks ||= []) << :run_2 throw :halt end end catch(:halt) { @callback.call(@object) } assert_equal [:run_1, :run_2], @object.before_callbacks assert_equal [:run_2], @object.after_callbacks end end class CallbackWithAroundTypeAndArgumentsTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_include_object_if_specified callback = StateMachine::Callback.new(:around, lambda {|object, block| @args = [object]; block.call}) callback.call(@object) assert_equal [@object], @args end def test_should_include_arguments_if_specified callback = StateMachine::Callback.new(:around, lambda {|object, arg1, arg2, arg3, block| @args = [object, arg1, arg2, arg3]; block.call}) callback.call(@object, {}, 1, 2, 3) assert_equal [@object, 1, 2, 3], @args end def test_should_include_arguments_if_splat_used callback = StateMachine::Callback.new(:around, lambda {|*args| block = args.pop; @args = args; block.call}) callback.call(@object, {}, 1, 2, 3) assert_equal [@object, 1, 2, 3], @args end end class CallbackWithAroundTypeAndTerminatorTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_not_halt_if_terminator_does_not_match callback = StateMachine::Callback.new(:around, :do => lambda {|block| block.call(false); false}, :terminator => lambda {|result| result == true}) assert_nothing_thrown { callback.call(@object) } end def test_should_not_halt_if_terminator_matches callback = StateMachine::Callback.new(:around, :do => lambda {|block| block.call(false); false}, :terminator => lambda {|result| result == false}) assert_nothing_thrown { callback.call(@object) } end end class CallbackWithAroundTypeAndBoundMethodTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_call_method_within_the_context_of_the_object context = nil callback = StateMachine::Callback.new(:around, :do => lambda {|block| context = self; block.call}, :bind_to_object => true) callback.call(@object, {}, 1, 2, 3) assert_equal @object, context end def test_should_include_arguments_if_specified context = nil callback = StateMachine::Callback.new(:around, :do => lambda {|*args| block = args.pop; context = args; block.call}, :bind_to_object => true) callback.call(@object, {}, 1, 2, 3) assert_equal [1, 2, 3], context end end state-machine-1.2.0/test/unit/integrations/0000755000175000017500000000000012305405267020262 5ustar boutilboutilstate-machine-1.2.0/test/unit/integrations/base_test.rb0000644000175000017500000000466312305405267022571 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../../test_helper') # Load library require 'rubygems' module BaseTest class IntegrationTest < Test::Unit::TestCase def test_should_have_an_integration_name assert_equal :base, StateMachine::Integrations::Base.integration_name end def test_should_not_be_available assert !StateMachine::Integrations::Base.available? end def test_should_not_have_any_matching_ancestors assert_equal [], StateMachine::Integrations::Base.matching_ancestors end def test_should_not_match_any_classes assert !StateMachine::Integrations::Base.matches?(Class.new) end def test_should_not_have_a_locale_path assert_nil StateMachine::Integrations::Base.locale_path end end class IncludedTest < Test::Unit::TestCase def setup @integration = Module.new StateMachine::Integrations.const_set('Custom', @integration) @integration.class_eval do include StateMachine::Integrations::Base end end def test_should_not_have_any_defaults assert_nil @integration.defaults end def test_should_not_have_any_versions assert_equal [], @integration.versions end def test_should_track_version version1 = @integration.version '1.0' do def self.active? true end end version2 = @integration.version '2.0' do def self.active? false end end assert_equal [version1, version2], @integration.versions end def test_should_allow_active_versions_to_override_default_behavior @integration.class_eval do def version1_included? false end def version2_included? false end end @integration.version '1.0' do def self.active? true end def version1_included? true end end @integration.version '2.0' do def self.active? false end def version2_included? true end end @machine = StateMachine::Machine.new(Class.new, :integration => :custom) assert @machine.version1_included? assert !@machine.version2_included? end def teardown StateMachine::Integrations.send(:remove_const, 'Custom') super end end end state-machine-1.2.0/test/unit/integrations/mongoid_test.rb0000644000175000017500000020471312305405267023311 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../../test_helper') require 'mongoid' require 'mongoid/version' # Establish database connection Mongoid.configure do |config| if Mongoid::VERSION =~ /^2\./ connection = Mongo::Connection.new('127.0.0.1', 27017, :slave_ok => true, :logger => Logger.new("#{File.dirname(__FILE__)}/../../mongoid.log")) config.master = connection.db('test') else Mongoid.logger = Moped.logger = Logger.new("#{File.dirname(__FILE__)}/../../mongoid.log") config.connect_to('test') end end module MongoidTest class BaseTestCase < Test::Unit::TestCase def default_test end def teardown if @table_names db = Mongoid::VERSION =~ /^2\./ ? Mongoid.master : Mongoid::Sessions.default db.collections.each {|c| c.drop if @table_names.include?(c.name)} end end protected # Creates a new Mongoid model (and the associated table) def new_model(name = :foo, &block) table_name = "#{name}_#{rand(1000000)}" @table_names ||= [] @table_names << table_name model = Class.new do (class << self; self; end).class_eval do define_method(:name) { "MongoidTest::#{name.to_s.capitalize}" } define_method(:to_s) { self.name } end end model.class_eval do include Mongoid::Document if Mongoid::VERSION =~ /^2\./ self.collection_name = table_name else self.default_collection_name = table_name end field :state, :type => String end model.class_eval(&block) if block_given? model end # Creates a new Mongoid observer def new_observer(model, &block) observer = Class.new(Mongoid::Observer) do attr_accessor :notifications def initialize super @notifications = [] end end (class << observer; self; end).class_eval do define_method(:name) do "#{model.name}Observer" end end observer.observe(model) observer.class_eval(&block) if block_given? observer end end class IntegrationTest < BaseTestCase def test_should_have_an_integration_name assert_equal :mongoid, StateMachine::Integrations::Mongoid.integration_name end def test_should_be_available assert StateMachine::Integrations::Mongoid.available? end def test_should_match_if_class_includes_mongoid assert StateMachine::Integrations::Mongoid.matches?(new_model) end def test_should_not_match_if_class_does_not_include_mongoid assert !StateMachine::Integrations::Mongoid.matches?(Class.new) end def test_should_have_defaults assert_equal({:action => :save}, StateMachine::Integrations::Mongoid.defaults) end def test_should_have_a_locale_path assert_not_nil StateMachine::Integrations::Mongoid.locale_path end end class MachineWithoutFieldTest < BaseTestCase def setup @model = new_model StateMachine::Machine.new(@model, :status) end def test_should_define_field_with_string_type field = @model.fields['status'] assert_not_nil field assert_equal String, field.type end end class MachineWithFieldTest < BaseTestCase def setup @model = new_model do field :status, :type => Integer end StateMachine::Machine.new(@model, :status) end def test_should_not_redefine_field field = @model.fields['status'] assert_not_nil field assert_equal Integer, field.type end end class MachineByDefaultTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) end def test_should_use_save_as_action assert_equal :save, @machine.action end def test_should_create_notifier_before_callback assert_equal 1, @machine.callbacks[:before].size end def test_should_create_notifier_after_callback assert_equal 1, @machine.callbacks[:after].size end end class MachineWithStatesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :first_gear end def test_should_humanize_name assert_equal 'first gear', @machine.state(:first_gear).human_name end end class MachineWithStaticInitialStateTest < BaseTestCase def setup @model = new_model(:vehicle) do attr_accessor :value end @machine = StateMachine::Machine.new(@model, :initial => :parked) end def test_should_set_initial_state_on_created_object record = @model.new assert_equal 'parked', record.state end def test_should_set_initial_state_with_nil_attributes record = @model.new(nil) assert_equal 'parked', record.state end def test_should_still_set_attributes record = @model.new(:value => 1) assert_equal 1, record.value end def test_should_still_allow_initialize_blocks block_args = nil record = @model.new do |*args| block_args = args end assert_equal [record], block_args end def test_should_set_attributes_prior_to_initialize_block state = nil @model.new do |record| state = record.state end assert_equal 'parked', state end def test_should_set_attributes_prior_to_after_initialize_hook state = nil @model.after_initialize do |record| state = record.state end @model.new assert_equal 'parked', state end def test_should_persist_initial_state record = @model.new record.save record.reload assert_equal 'parked', record.state end def test_should_persist_initial_state_on_dup record = @model.create.dup record.save record.reload assert_equal 'parked', record.state end def test_should_set_initial_state_before_setting_attributes @model.class_eval do attr_accessor :state_during_setter remove_method :value= define_method(:value=) do |value| self.state_during_setter = state end end record = @model.new(:value => 1) assert_equal 'parked', record.state_during_setter end def test_should_not_set_initial_state_after_already_initialized record = @model.new(:value => 1) assert_equal 'parked', record.state record.state = 'idling' record.attributes = {} assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database @machine.state :idling record = @model.find(@model.create(:state => 'idling').id) assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database_with_nil_state @machine.state nil record = @model.find(@model.create(:state => nil).id) assert_nil record.state end if Mongoid::VERSION >= '2.1.0' def test_should_use_stored_values_when_loading_for_many_association @machine.state :idling @model.belongs_to :owner, :class_name => 'MongoidTest::Owner' MongoidTest.const_set('Vehicle', @model) owner_model = new_model(:owner) do has_many :vehicles, :class_name => 'MongoidTest::Vehicle' end MongoidTest.const_set('Owner', owner_model) owner = owner_model.create record = @model.create(:state => 'idling', :owner_id => owner.id) assert_equal 'idling', owner.vehicles[0].state end def test_should_use_stored_values_when_loading_for_one_association @machine.state :idling @model.belongs_to :owner, :class_name => 'MongoidTest::Owner' MongoidTest.const_set('Vehicle', @model) owner_model = new_model(:owner) do has_one :vehicle, :class_name => 'MongoidTest::Vehicle' end MongoidTest.const_set('Owner', owner_model) owner = owner_model.create record = @model.create(:state => 'idling', :owner_id => owner.id) owner.reload assert_equal 'idling', owner.vehicle.state end def test_should_use_stored_values_when_loading_for_belongs_to_association @machine.state :idling MongoidTest.const_set('Vehicle', @model) driver_model = new_model(:driver) do belongs_to :vehicle, :class_name => 'MongoidTest::Vehicle' end MongoidTest.const_set('Driver', driver_model) record = @model.create(:state => 'idling') driver = driver_model.create(:vehicle_id => record.id) assert_equal 'idling', driver.vehicle.state end end def teardown MongoidTest.class_eval do remove_const('Vehicle') if defined?(MongoidTest::Vehicle) remove_const('Owner') if defined?(MongoidTest::Owner) remove_const('Driver') if defined?(MongoidTest::Driver) end super end end class MachineWithDynamicInitialStateTest < BaseTestCase def setup @model = new_model do attr_accessor :value end @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked}) @machine.state :parked end def test_should_set_initial_state_on_created_object record = @model.new assert_equal 'parked', record.state end def test_should_still_set_attributes record = @model.new(:value => 1) assert_equal 1, record.value end def test_should_still_allow_initialize_blocks block_args = nil record = @model.new do |*args| block_args = args end assert_equal [record], block_args end def test_should_set_attributes_prior_to_initialize_block state = nil @model.new do |record| state = record.state end assert_equal 'parked', state end def test_should_set_attributes_prior_to_after_initialize_hook state = nil @model.after_initialize do |record| state = record.state end @model.new assert_equal 'parked', state end def test_should_set_initial_state_after_setting_attributes @model.class_eval do attr_accessor :state_during_setter remove_method :value= define_method(:value=) do |value| self.state_during_setter = state || 'nil' end end record = @model.new(:value => 1) assert_equal 'nil', record.state_during_setter end def test_should_not_set_initial_state_after_already_initialized record = @model.new(:value => 1) assert_equal 'parked', record.state record.state = 'idling' record.attributes = {} assert_equal 'idling', record.state end def test_should_persist_initial_state record = @model.new record.save record.reload assert_equal 'parked', record.state end def test_should_persist_initial_state_on_dup record = @model.create.dup record.save record.reload assert_equal 'parked', record.state end def test_should_use_stored_values_when_loading_from_database @machine.state :idling record = @model.find(@model.create(:state => 'idling').id) assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database_with_nil_state @machine.state nil record = @model.find(@model.create(:state => nil).id) assert_nil record.state end end class MachineWithEventsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :shift_up end def test_should_humanize_name assert_equal 'shift up', @machine.event(:shift_up).human_name end end class MachineWithDifferentSameDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do field :status, :type => String, :default => 'parked' end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @record = @model.new end def test_should_use_machine_default assert_equal 'parked', @record.status end def test_should_not_generate_a_warning assert_no_match(/have defined a different default/, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithDifferentColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do field :status, :type => String, :default => 'idling' end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @record = @model.new end def test_should_use_machine_default assert_equal 'parked', @record.status end def test_should_generate_a_warning assert_match(/Both MongoidTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithDifferentIntegerColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do field :status, :type => Integer, :default => 0 end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.state :parked, :value => 1 @record = @model.new end def test_should_use_machine_default assert_equal 1, @record.status end def test_should_generate_a_warning assert_match(/Both MongoidTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithConflictingPredicateTest < BaseTestCase def setup @model = new_model do def state?(*args) true end end @machine = StateMachine::Machine.new(@model) @record = @model.new end def test_should_not_define_attribute_predicate assert @record.state? end end class MachineWithConflictingStateNameTest < BaseTestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @model = new_model end def test_should_output_warning_with_same_machine_name @machine = StateMachine::Machine.new(@model) @machine.state :state assert_match(/^Instance method "state\?" is already defined in .*, use generic helper instead.*\n$/, $stderr.string) end def test_should_output_warning_with_same_machine_attribute @machine = StateMachine::Machine.new(@model, :public_state, :attribute => :state) @machine.state :state assert_match(/^Instance method "state\?" is already defined in .*, use generic helper instead.*\n$/, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithColumnStateAttributeTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_not_override_the_column_reader @record[:state] = 'parked' assert_equal 'parked', @record.state end def test_should_not_override_the_column_writer @record.state = 'parked' assert_equal 'parked', @record[:state] end def test_should_have_an_attribute_predicate assert @record.respond_to?(:state?) end def test_should_test_for_existence_on_predicate_without_parameters assert @record.state? @record.state = nil assert !@record.state? end def test_should_return_false_for_predicate_if_does_not_match_current_value assert !@record.state?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.state?(:parked) end def test_should_raise_exception_for_predicate_if_invalid_state_specified assert_raise(IndexError) { @record.state?(:invalid) } end end class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase def setup @model = new_model do def initialize # Skip attribute initialization @initialized_state_machines = true super end end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_define_a_new_key_for_the_attribute assert_not_nil @model.fields['status'] end def test_should_define_a_reader_attribute_for_the_attribute assert @record.respond_to?(:status) end def test_should_define_a_writer_attribute_for_the_attribute assert @record.respond_to?(:status=) end def test_should_define_an_attribute_predicate assert @record.respond_to?(:status?) end end class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase def setup @model = new_model do def status self['status'] end def status=(value) self['status'] = value end end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_return_false_for_predicate_if_does_not_match_current_value assert !@record.status?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.status?(:parked) end def test_should_raise_exception_for_predicate_if_invalid_state_specified assert_raise(IndexError) { @record.status?(:invalid) } end def test_should_set_initial_state_on_created_object assert_equal 'parked', @record.status end end class MachineWithAliasedAttributeTest < BaseTestCase def setup @model = new_model do alias_attribute :vehicle_status, :state end @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status) @machine.state :parked @record = @model.new end def test_should_check_custom_attribute_for_predicate @record.vehicle_status = nil assert !@record.status?(:parked) @record.vehicle_status = 'parked' assert @record.status?(:parked) end end class MachineWithAliasedFieldTest < BaseTestCase def setup @model = new_model do field :status, :as => :vehicle_status end @machine = StateMachine::Machine.new(@model, :vehicle_status) @machine.state :parked @record = @model.new end def test_should_check_custom_attribute_for_predicate @record.vehicle_status = nil assert !@record.vehicle_status?(:parked) @record.vehicle_status = 'parked' assert @record.vehicle_status?(:parked) end end class MachineWithCustomAttributeTest < BaseTestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @model = new_model @machine = StateMachine::Machine.new(@model, :public_state, :attribute => :state) @record = @model.new end def test_should_not_delegate_attribute_predicate_with_different_attribute assert_raise(ArgumentError) { @record.public_state? } end def teardown $stderr = @original_stderr super end end class MachineWithInitializedStateTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.state :idling end def test_should_allow_nil_initial_state_when_static @machine.state nil record = @model.new(:state => nil) assert_nil record.state end def test_should_allow_nil_initial_state_when_dynamic @machine.state nil @machine.initial_state = lambda {:parked} record = @model.new(:state => nil) assert_nil record.state end def test_should_allow_different_initial_state_when_static record = @model.new(:state => 'idling') assert_equal 'idling', record.state end def test_should_allow_different_initial_state_when_dynamic @machine.initial_state = lambda {:parked} record = silence_warnings { @model.new(:state => 'idling') } assert_equal 'idling', record.state end def test_should_use_default_state_if_protected Mongoid.logger = nil @model.class_eval do attr_protected :state end record = @model.new(:state => 'idling') assert_equal 'parked', record.state end end class MachineMultipleTest < BaseTestCase def setup @model = new_model do field :status, :type => String end @state_machine = StateMachine::Machine.new(@model, :initial => :parked) @status_machine = StateMachine::Machine.new(@model, :status, :initial => :idling) end def test_should_should_initialize_each_state record = @model.new assert_equal 'parked', record.state assert_equal 'idling', record.status end end class MachineWithLoopbackTest < BaseTestCase def setup @model = new_model do field :updated_at, :type => Time before_update do |record| record.updated_at = Time.now end end @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create(:updated_at => Time.now - 1) @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @timestamp = @record.updated_at @transition.perform end def test_should_update_record assert_not_equal @timestamp, @record.updated_at end end class MachineWithDirtyAttributesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal %w(state), @record.changed end def test_should_track_attribute_change assert_equal %w(parked idling), @record.changes['state'] end def test_should_not_reset_changes_on_multiple_transitions transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform(false) assert_equal %w(parked idling), @record.changes['state'] end def test_should_not_have_changes_when_loaded_from_database record = @model.find(@record.id) assert !record.changed? end end class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform(false) end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_changes assert_equal nil, @record.send(:attribute_change, 'state') end end class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase def setup @model = new_model do field :status, :type => String end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal %w(status), @record.changed end def test_should_track_attribute_change assert_equal %w(parked idling), @record.changes['status'] end def test_should_not_reset_changes_on_multiple_transitions transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform(false) assert_equal %w(parked idling), @record.changes['status'] end end class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model do field :status, :type => String end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.event :park @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_track_attribute_changes assert_equal nil, @record.send(:attribute_change, 'status') end end class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :ignite @record = @model.create @record.state_event = 'ignite' end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_change assert_equal nil, @record.send(:attribute_change, 'state') end end class MachineWithCallbacksTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.other_states :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) end def test_should_run_before_callbacks called = false @machine.before_transition {called = true} @transition.perform assert called end def test_should_pass_record_to_before_callbacks_with_one_argument record = nil @machine.before_transition {|arg| record = arg} @transition.perform assert_equal @record, record end def test_should_pass_record_and_transition_to_before_callbacks_with_multiple_arguments callback_args = nil @machine.before_transition {|*args| callback_args = args} @transition.perform assert_equal [@record, @transition], callback_args end def test_should_run_before_callbacks_outside_the_context_of_the_record context = nil @machine.before_transition {context = self} @transition.perform assert_equal self, context end def test_should_run_after_callbacks called = false @machine.after_transition {called = true} @transition.perform assert called end def test_should_pass_record_to_after_callbacks_with_one_argument record = nil @machine.after_transition {|arg| record = arg} @transition.perform assert_equal @record, record end def test_should_pass_record_and_transition_to_after_callbacks_with_multiple_arguments callback_args = nil @machine.after_transition {|*args| callback_args = args} @transition.perform assert_equal [@record, @transition], callback_args end def test_should_run_after_callbacks_outside_the_context_of_the_record context = nil @machine.after_transition {context = self} @transition.perform assert_equal self, context end def test_should_run_after_callbacks_if_model_callback_added_prior_to_state_machine_definition model = new_model do after_save { nil } end machine = StateMachine::Machine.new(model, :initial => :parked) machine.other_states :idling machine.event :ignite after_called = false machine.after_transition {after_called = true} record = model.new(:state => 'parked') transition = StateMachine::Transition.new(record, machine, :ignite, :parked, :idling) transition.perform assert_equal true, after_called end def test_should_run_around_callbacks before_called = false after_called = false ensure_called = 0 @machine.around_transition do |block| before_called = true begin block.call ensure ensure_called += 1 end after_called = true end @transition.perform assert before_called assert after_called assert_equal ensure_called, 1 end def test_should_include_transition_states_in_known_states @machine.before_transition :to => :first_gear, :do => lambda {} assert_equal [:parked, :idling, :first_gear], @machine.states.map {|state| state.name} end def test_should_allow_symbolic_callbacks callback_args = nil klass = class << @record; self; end klass.send(:define_method, :after_ignite) do |*args| callback_args = args end @machine.before_transition(:after_ignite) @transition.perform assert_equal [@transition], callback_args end def test_should_allow_string_callbacks class << @record attr_reader :callback_result end @machine.before_transition('@callback_result = [1, 2, 3]') @transition.perform assert_equal [1, 2, 3], @record.callback_result end def test_should_run_in_expected_order expected = [ :before_transition, :before_validation, :after_validation, :before_save, :before_create, :after_create, :after_save, :after_transition ] callbacks = [] @model.before_validation { callbacks << :before_validation } @model.after_validation { callbacks << :after_validation } @model.before_save { callbacks << :before_save } @model.before_create { callbacks << :before_create } @model.after_create { callbacks << :after_create } @model.after_save { callbacks << :after_save } @machine.before_transition { callbacks << :before_transition } @machine.after_transition { callbacks << :after_transition } @transition.perform assert_equal expected, callbacks end end class MachineWithFailedBeforeCallbacksTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @machine.before_transition {@callbacks << :before_1; false} @machine.before_transition {@callbacks << :before_2} @machine.after_transition {@callbacks << :after} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_not_be_successful assert !@result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_run_action assert @record.new_record? end def test_should_not_run_further_callbacks assert_equal [:before_1], @callbacks end end class MachineNestedActionTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new(:state => 'parked') end def test_should_allow_transition_prior_to_creation_if_skipping_action record = @record @model.before_create { record.ignite(false) } result = @record.save assert_equal true, result assert_equal "idling", @record.state @record.reload assert_equal "idling", @record.state end if Mongoid::VERSION !~ /^2\.1\./ def test_should_allow_transition_after_creation record = @record @model.after_create { record.ignite } result = @record.save assert_equal true, result assert_equal "idling", @record.state @record.reload assert_equal "idling", @record.state end end end class MachineWithFailedActionTest < BaseTestCase def setup @model = new_model do validates_numericality_of :state end @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @callbacks = [] @machine.before_transition {@callbacks << :before} @machine.after_transition {@callbacks << :after} @machine.after_failure {@callbacks << :after_failure} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_not_be_successful assert !@result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_save_record assert @record.new_record? end def test_should_run_before_callbacks_and_after_callbacks_with_failures assert_equal [:before, :around_before, :after_failure], @callbacks end end class MachineWithFailedAfterCallbacksTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @machine.after_transition {@callbacks << :after_1; false} @machine.after_transition {@callbacks << :after_2} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_be_successful assert @result end def test_should_change_current_state assert_equal 'idling', @record.state end def test_should_save_record assert !@record.new_record? end def test_should_not_run_further_after_callbacks assert_equal [:around_before, :around_after, :after_1], @callbacks end end class MachineWithValidationsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked @record = @model.new end def test_should_invalidate_using_errors I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:ActiveModel) @record.state = 'parked' @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']]) assert_equal ['State cannot transition via "park"'], @record.errors.full_messages end def test_should_auto_prefix_custom_attributes_on_invalidation @machine.invalidate(@record, :event, :invalid) assert_equal ['State event is invalid'], @record.errors.full_messages end def test_should_clear_errors_on_reset @record.state = 'parked' @record.errors.add(:state, 'is invalid') @machine.reset(@record) assert_equal [], @record.errors.full_messages end def test_should_be_valid_if_state_is_known @record.state = 'parked' assert @record.valid? end def test_should_not_be_valid_if_state_is_unknown @record.state = 'invalid' assert !@record.valid? assert_equal ['State is invalid'], @record.errors.full_messages end end class MachineWithValidationsAndCustomAttributeTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :status, :attribute => :state) @machine.state :parked @record = @model.new end def test_should_add_validation_errors_to_custom_attribute @record.state = 'invalid' assert !@record.valid? assert_equal ['State is invalid'], @record.errors.full_messages @record.state = 'parked' assert @record.valid? end end class MachineErrorsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @record = @model.new end def test_should_be_able_to_describe_current_errors @record.errors.add(:id, 'cannot be blank') @record.errors.add(:state, 'is invalid') assert_equal ['Id cannot be blank', 'State is invalid'], @machine.errors_for(@record).split(', ').sort end def test_should_describe_as_halted_with_no_errors assert_equal 'Transition halted', @machine.errors_for(@record) end end class MachineWithStateDrivenValidationsTest < BaseTestCase def setup @model = new_model do attr_accessor :seatbealt end @machine = StateMachine::Machine.new(@model) @machine.state :first_gear do validates_presence_of :seatbelt, :key => :first_gear end @machine.state :second_gear do validates_presence_of :seatbelt, :key => :second_gear end @machine.other_states :parked end def test_should_be_valid_if_validation_fails_outside_state_scope record = @model.new(:state => 'parked', :seatbelt => nil) assert record.valid? end def test_should_be_invalid_if_validation_fails_within_state_scope record = @model.new(:state => 'first_gear', :seatbelt => nil) assert !record.valid? end def test_should_be_valid_if_validation_succeeds_within_state_scope record = @model.new(:state => 'second_gear', :seatbelt => true) assert record.valid? end end class MachineWithEventAttributesOnValidationTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert !@record.valid? assert_equal ['State event is invalid'], @record.errors.full_messages end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert !@record.valid? assert_equal ['State event cannot transition when idling'], @record.errors.full_messages end def test_should_be_successful_if_event_has_transition assert @record.valid? end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.valid? assert ran_callback end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } begin @record.valid? rescue ArgumentError raise if StateMachine::Transition.pause_supported? end assert ran_callback end def test_should_persist_new_state @record.valid? assert_equal 'idling', @record.state end def test_should_not_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.valid? assert !ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates_presence_of :seatbelt end ran_callback = false @machine.after_transition { ran_callback = true } @record.valid? assert !ran_callback end def test_should_not_run_around_callbacks_after_yield ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } begin @record.valid? rescue ArgumentError raise if StateMachine::Transition.pause_supported? end assert !ran_callback end def test_should_not_run_around_callbacks_after_yield_with_failures_disabled_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates_presence_of :seatbelt end ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } @record.valid? assert !ran_callback end def test_should_run_failure_callbacks_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates_presence_of :seatbelt end ran_callback = false @machine.after_failure { ran_callback = true } @record.valid? assert ran_callback end end class MachineWithEventAttributesOnSaveTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert_equal false, @record.save end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert_equal false, @record.save end def test_should_be_successful_if_event_has_transition assert_equal true, @record.save end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.save assert ran_callback end def test_should_run_before_callbacks_once before_count = 0 @machine.before_transition { before_count += 1 } @record.save assert_equal 1, before_count end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } @record.save assert ran_callback end def test_should_run_around_callbacks_before_yield_once around_before_count = 0 @machine.around_transition {|block| around_before_count += 1; block.call } @record.save assert_equal 1, around_before_count end def test_should_persist_new_state @record.save assert_equal 'idling', @record.state end def test_should_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.save assert ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_fails @model.class_eval do validates_numericality_of :state end ran_callback = false @machine.after_transition { ran_callback = true } begin; @record.save; rescue; end assert !ran_callback end def test_should_run_failure_callbacks__if_fails @model.class_eval do validates_numericality_of :state end ran_callback = false @machine.after_failure { ran_callback = true } begin; @record.save; rescue; end assert ran_callback end def test_should_not_run_around_callbacks_with_failures_disabled_if_fails @model.class_eval do validates_numericality_of :state end ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } begin; @record.save; rescue; end assert !ran_callback end def test_should_run_around_callbacks_after_yield ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } @record.save assert ran_callback end def test_should_allow_additional_transitions_to_new_state_in_after_transitions @machine.event :park do transition :idling => :parked end @machine.after_transition(:on => :ignite) { @record.park } @record.save assert_equal 'parked', @record.state @record.reload assert_equal 'parked', @record.state end def test_should_allow_additional_transitions_to_previous_state_in_after_transitions @machine.event :shift_up do transition :idling => :first_gear end @machine.after_transition(:on => :ignite) { @record.shift_up } @record.save assert_equal 'first_gear', @record.state @record.reload assert_equal 'first_gear', @record.state end end if Mongoid::VERSION >= '2.1.0' class MachineWithEventAttributesOnAutosaveTest < BaseTestCase def setup @vehicle_model = new_model(:vehicle) do belongs_to :owner, :class_name => 'MongoidTest::Owner' end MongoidTest.const_set('Vehicle', @vehicle_model) @owner_model = new_model(:owner) MongoidTest.const_set('Owner', @owner_model) machine = StateMachine::Machine.new(@vehicle_model) machine.event :ignite do transition :parked => :idling end @owner = @owner_model.create end def test_should_persist_many_association @owner_model.has_many :vehicles, :class_name => 'MongoidTest::Vehicle', :autosave => true @vehicle = @vehicle_model.create(:state => 'parked', :owner_id => @owner.id) @owner.vehicles[0].state_event = 'ignite' @owner.save @vehicle.reload assert_equal 'idling', @vehicle.state end def test_should_persist_one_association @owner_model.has_one :vehicle, :class_name => 'MongoidTest::Vehicle', :autosave => true @vehicle = @vehicle_model.create(:state => 'parked', :owner_id => @owner.id) @owner.vehicle.state_event = 'ignite' @owner.save @vehicle.reload assert_equal 'idling', @vehicle.state end def teardown MongoidTest.class_eval do remove_const('Vehicle') remove_const('Owner') end super end end end class MachineWithEventAttributesOnSaveBangTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert_raise(Mongoid::Errors::Validations) { @record.save! } end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert_raise(Mongoid::Errors::Validations) { @record.save! } end def test_should_be_successful_if_event_has_transition assert_equal true, @record.save! end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.save! assert ran_callback end def test_should_run_before_callbacks_once before_count = 0 @machine.before_transition { before_count += 1 } @record.save! assert_equal 1, before_count end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } @record.save! assert ran_callback end def test_should_run_around_callbacks_before_yield_once around_before_count = 0 @machine.around_transition {|block| around_before_count += 1; block.call } @record.save! assert_equal 1, around_before_count end def test_should_persist_new_state @record.save! assert_equal 'idling', @record.state end def test_should_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.save! assert ran_callback end def test_should_run_around_callbacks_after_yield ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } @record.save! assert ran_callback end end class MachineWithEventAttributesOnCustomActionTest < BaseTestCase def setup @superclass = new_model do def persist upsert end end @model = Class.new(@superclass) @machine = StateMachine::Machine.new(@model, :action => :persist) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_not_transition_on_valid? @record.valid? assert_equal 'parked', @record.state end def test_should_not_transition_on_save @record.save assert_equal 'parked', @record.state end def test_should_not_transition_on_save! @record.save! assert_equal 'parked', @record.state end def test_should_transition_on_custom_action @record.persist assert_equal 'idling', @record.state end end class MachineWithObserversTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) end def test_should_call_all_transition_callback_permutations callbacks = [ :before_ignite_from_parked_to_idling, :before_ignite_from_parked, :before_ignite_to_idling, :before_ignite, :before_transition_state_from_parked_to_idling, :before_transition_state_from_parked, :before_transition_state_to_idling, :before_transition_state, :before_transition ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance @transition.perform assert_equal callbacks, instance.notifications end def test_should_call_no_transition_callbacks_when_observers_disabled return unless ::ActiveModel::VERSION::MAJOR >= 3 && ::ActiveModel::VERSION::MINOR >= 1 callbacks = [ :before_ignite, :before_transition ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance @model.observers.disable(observer) do @transition.perform end assert_equal [], instance.notifications end def test_should_pass_record_and_transition_to_before_callbacks observer = new_observer(@model) do def before_transition(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end def test_should_pass_record_and_transition_to_after_callbacks observer = new_observer(@model) do def after_transition(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end def test_should_call_methods_outside_the_context_of_the_record observer = new_observer(@model) do def before_ignite(*args) notifications << self end end instance = observer.instance @transition.perform assert_equal [instance], instance.notifications end def test_should_support_nil_from_states callbacks = [ :before_ignite_from_nil_to_idling, :before_ignite_from_nil, :before_transition_state_from_nil_to_idling, :before_transition_state_from_nil ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance transition = StateMachine::Transition.new(@record, @machine, :ignite, nil, :idling) transition.perform assert_equal callbacks, instance.notifications end def test_should_support_nil_to_states callbacks = [ :before_ignite_from_parked_to_nil, :before_ignite_to_nil, :before_transition_state_from_parked_to_nil, :before_transition_state_to_nil ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, nil) transition.perform assert_equal callbacks, instance.notifications end end class MachineWithNamespacedObserversTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :state, :namespace => 'alarm') @machine.state :active, :off @machine.event :enable @record = @model.new(:state => 'off') @transition = StateMachine::Transition.new(@record, @machine, :enable, :off, :active) end def test_should_call_namespaced_before_event_method observer = new_observer(@model) do def before_enable_alarm(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end def test_should_call_namespaced_after_event_method observer = new_observer(@model) do def after_enable_alarm(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end end class MachineWithScopesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :first_gear @machine.state :idling, :value => lambda {'idling'} end def test_should_create_singular_with_scope assert @model.respond_to?(:with_state) end def test_should_only_include_records_with_state_in_singular_with_scope parked = @model.create :state => 'parked' @model.create :state => 'idling' assert_equal [parked], @model.with_state(:parked).to_a end def test_should_create_plural_with_scope assert @model.respond_to?(:with_states) end def test_should_only_include_records_with_states_in_plural_with_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked, idling], @model.with_states(:parked, :idling).to_a end def test_should_allow_lookup_by_string_name parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked, idling], @model.with_states('parked', 'idling').to_a end def test_should_create_singular_without_scope assert @model.respond_to?(:without_state) end def test_should_only_include_records_without_state_in_singular_without_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked], @model.without_state(:idling).to_a end def test_should_create_plural_without_scope assert @model.respond_to?(:without_states) end def test_should_only_include_records_without_states_in_plural_without_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' first_gear = @model.create :state => 'first_gear' assert_equal [parked, idling], @model.without_states(:first_gear).to_a end def test_should_allow_chaining_scopes parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [idling], @model.without_state(:parked).with_state(:idling).all end end class MachineWithScopesAndOwnerSubclassTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :state) MongoidTest.const_set('Foo', @model) # Remove the #name override so that Mongoid picks up the subclass name # properly class << @model; remove_method(:name); end @subclass = MongoidTest.class_eval <<-end_eval class SubFoo < MongoidTest::Foo self end end_eval @subclass_machine = @subclass.state_machine(:state) {} @subclass_machine.state :parked, :idling, :first_gear end def test_should_only_include_records_with_subclass_states_in_with_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' assert_equal [parked, idling], @subclass.with_states(:parked, :idling).to_a end def test_should_only_include_records_without_subclass_states_in_without_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' first_gear = @subclass.create :state => 'first_gear' assert_equal [parked, idling], @subclass.without_states(:first_gear).to_a end def teardown MongoidTest.send(:remove_const, 'SubFoo') MongoidTest.send(:remove_const, 'Foo') super end end class MachineWithComplexPluralizationScopesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :status) end def test_should_create_singular_with_scope assert @model.respond_to?(:with_status) end def test_should_create_plural_with_scope assert @model.respond_to?(:with_statuses) end end class MachineWithDefaultScope < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.state :idling @model.class_eval do default_scope with_state(:parked, :idling) end end def test_should_set_initial_state_on_created_object object = @model.new assert_equal 'parked', object.state end end class MachineWithInternationalizationTest < BaseTestCase def setup I18n.backend = I18n::Backend::Simple.new # Initialize the backend StateMachine::Machine.new(new_model) I18n.backend.translate(:en, 'mongoid.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling') @model = new_model end def test_should_use_defaults I18n.backend.store_translations(:en, { :mongoid => {:errors => {:messages => {:invalid_transition => 'cannot %{event}'}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked, :idling machine.event :ignite record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_error_key I18n.backend.store_translations(:en, { :mongoid => {:errors => {:messages => {:bad_transition => 'cannot %{event}'}}} }) machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => :bad_transition}) machine.state :parked, :idling record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_error_string machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => 'cannot %{event}'}) machine.state :parked, :idling record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_state_key_scoped_to_class_and_machine I18n.backend.store_translations(:en, { :mongoid => {:state_machines => {:'mongoid_test/foo' => {:state => {:states => {:parked => 'shutdown'}}}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_scoped_to_class I18n.backend.store_translations(:en, { :mongoid => {:state_machines => {:'mongoid_test/foo' => {:states => {:parked => 'shutdown'}}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_scoped_to_machine I18n.backend.store_translations(:en, { :mongoid => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_unscoped I18n.backend.store_translations(:en, { :mongoid => {:state_machines => {:states => {:parked => 'shutdown'}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_support_nil_state_key I18n.backend.store_translations(:en, { :mongoid => {:state_machines => {:states => {:nil => 'empty'}}} }) machine = StateMachine::Machine.new(@model) assert_equal 'empty', machine.state(nil).human_name end def test_should_allow_customized_event_key_scoped_to_class_and_machine I18n.backend.store_translations(:en, { :mongoid => {:state_machines => {:'mongoid_test/foo' => {:state => {:events => {:park => 'stop'}}}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_scoped_to_class I18n.backend.store_translations(:en, { :mongoid => {:state_machines => {:'mongoid_test/foo' => {:events => {:park => 'stop'}}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_scoped_to_machine I18n.backend.store_translations(:en, { :mongoid => {:state_machines => {:state => {:events => {:park => 'stop'}}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_unscoped I18n.backend.store_translations(:en, { :mongoid => {:state_machines => {:events => {:park => 'stop'}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_only_add_locale_once_in_load_path assert_equal 1, I18n.load_path.select {|path| path =~ %r{mongoid/locale\.rb$}}.length # Create another Mongoid model that will triger the i18n feature new_model assert_equal 1, I18n.load_path.select {|path| path =~ %r{mongoid/locale\.rb$}}.length end def test_should_add_locale_to_beginning_of_load_path @original_load_path = I18n.load_path I18n.backend = I18n::Backend::Simple.new app_locale = File.dirname(__FILE__) + '/../../files/en.yml' default_locale = File.dirname(__FILE__) + '/../../../lib/state_machine/integrations/mongoid/locale.rb' I18n.load_path = [app_locale] StateMachine::Machine.new(@model) assert_equal [default_locale, app_locale].map {|path| File.expand_path(path)}, I18n.load_path.map {|path| File.expand_path(path)} ensure I18n.load_path = @original_load_path end def test_should_prefer_other_locales_first @original_load_path = I18n.load_path I18n.backend = I18n::Backend::Simple.new I18n.load_path = [File.dirname(__FILE__) + '/../../files/en.yml'] machine = StateMachine::Machine.new(@model) machine.state :parked, :idling machine.event :ignite record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot transition'], record.errors.full_messages ensure I18n.load_path = @original_load_path end end end state-machine-1.2.0/test/unit/integrations/data_mapper_test.rb0000644000175000017500000020031412305405267024123 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../../test_helper') require 'dm-core' require 'dm-core/version' unless defined?(DataMapper::VERSION) require 'dm-observer' if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.3') require 'dm-migrations' end # Establish database connection DataMapper.setup(:default, 'sqlite3::memory:') DataObjects::Sqlite3.logger = DataObjects::Logger.new("#{File.dirname(__FILE__)}/../../data_mapper.log", :debug) module DataMapperTest class BaseTestCase < Test::Unit::TestCase def default_test end def teardown super @resources.uniq.each {|resource| DataMapperTest.send(:remove_const, resource)} if instance_variable_defined?('@resources') end protected # Creates a new DataMapper resource (and the associated table) def new_resource(create_table = :foo, &block) base_table_name = create_table || :foo name = base_table_name.to_s.capitalize table_name = "#{base_table_name}_#{rand(1000000)}" resource = Class.new DataMapperTest.send(:remove_const, name) if DataMapperTest.const_defined?(name) DataMapperTest.const_set(name, resource) (@resources ||= []) << name resource.class_eval do include DataMapper::Resource storage_names[:default] = table_name.to_s property :id, resource.class_eval('Serial') property :state, String end resource.class_eval(&block) if block_given? resource.auto_migrate! if create_table resource end # Creates a new DataMapper observer def new_observer(resource, &block) observer = Class.new do include DataMapper::Observer end observer.observe(resource) observer.class_eval(&block) if block_given? observer end end class IntegrationTest < BaseTestCase def test_should_have_an_integration_name assert_equal :data_mapper, StateMachine::Integrations::DataMapper.integration_name end def test_should_be_available assert StateMachine::Integrations::DataMapper.available? end def test_should_match_if_class_includes_data_mapper assert StateMachine::Integrations::DataMapper.matches?(new_resource) end def test_should_not_match_if_class_does_not_include_data_mapper assert !StateMachine::Integrations::DataMapper.matches?(Class.new) end def test_should_have_defaults assert_equal({:action => :save, :use_transactions => false}, StateMachine::Integrations::DataMapper.defaults) end def test_should_not_have_a_locale_path assert_nil StateMachine::Integrations::DataMapper.locale_path end end class MachineWithoutDatabaseTest < BaseTestCase def setup @resource = new_resource(false) do # Simulate the database not being available entirely def self.repository raise DataObjects::SyntaxError end end end def test_should_allow_machine_creation assert_nothing_raised { StateMachine::Machine.new(@resource) } end end class MachineUnmigratedTest < BaseTestCase def setup @resource = new_resource(false) end def test_should_allow_machine_creation assert_nothing_raised { StateMachine::Machine.new(@resource) } end end class MachineWithoutPropertyTest < BaseTestCase def setup @resource = new_resource StateMachine::Machine.new(@resource, :status) end def test_should_define_field_with_string_type property = @resource.properties.detect {|p| p.name == :status} assert_not_nil property if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('1.0.0') assert_instance_of DataMapper::Property::String, property else assert_equal String, property.type end end end class MachineWithPropertyTest < BaseTestCase def setup @resource = new_resource do property :status, Integer end StateMachine::Machine.new(@resource, :status) end def test_should_not_redefine_field property = @resource.properties.detect {|p| p.name == :status} assert_not_nil property if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('1.0.0') assert_instance_of DataMapper::Property::Integer, property else assert_equal Integer, property.type end end end class MachineByDefaultTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource) end def test_should_use_save_as_action assert_equal :save, @machine.action end def test_should_not_use_transactions assert_equal false, @machine.use_transactions end def test_should_not_have_any_before_callbacks assert_equal 0, @machine.callbacks[:before].size end def test_should_not_have_any_after_callbacks assert_equal 0, @machine.callbacks[:after].size end end class MachineWithStatesTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource) @machine.state :first_gear end def test_should_humanize_name assert_equal 'first gear', @machine.state(:first_gear).human_name end end class MachineWithStaticInitialStateTest < BaseTestCase def setup @resource = new_resource(:vehicle) do attr_accessor :value end @machine = StateMachine::Machine.new(@resource, :initial => :parked) end def test_should_set_initial_state_on_created_object record = @resource.new assert_equal 'parked', record.state end def test_should_set_initial_state_with_nil_attributes @resource.class_eval do def attributes=(attributes) super(attributes || {}) end end record = @resource.new(nil) assert_equal 'parked', record.state end def test_should_still_set_attributes record = @resource.new(:value => 1) assert_equal 1, record.value end def test_should_not_allow_initialize_blocks block_args = nil @resource.new do |*args| block_args = args end assert_nil block_args end def test_should_set_initial_state_before_setting_attributes @resource.class_eval do attr_accessor :state_during_setter remove_method :value= define_method(:value=) do |value| self.state_during_setter = state end end record = @resource.new(:value => 1) assert_equal 'parked', record.state_during_setter end def test_should_not_set_initial_state_after_already_initialized record = @resource.new(:value => 1) assert_equal 'parked', record.state record.state = 'idling' record.attributes = {} assert_equal 'idling', record.state end def test_should_persist_initial_state record = @resource.new record.save record.reload assert_equal 'parked', record.state end def test_should_persist_initial_state_on_dup record = @resource.create.dup record.save record.reload assert_equal 'parked', record.state end def test_should_use_stored_values_when_loading_from_database @machine.state :idling record = @resource.get(@resource.create(:state => 'idling').id) assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database_with_nil_state @machine.state nil record = @resource.get(@resource.create(:state => nil).id) assert_nil record.state end def test_should_use_stored_values_when_loading_for_many_association @machine.state :idling @resource.property :owner_id, Integer @resource.auto_migrate! owner_resource = new_resource(:owner) do has n, :vehicles end owner = owner_resource.create record = @resource.new(:state => 'idling') record.owner_id = owner.id record.save assert_equal 'idling', owner.vehicles[0].state end def test_should_use_stored_values_when_loading_for_one_association @machine.state :idling @resource.property :owner_id, Integer @resource.auto_migrate! owner_resource = new_resource(:owner) do has 1, :vehicle end owner = owner_resource.create record = @resource.new(:state => 'idling') record.owner_id = owner.id record.save assert_equal 'idling', owner.vehicle.state end def test_should_use_stored_values_when_loading_for_belongs_to_association @machine.state :idling driver_resource = new_resource(:driver) do belongs_to :vehicle end record = @resource.create(:state => 'idling') driver = driver_resource.create(:vehicle_id => record.id) assert_equal 'idling', driver.vehicle.state end end class MachineWithDynamicInitialStateTest < BaseTestCase def setup @resource = new_resource do attr_accessor :value end @machine = StateMachine::Machine.new(@resource, :initial => lambda {|object| :parked}) @machine.state :parked end def test_should_set_initial_state_on_created_object record = @resource.new assert_equal 'parked', record.state end def test_should_still_set_attributes record = @resource.new(:value => 1) assert_equal 1, record.value end def test_should_not_allow_initialize_blocks block_args = nil @resource.new do |*args| block_args = args end assert_nil block_args end def test_should_set_initial_state_after_setting_attributes @resource.class_eval do attr_accessor :state_during_setter remove_method :value= define_method(:value=) do |value| self.state_during_setter = state || 'nil' end end record = @resource.new(:value => 1) assert_equal 'nil', record.state_during_setter end def test_should_not_set_initial_state_after_already_initialized record = @resource.new(:value => 1) assert_equal 'parked', record.state record.state = 'idling' record.attributes = {} assert_equal 'idling', record.state end def test_should_persist_initial_state record = @resource.new record.save record.reload assert_equal 'parked', record.state end def test_should_persist_initial_state_on_dup record = @resource.create.dup record.save record.reload assert_equal 'parked', record.state end def test_should_use_stored_values_when_loading_from_database @machine.state :idling record = @resource.get(@resource.create(:state => 'idling').id) assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database_with_nil_state @machine.state nil record = @resource.get(@resource.create(:state => nil).id) assert_nil record.state end end class MachineWithEventsTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource) @machine.event :shift_up end def test_should_humanize_name assert_equal 'shift up', @machine.event(:shift_up).human_name end end class MachineWithSameColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @resource = new_resource do property :status, String, :default => 'parked' end @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked) @record = @resource.new end def test_should_use_machine_default assert_equal 'parked', @record.status end def test_should_not_generate_a_warning assert_no_match(/have defined a different default/, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithDifferentColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @resource = new_resource do property :status, String, :default => 'idling' end @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked) @record = @resource.new end def test_should_use_machine_default assert_equal 'parked', @record.status end def test_should_generate_a_warning assert_match(/Both DataMapperTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithDifferentIntegerColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @resource = new_resource do property :status, Integer, :default => 0 end @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked) @machine.state :parked, :value => 1 @record = @resource.new end def test_should_use_machine_default assert_equal 1, @record.status end def test_should_generate_a_warning assert_match(/Both DataMapperTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithConflictingPredicateTest < BaseTestCase def setup @resource = new_resource do def state?(*args) true end end @machine = StateMachine::Machine.new(@resource) @record = @resource.new end def test_should_not_define_attribute_predicate assert @record.state? end end class MachineWithConflictingStateNameTest < BaseTestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @resource = new_resource end def test_should_output_warning_with_same_machine_name @machine = StateMachine::Machine.new(@resource) @machine.state :state assert_match(/^Instance method "state\?" is already defined in DataMapperTest::Foo :state instance helpers, use generic helper instead.*\n$/, $stderr.string) end def test_should_not_output_warning_with_same_machine_attribute @machine = StateMachine::Machine.new(@resource, :public_state, :attribute => :state) @machine.state :state assert_no_match(/^Instance method "state\?" is already defined.*\n$/, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithColumnStateAttributeTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource, :initial => :parked) @machine.other_states(:idling) @record = @resource.new end def test_should_not_override_the_column_reader @record.attribute_set(:state, 'parked') assert_equal 'parked', @record.state end def test_should_not_override_the_column_writer @record.state = 'parked' assert_equal 'parked', @record.attribute_get(:state) end def test_should_have_an_attribute_predicate assert @record.respond_to?(:state?) end def test_should_raise_exception_for_predicate_without_parameters assert_raise(ArgumentError) { @record.state? } end def test_should_return_false_for_predicate_if_does_not_match_current_value assert !@record.state?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.state?(:parked) end def test_should_raise_exception_for_predicate_if_invalid_state_specified assert_raise(IndexError) { @record.state?(:invalid) } end end class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase def setup @resource = new_resource do def initialize # Skip attribute initialization @initialized_state_machines = true super end end @machine = StateMachine::Machine.new(@resource, :status, :initial => 'parked') @record = @resource.new end def test_should_define_a_new_property_for_the_attribute assert_not_nil @resource.properties[:status] end def test_should_define_a_reader_attribute_for_the_attribute assert @record.respond_to?(:status) end def test_should_define_a_writer_attribute_for_the_attribute assert @record.respond_to?(:status=) end def test_should_define_an_attribute_predicate assert @record.respond_to?(:status?) end end class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase def setup @resource = new_resource do attr_accessor :status end @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked) @machine.other_states(:idling) @record = @resource.new end def test_should_return_false_for_predicate_if_does_not_match_current_value assert !@record.status?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.status?(:parked) end def test_should_raise_exception_for_predicate_if_invalid_state_specified assert_raise(IndexError) { @record.status?(:invalid) } end def test_should_set_initial_state_on_created_object assert_equal 'parked', @record.status end end class MachineWithInitializedStateTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource, :initial => :parked) @machine.state :idling end def test_should_allow_nil_initial_state_when_static @machine.state nil record = @resource.new(:state => nil) assert_nil record.state end def test_should_allow_nil_initial_state_when_dynamic @machine.state nil @machine.initial_state = lambda {:parked} record = @resource.new(:state => nil) assert_nil record.state end def test_should_allow_different_initial_state_when_static record = @resource.new(:state => 'idling') assert_equal 'idling', record.state end def test_should_allow_different_initial_state_when_dynamic @machine.initial_state = lambda {:parked} record = @resource.new(:state => 'idling') assert_equal 'idling', record.state end if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.9.8') def test_should_raise_exception_if_protected resource = new_resource do protected :state= end machine = StateMachine::Machine.new(resource, :initial => :parked) machine.state :idling assert_raise(ArgumentError) { resource.new(:state => 'idling') } end end end class MachineMultipleTest < BaseTestCase def setup @resource = new_resource do property :status, String end @state_machine = StateMachine::Machine.new(@resource, :initial => :parked) @status_machine = StateMachine::Machine.new(@resource, :status, :initial => :idling) end def test_should_should_initialize_each_state record = @resource.new assert_equal 'parked', record.state assert_equal 'idling', record.status end end class MachineWithLoopbackTest < BaseTestCase def setup @resource = new_resource do property :updated_at, DateTime # Simulate dm-timestamps before :update do return unless dirty? self.updated_at = DateTime.now end end @machine = StateMachine::Machine.new(@resource, :initial => :parked) @machine.event :park @record = @resource.create(:updated_at => Time.now - 1) @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @timestamp = @record.updated_at @transition.perform end def test_should_not_update_record assert_equal @timestamp, @record.updated_at end end class MachineWithDirtyAttributesTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @resource.create @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal({@resource.properties[:state] => 'idling'}, @record.dirty_attributes) end def test_should_track_attribute_change if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0') assert_equal({@resource.properties[:state] => 'parked'}, @record.original_attributes) else assert_equal({:state => 'parked'}, @record.original_values) end end def test_should_not_reset_changes_on_multiple_transitions transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform(false) if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0') assert_equal({@resource.properties[:state] => 'parked'}, @record.original_attributes) else assert_equal({:state => 'parked'}, @record.original_values) end end def test_should_not_have_changes_when_loaded_from_database record = @resource.get(@record.id) assert record.dirty_attributes.empty? end end class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource, :initial => :parked) @machine.event :park @record = @resource.create @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform(false) end def test_should_not_include_state_in_changed_attributes assert_equal({}, @record.dirty_attributes) end end class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase def setup @resource = new_resource do property :status, String end @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @resource.create @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal({@resource.properties[:status] => 'idling'}, @record.dirty_attributes) end def test_should_track_attribute_change if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0') assert_equal({@resource.properties[:status] => 'parked'}, @record.original_attributes) else assert_equal({:status => 'parked'}, @record.original_values) end end def test_should_not_reset_changes_on_multiple_transitions transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform(false) if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0') assert_equal({@resource.properties[:status] => 'parked'}, @record.original_attributes) else assert_equal({:status => 'parked'}, @record.original_values) end end end class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase def setup @resource = new_resource do property :status, String end @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked) @machine.event :park @record = @resource.create @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform(false) end def test_should_not_include_state_in_changed_attributes assert_equal({}, @record.dirty_attributes) end end class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource, :initial => :parked) @machine.event :ignite @record = @resource.create @record.state_event = 'ignite' end def test_should_not_include_state_in_changed_attributes assert_equal({}, @record.dirty_attributes) end def test_should_not_track_attribute_change if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0') assert_equal({}, @record.original_attributes) else assert_equal({}, @record.original_values) end end end class MachineWithoutTransactionsTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource, :use_transactions => false) end def test_should_not_rollback_transaction_if_false @machine.within_transaction(@resource.new) do @resource.create false end assert_equal 1, @resource.all.size end def test_should_not_rollback_transaction_if_true @machine.within_transaction(@resource.new) do @resource.create true end assert_equal 1, @resource.all.size end end begin if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.3') require 'dm-transactions' end class MachineWithTransactionsTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource, :use_transactions => true) end def test_should_rollback_transaction_if_false @machine.within_transaction(@resource.new) do @resource.create false end assert_equal 0, @resource.all.size end def test_should_not_rollback_transaction_if_true @machine.within_transaction(@resource.new) do @resource.create true end assert_equal 1, @resource.all.size end end rescue LoadError $stderr.puts "Skipping DataMapper Transaction tests." end class MachineWithCallbacksTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource) @machine.state :parked, :idling @machine.event :ignite @record = @resource.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) end def test_should_run_before_callbacks called = false @machine.before_transition {called = true} @transition.perform assert called end def test_should_pass_transition_to_before_callbacks_with_one_argument transition = nil @machine.before_transition {|arg| transition = arg} @transition.perform assert_equal @transition, transition end def test_should_pass_transition_to_before_callbacks_with_multiple_arguments callback_args = nil @machine.before_transition {|*args| callback_args = args} @transition.perform assert_equal [@transition], callback_args end def test_should_run_before_callbacks_within_the_context_of_the_record context = nil @machine.before_transition {context = self} @transition.perform assert_equal @record, context end def test_should_run_after_callbacks called = false @machine.after_transition {called = true} @transition.perform assert called end def test_should_pass_transition_to_after_callbacks_with_multiple_arguments callback_args = nil @machine.after_transition {|*args| callback_args = args} @transition.perform assert_equal [@transition], callback_args end def test_should_run_after_callbacks_with_the_context_of_the_record context = nil @machine.after_transition {context = self} @transition.perform assert_equal @record, context end def test_should_run_around_callbacks before_called = false after_called = false ensure_called = 0 @machine.around_transition do |block| before_called = true begin block.call ensure ensure_called += 1 end after_called = true end @transition.perform assert before_called assert after_called assert_equal ensure_called, 1 end def test_should_run_around_callbacks_with_the_context_of_the_record context = nil @machine.around_transition {|block| context = self; block.call} @transition.perform assert_equal @record, context end def test_should_allow_symbolic_callbacks callback_args = nil klass = class << @record; self; end klass.send(:define_method, :after_ignite) do |*args| callback_args = args end @machine.before_transition(:after_ignite) @transition.perform assert_equal [@transition], callback_args end def test_should_allow_string_callbacks class << @record attr_reader :callback_result end @machine.before_transition('@callback_result = [1, 2, 3]') @transition.perform assert_equal [1, 2, 3], @record.callback_result end def test_should_run_in_expected_order # Avoid Ruby 2.0.0 stack too deep issues @resource.class_eval do def valid?(*) super end end expected = [ :before_transition, :before_validation, :after_validation, :before_save, :before_create, :after_create, :after_save, :after_transition ] callbacks = [] @resource.before(:valid?) { callbacks << :before_validation } @resource.after(:valid?) { callbacks << :after_validation } @resource.before(:save) { callbacks << :before_save } @resource.before(:create) { callbacks << :before_create } @resource.after(:create) { callbacks << :after_create } @resource.after(:save) { callbacks << :after_save } @machine.before_transition { callbacks << :before_transition } @machine.after_transition { callbacks << :after_transition } @transition.perform assert_equal expected, callbacks end end class MachineWithFailedBeforeCallbacksTest < BaseTestCase def setup callbacks = [] @resource = new_resource @machine = StateMachine::Machine.new(@resource) @machine.state :parked, :idling @machine.event :ignite @machine.before_transition {callbacks << :before_1; throw :halt} @machine.before_transition {callbacks << :before_2} @machine.after_transition {callbacks << :after} @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after} @record = @resource.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform @callbacks = callbacks end def test_should_not_be_successful assert !@result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_run_action assert @record.respond_to?(:new?) ? @record.new? : @record.new_record? end def test_should_not_run_further_callbacks assert_equal [:before_1], @callbacks end end if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('1.0.0') class MachineNestedActionTest < BaseTestCase def setup @callbacks = [] @resource = new_resource @machine = StateMachine::Machine.new(@resource) @machine.event :ignite do transition :parked => :idling end @record = @resource.new(:state => 'parked') end def test_should_allow_transition_prior_to_creation_if_skipping_action record = @record @resource.before(:create) { record.ignite } result = @record.save assert_equal true, result assert_equal "idling", @record.state @record.reload assert_equal "idling", @record.state end def test_should_not_allow_transition_after_creation record = @record @resource.after(:create) { record.ignite(false) } result = @record.save assert_equal false, result end end end class MachineWithFailedActionTest < BaseTestCase def setup @resource = new_resource do before(:create) { throw :halt } end @machine = StateMachine::Machine.new(@resource) @machine.state :parked, :idling @machine.event :ignite callbacks = [] @machine.before_transition {callbacks << :before} @machine.after_transition {callbacks << :after} @machine.after_failure {callbacks << :after_failure} @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after} @record = @resource.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform @callbacks = callbacks end def test_should_not_be_successful assert !@result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_save_record assert @record.respond_to?(:new?) ? @record.new? : @record.new_record? end def test_should_run_before_callbacks_and_after_callbacks_with_failures assert_equal [:before, :around_before, :after_failure], @callbacks end end class MachineWithFailedAfterCallbacksTest < BaseTestCase def setup callbacks = [] @resource = new_resource @machine = StateMachine::Machine.new(@resource) @machine.state :parked, :idling @machine.event :ignite @machine.after_transition {callbacks << :after_1; throw :halt} @machine.after_transition {callbacks << :after_2} @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after} @record = @resource.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform @callbacks = callbacks end def test_should_be_successful assert @result end def test_should_change_current_state assert_equal 'idling', @record.state end def test_should_save_record assert !(@record.respond_to?(:new?) ? @record.new? : @record.new_record?) end def test_should_not_run_further_after_callbacks assert_equal [:around_before, :around_after, :after_1], @callbacks end end begin require 'dm-validations' class MachineWithValidationsTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource) @machine.state :parked @record = @resource.new end def test_should_invalidate_using_errors @record.state = 'parked' @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']]) assert_equal ['cannot transition via "park"'], @record.errors.on(:state) end def test_should_auto_prefix_custom_attributes_on_invalidation @machine.invalidate(@record, :event, :invalid) assert_equal ['is invalid'], @record.errors.on(:state_event) end def test_should_clear_errors_on_reset @record.state = 'parked' @record.errors.add(:state, 'is invalid') @machine.reset(@record) assert_nil @record.errors.on(:id) end def test_should_be_valid_if_state_is_known @record.state = 'parked' assert @record.valid? end def test_should_not_be_valid_if_state_is_unknown @record.state = 'invalid' assert !@record.valid? assert_equal ['is invalid'], @record.errors.on(:state) end end class MachineWithValidationsAndCustomAttributeTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource, :status, :attribute => :state) @machine.state :parked @record = @resource.new end def test_should_add_validation_errors_to_custom_attribute @record.state = 'invalid' assert !@record.valid? assert_equal ['is invalid'], @record.errors.on(:state) @record.state = 'parked' assert @record.valid? end end class MachineErrorsTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource) @record = @resource.new end def test_should_be_able_to_describe_current_errors @record.errors.add(:id, 'cannot be blank') @record.errors.add(:state, 'is invalid') assert_equal ['id cannot be blank', 'state is invalid'], @machine.errors_for(@record).split(', ').sort end def test_should_describe_as_halted_with_no_errors assert_equal 'Transition halted', @machine.errors_for(@record) end end class MachineWithStateDrivenValidationsTest < BaseTestCase def setup @resource = resource = new_resource do attr_accessor :seatbelt end @machine = StateMachine::Machine.new(@resource) @machine.state :first_gear, :second_gear do if resource.respond_to?(:validates_presence_of) validates_presence_of :seatbelt else validates_present :seatbelt end end @machine.other_states :parked end def test_should_be_valid_if_validation_fails_outside_state_scope record = @resource.new(:state => 'parked', :seatbelt => nil) assert record.valid? end def test_should_be_invalid_if_validation_fails_within_state_scope record = @resource.new(:state => 'first_gear', :seatbelt => nil) assert !record.valid? end def test_should_be_valid_if_validation_succeeds_within_state_scope record = @resource.new(:state => 'second_gear', :seatbelt => true) assert record.valid? end end # See README caveats if Gem::Version.new(DataMapper::VERSION) > Gem::Version.new('0.9.6') class MachineWithEventAttributesOnValidationTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource) @machine.event :ignite do transition :parked => :idling end @record = @resource.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert !@record.valid? assert_equal ['is invalid'], @record.errors.full_messages end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert !@record.valid? assert_equal ['cannot transition when idling'], @record.errors.full_messages end def test_should_be_successful_if_event_has_transition assert @record.valid? end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.valid? assert ran_callback end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } begin @record.valid? rescue ArgumentError raise if StateMachine::Transition.pause_supported? end assert ran_callback end def test_should_persist_new_state @record.valid? assert_equal 'idling', @record.state end def test_should_not_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.valid? assert !ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails @resource.class_eval do attr_accessor :seatbelt if respond_to?(:validates_presence_of) validates_presence_of :seatbelt else validates_present :seatbelt end end ran_callback = false @machine.after_transition { ran_callback = true } @record.valid? assert !ran_callback end def test_should_run_failure_callbacks_if_validation_fails @resource.class_eval do attr_accessor :seatbelt if respond_to?(:validates_presence_of) validates_presence_of :seatbelt else validates_present :seatbelt end end ran_callback = false @machine.after_failure { ran_callback = true } @record.valid? assert ran_callback end def test_should_not_run_around_callbacks_after_yield ran_callback = [false] @machine.around_transition {|block| block.call; ran_callback[0] = true } begin @record.valid? rescue ArgumentError raise if StateMachine::Transition.pause_supported? end assert !ran_callback[0] end def test_should_not_run_around_callbacks_after_yield_with_failures_disabled_if_validation_fails @resource.class_eval do attr_accessor :seatbelt if respond_to?(:validates_presence_of) validates_presence_of :seatbelt else validates_present :seatbelt end end ran_callback = [false] @machine.around_transition {|block| block.call; ran_callback[0] = true } begin @record.valid? rescue ArgumentError raise if StateMachine::Transition.pause_supported? end assert !ran_callback[0] end def test_should_not_run_before_transitions_within_transaction @machine.before_transition { self.class.create; throw :halt } assert !@record.valid? assert_equal 1, @resource.all.size end end class MachineWithEventAttributesOnSaveTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource) @machine.event :ignite do transition :parked => :idling end @record = @resource.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert !@record.save end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert !@record.save end def test_should_be_successful_if_event_has_transition assert_equal true, @record.save end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.save assert ran_callback end def test_should_run_before_callbacks_once before_count = 0 @machine.before_transition { before_count += 1 } @record.save assert_equal 1, before_count end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } @record.save assert ran_callback end def test_should_run_around_callbacks_before_yield_once around_before_count = 0 @machine.around_transition {|block| around_before_count += 1; block.call } @record.save assert_equal 1, around_before_count end def test_should_persist_new_state @record.save assert_equal 'idling', @record.state end def test_should_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.save assert ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_fails @resource.before(:create) { throw :halt } ran_callback = false @machine.after_transition { ran_callback = true } @record.save assert !ran_callback end def test_should_run_failure_callbacks_if_fails @resource.before(:create) { throw :halt } ran_callback = false @machine.after_failure { ran_callback = true } @record.save assert ran_callback end def test_should_not_run_around_callbacks_with_failures_disabled_if_fails @resource.before(:create) { throw :halt } ran_callback = [false] @machine.around_transition {|block| block.call; ran_callback[0] = true } @record.save assert !ran_callback[0] end def test_should_run_around_callbacks_after_yield ran_callback = [false] @machine.around_transition {|block| block.call; ran_callback[0] = true } @record.save assert ran_callback[0] end def test_should_not_run_before_transitions_within_transaction @machine.before_transition { self.class.create; throw :halt } assert_equal false, @record.save assert_equal 1, @resource.all.size end def test_should_not_run_after_transitions_within_transaction @machine.before_transition { self.class.create; throw :halt } assert_equal false, @record.save assert_equal 1, @resource.all.size end def test_should_not_run_around_transition_within_transaction @machine.around_transition { self.class.create; throw :halt } assert_equal false, @record.save assert_equal 1, @resource.all.size end def test_should_allow_additional_transitions_to_new_state_in_after_transitions @machine.event :park do transition :idling => :parked end @machine.after_transition(:on => :ignite) { park } @record.save assert_equal 'parked', @record.state @record.reload assert_equal 'parked', @record.state end def test_should_allow_additional_transitions_to_previous_state_in_after_transitions @machine.event :shift_up do transition :idling => :first_gear end @machine.after_transition(:on => :ignite) { shift_up } @record.save assert_equal 'first_gear', @record.state @record.reload assert_equal 'first_gear', @record.state end end end if Gem::Version.new(DataMapper::VERSION) >= Gem::Version.new('0.10.0') class MachineWithEventAttributesOnSaveBangTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource) @machine.event :ignite do transition :parked => :idling end @record = @resource.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert !@record.save! end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert !@record.save! end def test_should_be_successful_if_event_has_transition assert_equal true, @record.save! end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.save! assert ran_callback end def test_should_run_before_callbacks_once before_count = 0 @machine.before_transition { before_count += 1 } @record.save! assert_equal 1, before_count end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } @record.save! assert ran_callback end def test_should_run_around_callbacks_before_yield_once around_before_count = 0 @machine.around_transition {|block| around_before_count += 1; block.call } @record.save! assert_equal 1, around_before_count end def test_should_persist_new_state @record.save! assert_equal 'idling', @record.state end def test_should_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.save! assert ran_callback end def test_should_run_around_callbacks_after_yield ran_callback = [false] @machine.around_transition {|block| block.call; ran_callback[0] = true } @record.save! assert ran_callback[0] end end end if Gem::Version.new(DataMapper::VERSION) > Gem::Version.new('0.9.6') class MachineWithEventAttributesOnCustomActionTest < BaseTestCase def setup @superclass = new_resource do def persist save end end @resource = Class.new(@superclass) @machine = StateMachine::Machine.new(@resource, :action => :persist) @machine.event :ignite do transition :parked => :idling end @record = @resource.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_not_transition_on_valid? @record.valid? assert_equal 'parked', @record.state end def test_should_not_transition_on_save @record.save assert_equal 'parked', @record.state end def test_should_transition_on_custom_action @record.persist assert_equal 'idling', @record.state end end end rescue LoadError $stderr.puts "Skipping DataMapper Validation tests." end class MachineWithObserversTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource) @machine.state :parked, :idling @machine.event :ignite @record = @resource.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) end def test_should_provide_matcher_helpers matchers = [] new_observer(@resource) do matchers = [all, any, same] end assert_equal [StateMachine::AllMatcher.instance, StateMachine::AllMatcher.instance, StateMachine::LoopbackMatcher.instance], matchers end def test_should_call_before_transition_callback_if_requirements_match called = false new_observer(@resource) do before_transition :from => :parked do called = true end end @transition.perform assert called end def test_should_not_call_before_transition_callback_if_requirements_do_not_match called = false new_observer(@resource) do before_transition :from => :idling do called = true end end @transition.perform assert !called end def test_should_pass_transition_to_before_callbacks callback_args = nil new_observer(@resource) do before_transition do |*args| callback_args = args end end @transition.perform assert_equal [@transition], callback_args end def test_should_call_after_transition_callback_if_requirements_match called = false new_observer(@resource) do after_transition :from => :parked do called = true end end @transition.perform assert called end def test_should_not_call_after_transition_callback_if_requirements_do_not_match called = false new_observer(@resource) do after_transition :from => :idling do called = true end end @transition.perform assert !called end def test_should_pass_transition_to_after_callbacks callback_args = nil new_observer(@resource) do after_transition do |*args| callback_args = args end end @transition.perform assert_equal [@transition], callback_args end def test_should_call_around_transition_callback_if_requirements_match called = false new_observer(@resource) do around_transition :from => :parked do |block| called = true block.call end end @transition.perform assert called end def test_should_not_call_around_transition_callback_if_requirements_do_not_match called = false new_observer(@resource) do around_transition :from => :idling do |block| called = true block.call end end @transition.perform assert !called end def test_should_pass_transition_to_around_callbacks callback_args = nil new_observer(@resource) do around_transition do |*args| block = args.pop callback_args = args block.call end end @transition.perform assert_equal [@transition], callback_args end def test_should_call_failure_callback_if_requirements_match @resource.before(:create) { throw :halt } called = false new_observer(@resource) do after_transition_failure :on => :ignite do called = true end end @transition.perform assert called end def test_should_not_call_failure_callback_if_requirements_do_not_match @resource.before(:create) { throw :halt } called = false new_observer(@resource) do after_transition_failure :on => :park do called = true end end @transition.perform assert !called end def test_should_pass_transition_to_failure_callbacks @resource.before(:create) { throw :halt } callback_args = nil new_observer(@resource) do after_transition_failure do |*args| callback_args = args end end @transition.perform assert_equal [@transition], callback_args end def test_should_raise_exception_if_targeting_invalid_machine assert_raise(RUBY_VERSION < '1.9' ? IndexError : KeyError) do new_observer(@resource) do before_transition :invalid, :from => :parked do end end end end def test_should_allow_targeting_specific_machine @second_machine = StateMachine::Machine.new(@resource, :status, :namespace => 'alarm') @resource.auto_migrate! called_state = false called_status = false new_observer(@resource) do before_transition :state, :from => :parked do called_state = true end before_transition :status, :from => :parked do called_status = true end end @transition.perform assert called_state assert !called_status end def test_should_allow_targeting_multiple_specific_machines @second_machine = StateMachine::Machine.new(@resource, :status, :namespace => 'alarm') @second_machine.state :parked, :idling @second_machine.event :ignite @resource.auto_migrate! called_attribute = nil new_observer(@resource) do before_transition :state, :status, :from => :parked do |transition| called_attribute = transition.attribute end end @transition.perform assert_equal :state, called_attribute StateMachine::Transition.new(@record, @second_machine, :ignite, :parked, :idling).perform assert_equal :status, called_attribute end end class MachineWithMixedCallbacksTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource) @machine.state :parked, :idling @machine.event :ignite @record = @resource.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @notifications = notifications = [] # Create callbacks @machine.before_transition {notifications << :callback_before_transition} @machine.after_transition {notifications << :callback_after_transition} @machine.around_transition do |block| notifications << :callback_around_before_transition block.call notifications << :callback_around_after_transition end new_observer(@resource) do before_transition do notifications << :observer_before_transition end after_transition do notifications << :observer_after_transition end around_transition do |block| notifications << :observer_around_before_transition block.call notifications << :observer_around_after_transition end end @transition.perform end def test_should_invoke_callbacks_in_specific_order expected = [ :callback_before_transition, :callback_around_before_transition, :observer_before_transition, :observer_around_before_transition, :observer_around_after_transition, :callback_around_after_transition, :callback_after_transition, :observer_after_transition ] assert_equal expected, @notifications end end class MachineWithScopesTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource) @machine.state :parked, :first_gear @machine.state :idling, :value => lambda {'idling'} end def test_should_create_singular_with_scope assert @resource.respond_to?(:with_state) end def test_should_only_include_records_with_state_in_singular_with_scope parked = @resource.create :state => 'parked' @resource.create :state => 'idling' assert_equal [parked], @resource.with_state(:parked) end def test_should_create_plural_with_scope assert @resource.respond_to?(:with_states) end def test_should_only_include_records_with_states_in_plural_with_scope parked = @resource.create :state => 'parked' idling = @resource.create :state => 'idling' assert_equal [parked, idling], @resource.with_states(:parked, :idling) end def test_should_allow_lookup_by_string_name parked = @resource.create :state => 'parked' idling = @resource.create :state => 'idling' assert_equal [parked, idling], @resource.with_states('parked', 'idling') end def test_should_create_singular_without_scope assert @resource.respond_to?(:without_state) end def test_should_only_include_records_without_state_in_singular_without_scope parked = @resource.create :state => 'parked' idling = @resource.create :state => 'idling' assert_equal [parked], @resource.without_state(:idling) end def test_should_create_plural_without_scope assert @resource.respond_to?(:without_states) end def test_should_only_include_records_without_states_in_plural_without_scope parked = @resource.create :state => 'parked' idling = @resource.create :state => 'idling' first_gear = @resource.create :state => 'first_gear' assert_equal [parked, idling], @resource.without_states(:first_gear) end def test_should_allow_chaining_scopes parked = @resource.create :state => 'parked' idling = @resource.create :state => 'idling' assert_equal [idling], @resource.without_state(:parked).with_state(:idling) end end class MachineWithScopesAndOwnerSubclassTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource, :state) @subclass = Class.new(@resource) DataMapperTest.const_set('Bar', @subclass) @resources << 'Bar' @subclass.auto_migrate! @subclass_machine = @subclass.state_machine(:state) {} @subclass_machine.state :parked, :idling, :first_gear end def test_should_only_include_records_with_subclass_states_in_with_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' assert_equal [parked, idling], @subclass.with_states(:parked, :idling) end def test_should_only_include_records_without_subclass_states_in_without_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' first_gear = @subclass.create :state => 'first_gear' assert_equal [parked, idling], @subclass.without_states(:first_gear) end end class MachineWithComplexPluralizationScopesTest < BaseTestCase def setup @resource = new_resource @machine = StateMachine::Machine.new(@resource, :status) end def test_should_create_singular_with_scope assert @resource.respond_to?(:with_status) end def test_should_create_plural_with_scope assert @resource.respond_to?(:with_statuses) end end class MachineWithScopesAndJoinsTest < BaseTestCase def setup @company = new_resource(:company) @vehicle = new_resource(:vehicle) do property :company_id, Integer belongs_to :company end @company_machine = StateMachine::Machine.new(@company, :initial => :active) @vehicle_machine = StateMachine::Machine.new(@vehicle, :initial => :parked) @vehicle_machine.state :idling @ford = @company.create @mustang = @vehicle.create(:company => @ford) end def test_should_find_records_in_with_scope assert_equal [@mustang], @vehicle.with_states(:parked).all(Vehicle.company.state => 'active') end def test_should_find_records_in_without_scope assert_equal [@mustang], @vehicle.without_states(:idling).all(Vehicle.company.state => 'active') end end end state-machine-1.2.0/test/unit/integrations/mongo_mapper_test.rb0000644000175000017500000016722512305405267024346 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../../test_helper') require 'mongo_mapper' # Establish database connection MongoMapper.connection = Mongo::Connection.new('127.0.0.1', 27017, {:logger => Logger.new("#{File.dirname(__FILE__)}/../../mongo_mapper.log"), :slave_ok => true}) MongoMapper.database = 'test' module MongoMapperTest class BaseTestCase < Test::Unit::TestCase def default_test end def teardown if @table_names MongoMapper.database.collections.each {|c| c.drop if @table_names.include?(c.name)} end end protected # Creates a new MongoMapper model (and the associated table) def new_model(name = :foo, &block) table_name = "#{name}_#{rand(1000000)}" @table_names ||= [] @table_names << table_name model = Class.new do (class << self; self; end).class_eval do define_method(:name) { "MongoMapperTest::#{name.to_s.capitalize}" } define_method(:to_s) { self.name } end end model.class_eval do include MongoMapper::Document set_collection_name(table_name) key :state, String end model.class_eval(&block) if block_given? model end end class IntegrationTest < BaseTestCase def test_should_have_an_integration_name assert_equal :mongo_mapper, StateMachine::Integrations::MongoMapper.integration_name end def test_should_be_available assert StateMachine::Integrations::MongoMapper.available? end def test_should_match_if_class_includes_mongo_mapper assert StateMachine::Integrations::MongoMapper.matches?(new_model) end def test_should_not_match_if_class_does_not_include_mongo_mapper assert !StateMachine::Integrations::MongoMapper.matches?(Class.new) end def test_should_have_defaults assert_equal({:action => :save}, StateMachine::Integrations::MongoMapper.defaults) end def test_should_have_a_locale_path assert_not_nil StateMachine::Integrations::MongoMapper.locale_path end end class MachineWithoutDatabaseTest < BaseTestCase def setup @model = new_model do # Simulate the database not being available entirely def self.connection raise Mongo::ConnectionFailure end end end def test_should_allow_machine_creation assert_nothing_raised { StateMachine::Machine.new(@model) } end end class MachineWithoutKeyTest < BaseTestCase def setup @model = new_model StateMachine::Machine.new(@model, :status) end def test_should_define_field_with_string_type key = @model.keys['status'] assert_not_nil key assert_equal String, key.type end end class MachineWithKeyTest < BaseTestCase def setup @model = new_model do key :status, Integer end StateMachine::Machine.new(@model, :status) end def test_should_not_redefine_field key = @model.keys['status'] assert_not_nil key assert_equal Integer, key.type end end class MachineByDefaultTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) end def test_should_use_save_as_action assert_equal :save, @machine.action end def test_should_not_have_any_before_callbacks assert_equal 0, @machine.callbacks[:before].size end def test_should_not_have_any_after_callbacks assert_equal 0, @machine.callbacks[:after].size end end class MachineWithStatesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :first_gear end def test_should_humanize_name assert_equal 'first gear', @machine.state(:first_gear).human_name end end class MachineWithStaticInitialStateTest < BaseTestCase def setup @model = new_model(:vehicle) do attr_accessor :value end @machine = StateMachine::Machine.new(@model, :initial => :parked) end def test_should_set_initial_state_on_created_object record = @model.new assert_equal 'parked', record.state end def test_should_set_initial_state_with_nil_attributes record = @model.new(nil) assert_equal 'parked', record.state end def test_should_still_set_attributes record = @model.new(:value => 1) assert_equal 1, record.value end def test_should_not_allow_initialize_blocks block_args = nil @model.new do |*args| block_args = args end assert_nil block_args end def test_should_set_initial_state_before_setting_attributes @model.class_eval do attr_accessor :state_during_setter remove_method :value= define_method(:value=) do |value| self.state_during_setter = state end end record = @model.new(:value => 1) assert_equal 'parked', record.state_during_setter end def test_should_not_set_initial_state_after_already_initialized record = @model.new(:value => 1) assert_equal 'parked', record.state record.state = 'idling' record.attributes = {} assert_equal 'idling', record.state end def test_should_persist_initial_state record = @model.new record.save record = @model.find(record.id) assert_equal 'parked', record.state end def test_should_persist_initial_state_on_dup record = @model.create.dup record.save record = @model.find(record.id) assert_equal 'parked', record.state end def test_should_use_stored_values_when_loading_from_database @machine.state :idling record = @model.find(@model.create(:state => 'idling').id) assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database_with_nil_state @machine.state nil record = @model.find(@model.create(:state => nil).id) assert_nil record.state end if defined?(MongoMapper::Version) && MongoMapper::Version !~ /^0\.[5-8]\./ def test_should_use_stored_values_when_loading_for_many_association @machine.state :idling @model.belongs_to :owner, :class_name => 'MongoMapperTest::Owner' MongoMapperTest.const_set('Vehicle', @model) owner_model = new_model(:owner) do many :vehicles, :class_name => 'MongoMapperTest::Vehicle' end MongoMapperTest.const_set('Owner', owner_model) owner = owner_model.create record = @model.create(:state => 'idling', :owner_id => owner.id) assert_equal 'idling', owner.vehicles[0].state end def test_should_use_stored_values_when_loading_for_one_association @machine.state :idling @model.belongs_to :owner, :class_name => 'MongoMapperTest::Owner' MongoMapperTest.const_set('Vehicle', @model) owner_model = new_model(:owner) do one :vehicle, :class_name => 'MongoMapperTest::Vehicle' end MongoMapperTest.const_set('Owner', owner_model) owner = owner_model.create record = @model.create(:state => 'idling', :owner_id => owner.id) owner.reload assert_equal 'idling', owner.vehicle.state end def test_should_use_stored_values_when_loading_for_belongs_to_association @machine.state :idling MongoMapperTest.const_set('Vehicle', @model) driver_model = new_model(:driver) do belongs_to :vehicle, :class_name => 'MongoMapperTest::Vehicle' end MongoMapperTest.const_set('Driver', driver_model) record = @model.create(:state => 'idling') driver = driver_model.create(:vehicle_id => record.id) assert_equal 'idling', driver.vehicle.state end end def teardown MongoMapperTest.class_eval do remove_const('Vehicle') if defined?(MongoMapperTest::Vehicle) remove_const('Owner') if defined?(MongoMapperTest::Owner) remove_const('Driver') if defined?(MongoMapperTest::Driver) end super end end class MachineWithDynamicInitialStateTest < BaseTestCase def setup @model = new_model do attr_accessor :value end @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked}) @machine.state :parked end def test_should_set_initial_state_on_created_object record = @model.new assert_equal 'parked', record.state end def test_should_still_set_attributes record = @model.new(:value => 1) assert_equal 1, record.value end def test_should_not_allow_initialize_blocks block_args = nil @model.new do |*args| block_args = args end assert_nil block_args end def test_should_set_initial_state_after_setting_attributes @model.class_eval do attr_accessor :state_during_setter remove_method :value= define_method(:value=) do |value| self.state_during_setter = state || 'nil' end end record = @model.new(:value => 1) assert_equal 'nil', record.state_during_setter end def test_should_not_set_initial_state_after_already_initialized record = @model.new(:value => 1) assert_equal 'parked', record.state record.state = 'idling' record.attributes = {} assert_equal 'idling', record.state end def test_should_persist_initial_state record = @model.new record.save record = @model.find(record.id) assert_equal 'parked', record.state end def test_should_persist_initial_state_on_dup record = @model.create.dup record.save record = @model.find(record.id) assert_equal 'parked', record.state end def test_should_use_stored_values_when_loading_from_database @machine.state :idling record = @model.find(@model.create(:state => 'idling').id) assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database_with_nil_state @machine.state nil record = @model.find(@model.create(:state => nil).id) assert_nil record.state end end class MachineWithEventsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :shift_up end def test_should_humanize_name assert_equal 'shift up', @machine.event(:shift_up).human_name end end class MachineWithSameColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do key :status, String, :default => 'parked' end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @record = @model.new end def test_should_use_machine_default assert_equal 'parked', @record.status end def test_should_not_generate_a_warning assert_no_match(/have defined a different default/, $stderr.string) end def teardown $stderr = @original_stderr end end class MachineWithDifferentColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do key :status, String, :default => 'idling' end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @record = @model.new end def test_should_use_machine_default assert_equal 'parked', @record.status end def test_should_generate_a_warning assert_match(/Both MongoMapperTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string) end def teardown $stderr = @original_stderr end end class MachineWithDifferentIntegerColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do key :status, Integer, :default => 0 end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.state :parked, :value => 1 @record = @model.new end def test_should_use_machine_default assert_equal 1, @record.status end def test_should_generate_a_warning assert_match(/Both MongoMapperTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string) end def teardown $stderr = @original_stderr end end class MachineWithConflictingPredicateTest < BaseTestCase def setup @model = new_model do def state?(*args) true end end @machine = StateMachine::Machine.new(@model) @record = @model.new end def test_should_not_define_attribute_predicate assert @record.state? end end class MachineWithConflictingStateNameTest < BaseTestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @model = new_model end def test_should_output_warning_with_same_machine_name @machine = StateMachine::Machine.new(@model) @machine.state :state assert_match(/^Instance method "state\?" is already defined in .*, use generic helper instead.*\n$/, $stderr.string) end def test_should_output_warning_with_same_machine_attribute @machine = StateMachine::Machine.new(@model, :public_state, :attribute => :state) @machine.state :state assert_match(/^Instance method "state\?" is already defined in .*, use generic helper instead.*\n$/, $stderr.string) end def teardown $stderr = @original_stderr end end class MachineWithColumnStateAttributeTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_not_override_the_column_reader @record[:state] = 'parked' assert_equal 'parked', @record.state end def test_should_not_override_the_column_writer @record.state = 'parked' assert_equal 'parked', @record[:state] end def test_should_have_an_attribute_predicate assert @record.respond_to?(:state?) end def test_should_test_for_existence_on_predicate_without_parameters assert @record.state? @record.state = nil assert !@record.state? end def test_should_return_false_for_predicate_if_does_not_match_current_value assert !@record.state?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.state?(:parked) end def test_should_raise_exception_for_predicate_if_invalid_state_specified assert_raise(IndexError) { @record.state?(:invalid) } end end class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase def setup @model = new_model do def initialize # Skip attribute initialization @initialized_state_machines = true super end end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_define_a_new_key_for_the_attribute assert_not_nil @model.keys['status'] end def test_should_define_a_reader_attribute_for_the_attribute assert @record.respond_to?(:status) end def test_should_define_a_writer_attribute_for_the_attribute assert @record.respond_to?(:status=) end def test_should_define_an_attribute_predicate assert @record.respond_to?(:status?) end end class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase def setup @model = new_model do attr_accessor :status end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_return_false_for_predicate_if_does_not_match_current_value assert !@record.status?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.status?(:parked) end def test_should_raise_exception_for_predicate_if_invalid_state_specified assert_raise(IndexError) { @record.status?(:invalid) } end def test_should_set_initial_state_on_created_object assert_equal 'parked', @record.status end end if !defined?(MongoMapper::Version) || MongoMapper::Version =~ /^0\.[5-8]\./ class MachineWithAliasedAttributeTest < BaseTestCase def setup @model = new_model do alias_attribute :vehicle_status, :state end @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status) @machine.state :parked @record = @model.new end def test_should_check_custom_attribute_for_predicate @record.vehicle_status = nil assert !@record.status?(:parked) @record.vehicle_status = 'parked' assert @record.status?(:parked) end end end class MachineWithCustomAttributeTest < BaseTestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @model = new_model @machine = StateMachine::Machine.new(@model, :public_state, :attribute => :state) @record = @model.new end def test_should_not_delegate_attribute_predicate_with_different_attribute assert_raise(ArgumentError) { @record.public_state? } end def teardown $stderr = @original_stderr end end class MachineWithInitializedStateTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.state :idling end def test_should_allow_nil_initial_state_when_static @machine.state nil record = @model.new(:state => nil) assert_nil record.state end def test_should_allow_nil_initial_state_when_dynamic @machine.state nil @machine.initial_state = lambda {:parked} record = @model.new(:state => nil) assert_nil record.state end def test_should_allow_different_initial_state_when_static record = @model.new(:state => 'idling') assert_equal 'idling', record.state end def test_should_allow_different_initial_state_when_dynamic @machine.initial_state = lambda {:parked} record = @model.new(:state => 'idling') assert_equal 'idling', record.state end if defined?(MongoMapper::Plugins::Protected) def test_should_use_default_state_if_protected @model.class_eval do attr_protected :state end record = @model.new(:state => 'idling') assert_equal 'parked', record.state end end end class MachineMultipleTest < BaseTestCase def setup @model = new_model do key :status, String end @state_machine = StateMachine::Machine.new(@model, :initial => :parked) @status_machine = StateMachine::Machine.new(@model, :status, :initial => :idling) end def test_should_should_initialize_each_state record = @model.new assert_equal 'parked', record.state assert_equal 'idling', record.status end end class MachineWithLoopbackTest < BaseTestCase def setup @model = new_model do key :updated_at, Time before_update do |record| record.updated_at = Time.now end end @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create(:updated_at => Time.now - 1) @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @timestamp = @record.updated_at @transition.perform end def test_should_update_record assert_not_equal @timestamp, @record.updated_at end end class MachineWithDirtyAttributesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal %w(state), @record.changed end def test_should_track_attribute_change assert_equal %w(parked idling), @record.changes['state'] end def test_should_not_reset_changes_on_multiple_transitions transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform(false) assert_equal %w(parked idling), @record.changes['state'] end def test_should_not_have_changes_when_loaded_from_database record = @model.find(@record.id) assert !record.changed? end end class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform(false) end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_changes assert_equal nil, @record.changes['state'] end end class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase def setup @model = new_model do key :status, String end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal %w(status), @record.changed end def test_should_track_attribute_change assert_equal %w(parked idling), @record.changes['status'] end def test_should_not_reset_changes_on_multiple_transitions transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform(false) assert_equal %w(parked idling), @record.changes['status'] end end class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model do key :status, String end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.event :park @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform(false) end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_changes assert_equal nil, @record.changes['status'] end end class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :ignite @record = @model.create @record.state_event = 'ignite' end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_change assert_equal nil, @record.changes['state'] end end class MachineWithCallbacksTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.other_states :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) end def test_should_run_before_callbacks called = false @machine.before_transition {called = true} @transition.perform assert called end def test_should_pass_record_to_before_callbacks_with_one_argument record = nil @machine.before_transition {|arg| record = arg} @transition.perform assert_equal @record, record end def test_should_pass_record_and_transition_to_before_callbacks_with_multiple_arguments callback_args = nil @machine.before_transition {|*args| callback_args = args} @transition.perform assert_equal [@record, @transition], callback_args end def test_should_run_before_callbacks_outside_the_context_of_the_record context = nil @machine.before_transition {context = self} @transition.perform assert_equal self, context end def test_should_run_after_callbacks called = false @machine.after_transition {called = true} @transition.perform assert called end def test_should_pass_record_to_after_callbacks_with_one_argument record = nil @machine.after_transition {|arg| record = arg} @transition.perform assert_equal @record, record end def test_should_pass_record_and_transition_to_after_callbacks_with_multiple_arguments callback_args = nil @machine.after_transition {|*args| callback_args = args} @transition.perform assert_equal [@record, @transition], callback_args end def test_should_run_after_callbacks_outside_the_context_of_the_record context = nil @machine.after_transition {context = self} @transition.perform assert_equal self, context end def test_should_run_after_callbacks_if_model_callback_added_prior_to_state_machine_definition model = new_model do after_save { nil } end machine = StateMachine::Machine.new(model, :initial => :parked) machine.other_states :idling machine.event :ignite after_called = false machine.after_transition {after_called = true} record = model.new(:state => 'parked') transition = StateMachine::Transition.new(record, machine, :ignite, :parked, :idling) transition.perform assert_equal true, after_called end def test_should_run_around_callbacks before_called = false after_called = false ensure_called = 0 @machine.around_transition do |block| before_called = true begin block.call ensure ensure_called += 1 end after_called = true end @transition.perform assert before_called assert after_called assert_equal ensure_called, 1 end def test_should_include_transition_states_in_known_states @machine.before_transition :to => :first_gear, :do => lambda {} assert_equal [:parked, :idling, :first_gear], @machine.states.map {|state| state.name} end def test_should_allow_symbolic_callbacks callback_args = nil klass = class << @record; self; end klass.send(:define_method, :after_ignite) do |*args| callback_args = args end @machine.before_transition(:after_ignite) @transition.perform assert_equal [@transition], callback_args end def test_should_allow_string_callbacks class << @record attr_reader :callback_result end @machine.before_transition('@callback_result = [1, 2, 3]') @transition.perform assert_equal [1, 2, 3], @record.callback_result end def test_should_run_in_expected_order expected = [ :before_transition, :before_validation, :after_validation, :before_save, :before_create, :after_create, :after_save, :after_transition ] callbacks = [] @model.before_validation { callbacks << :before_validation } @model.after_validation { callbacks << :after_validation } @model.before_save { callbacks << :before_save } @model.before_create { callbacks << :before_create } @model.after_create { callbacks << :after_create } @model.after_save { callbacks << :after_save } @machine.before_transition { callbacks << :before_transition } @machine.after_transition { callbacks << :after_transition } @transition.perform assert_equal expected, callbacks end end class MachineWithFailedBeforeCallbacksTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @machine.before_transition {@callbacks << :before_1; false} @machine.before_transition {@callbacks << :before_2} @machine.after_transition {@callbacks << :after} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end if !defined?(MongoMapper::Version) || MongoMapper::Version =~ /^0\.[5-8]\./ def test_should_be_successful assert @result end def test_should_change_current_state assert_equal 'idling', @record.state end def test_should_run_action assert !@record.new_record? end def test_should_run_further_callbacks assert_equal [:before_1, :before_2, :around_before, :around_after, :after], @callbacks end else def test_should_not_be_successful assert !@result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_run_action assert @record.new_record? end def test_should_not_run_further_callbacks assert_equal [:before_1], @callbacks end end end class MachineNestedActionTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new(:state => 'parked') end def test_should_allow_transition_prior_to_creation_if_skipping_action record = @record @model.before_create { record.ignite(false) } result = @record.save assert_equal true, result assert_equal "idling", @record.state @record = @model.find(@record.id) assert_equal "idling", @record.state end def test_should_allow_transition_after_creation record = @record @model.after_create { record.ignite } result = @record.save assert_equal true, result assert_equal "idling", @record.state @record = @model.find(@record.id) assert_equal "idling", @record.state end end class MachineWithFailedActionTest < BaseTestCase def setup @model = new_model do validates_numericality_of :state end @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @callbacks = [] @machine.before_transition {@callbacks << :before} @machine.after_transition {@callbacks << :after} @machine.after_failure {@callbacks << :after_failure} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_not_be_successful assert !@result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_save_record assert @record.new_record? end def test_should_run_before_callbacks_and_after_callbacks_with_failures assert_equal [:before, :around_before, :after_failure], @callbacks end end class MachineWithFailedAfterCallbacksTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @machine.after_transition {@callbacks << :after_1; false} @machine.after_transition {@callbacks << :after_2} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_be_successful assert @result end def test_should_change_current_state assert_equal 'idling', @record.state end def test_should_save_record assert !@record.new_record? end if !defined?(MongoMapper::Version) || MongoMapper::Version =~ /^0\.[5-8]\./ def test_should_still_run_further_after_callbacks assert_equal [:around_before, :around_after, :after_1, :after_2], @callbacks end else def test_should_not_run_further_after_callbacks assert_equal [:around_before, :around_after, :after_1], @callbacks end end end class MachineWithValidationsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked @record = @model.new end def test_should_invalidate_using_errors I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:ActiveModel) @record.state = 'parked' @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']]) assert_equal ['State cannot transition via "park"'], @record.errors.full_messages end def test_should_auto_prefix_custom_attributes_on_invalidation @machine.invalidate(@record, :event, :invalid) assert_equal ['State event is invalid'], @record.errors.full_messages end def test_should_clear_errors_on_reset @record.state = 'parked' @record.errors.add(:state, 'is invalid') @machine.reset(@record) assert_equal [], @record.errors.full_messages end def test_should_be_valid_if_state_is_known @record.state = 'parked' assert @record.valid? end def test_should_not_be_valid_if_state_is_unknown @record.state = 'invalid' assert !@record.valid? assert_equal ['State is invalid'], @record.errors.full_messages end end class MachineWithValidationsAndCustomAttributeTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :status, :attribute => :state) @machine.state :parked @record = @model.new end def test_should_add_validation_errors_to_custom_attribute @record.state = 'invalid' assert !@record.valid? assert_equal ['State is invalid'], @record.errors.full_messages @record.state = 'parked' assert @record.valid? end end class MachineErrorsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @record = @model.new end def test_should_be_able_to_describe_current_errors @record.errors.add(:id, 'cannot be blank') @record.errors.add(:state, 'is invalid') assert_equal ['Id cannot be blank', 'State is invalid'], @machine.errors_for(@record).split(', ').sort end def test_should_describe_as_halted_with_no_errors assert_equal 'Transition halted', @machine.errors_for(@record) end end class MachineWithStateDrivenValidationsTest < BaseTestCase def setup @model = new_model do attr_accessor :seatbealt end @machine = StateMachine::Machine.new(@model) @machine.state :first_gear do validates_presence_of :seatbelt, :key => :first_gear end @machine.state :second_gear do validates_presence_of :seatbelt, :key => :second_gear end @machine.other_states :parked end def test_should_be_valid_if_validation_fails_outside_state_scope record = @model.new(:state => 'parked', :seatbelt => nil) assert record.valid? end def test_should_be_invalid_if_validation_fails_within_state_scope record = @model.new(:state => 'first_gear', :seatbelt => nil) assert !record.valid? end def test_should_be_valid_if_validation_succeeds_within_state_scope record = @model.new(:state => 'second_gear', :seatbelt => true) assert record.valid? end end class MachineWithEventAttributesOnValidationTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert !@record.valid? assert_equal ['State event is invalid'], @record.errors.full_messages end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert !@record.valid? assert_equal ['State event cannot transition when idling'], @record.errors.full_messages end def test_should_be_successful_if_event_has_transition assert @record.valid? end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.valid? assert ran_callback end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } begin @record.valid? rescue ArgumentError raise if StateMachine::Transition.pause_supported? end assert ran_callback end def test_should_persist_new_state @record.valid? assert_equal 'idling', @record.state end def test_should_not_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.valid? assert !ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates_presence_of :seatbelt end ran_callback = false @machine.after_transition { ran_callback = true } @record.valid? assert !ran_callback end def test_should_not_run_around_callbacks_after_yield ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } begin @record.valid? rescue ArgumentError raise if StateMachine::Transition.pause_supported? end assert !ran_callback end def test_should_not_run_around_callbacks_after_yield_with_failures_disabled_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates_presence_of :seatbelt end ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } @record.valid? assert !ran_callback end def test_should_run_failure_callbacks_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates_presence_of :seatbelt end ran_callback = false @machine.after_failure { ran_callback = true } @record.valid? assert ran_callback end end class MachineWithEventAttributesOnSaveTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert_equal false, @record.save end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert_equal false, @record.save end def test_should_be_successful_if_event_has_transition assert_equal true, @record.save end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.save assert ran_callback end def test_should_run_before_callbacks_once before_count = 0 @machine.before_transition { before_count += 1 } @record.save assert_equal 1, before_count end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } @record.save assert ran_callback end def test_should_run_around_callbacks_before_yield_once around_before_count = 0 @machine.around_transition {|block| around_before_count += 1; block.call } @record.save assert_equal 1, around_before_count end def test_should_persist_new_state @record.save assert_equal 'idling', @record.state end def test_should_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.save assert ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_fails @model.class_eval do validates_numericality_of :state end ran_callback = false @machine.after_transition { ran_callback = true } begin; @record.save; rescue; end assert !ran_callback end def test_should_run_failure_callbacks__if_fails @model.class_eval do validates_numericality_of :state end ran_callback = false @machine.after_failure { ran_callback = true } begin; @record.save; rescue; end assert ran_callback end def test_should_not_run_around_callbacks_with_failures_disabled_if_fails @model.class_eval do validates_numericality_of :state end ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } begin; @record.save; rescue; end assert !ran_callback end def test_should_run_around_callbacks_after_yield ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } @record.save assert ran_callback end def test_should_allow_additional_transitions_to_new_state_in_after_transitions @machine.event :park do transition :idling => :parked end @machine.after_transition(:on => :ignite) { @record.park } @record.save assert_equal 'parked', @record.state @record = @model.find(@record.id) assert_equal 'parked', @record.state end def test_should_allow_additional_transitions_to_previous_state_in_after_transitions @machine.event :shift_up do transition :idling => :first_gear end @machine.after_transition(:on => :ignite) { @record.shift_up } @record.save assert_equal 'first_gear', @record.state @record = @model.find(@record.id) assert_equal 'first_gear', @record.state end end if defined?(MongoMapper::Version) && MongoMapper::Version !~ /^0\.[5-8]\./ class MachineWithEventAttributesOnAutosaveTest < BaseTestCase def setup @vehicle_model = new_model(:vehicle) do belongs_to :owner, :class_name => 'MongoMapperTest::Owner' end MongoMapperTest.const_set('Vehicle', @vehicle_model) @owner_model = new_model(:owner) MongoMapperTest.const_set('Owner', @owner_model) machine = StateMachine::Machine.new(@vehicle_model) machine.event :ignite do transition :parked => :idling end @owner = @owner_model.create @vehicle = @vehicle_model.create(:state => 'parked', :owner_id => @owner.id) end def test_should_persist_many_association @owner_model.many :vehicles, :class_name => 'MongoMapperTest::Vehicle', :autosave => true @owner.vehicles[0].state_event = 'ignite' @owner.save @vehicle.reload assert_equal 'idling', @vehicle.state end def teardown MongoMapperTest.class_eval do remove_const('Vehicle') remove_const('Owner') end super end end end class MachineWithEventAttributesOnSaveBangTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert_raise(MongoMapper::DocumentNotValid) { @record.save! } end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert_raise(MongoMapper::DocumentNotValid) { @record.save! } end def test_should_be_successful_if_event_has_transition assert_equal true, @record.save! end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.save! assert ran_callback end def test_should_run_before_callbacks_once before_count = 0 @machine.before_transition { before_count += 1 } @record.save! assert_equal 1, before_count end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } @record.save! assert ran_callback end def test_should_run_around_callbacks_before_yield_once around_before_count = 0 @machine.around_transition {|block| around_before_count += 1; block.call } @record.save! assert_equal 1, around_before_count end def test_should_persist_new_state @record.save! assert_equal 'idling', @record.state end def test_should_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.save! assert ran_callback end def test_should_run_around_callbacks_after_yield ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } @record.save! assert ran_callback end end class MachineWithEventAttributesOnCustomActionTest < BaseTestCase def setup @superclass = new_model do def persist create_or_update end end @model = Class.new(@superclass) @machine = StateMachine::Machine.new(@model, :action => :persist) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_not_transition_on_valid? @record.valid? assert_equal 'parked', @record.state end def test_should_not_transition_on_save @record.save assert_equal 'parked', @record.state end def test_should_not_transition_on_save! @record.save! assert_equal 'parked', @record.state end def test_should_transition_on_custom_action @record.persist assert_equal 'idling', @record.state end end class MachineWithScopesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :first_gear @machine.state :idling, :value => lambda {'idling'} end def test_should_create_singular_with_scope assert @model.respond_to?(:with_state) end def test_should_only_include_records_with_state_in_singular_with_scope parked = @model.create :state => 'parked' @model.create :state => 'idling' assert_equal [parked], @model.with_state(:parked).to_a end def test_should_create_plural_with_scope assert @model.respond_to?(:with_states) end def test_should_only_include_records_with_states_in_plural_with_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked, idling], @model.with_states(:parked, :idling).to_a end def test_should_allow_lookup_by_string_name parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked, idling], @model.with_states('parked', 'idling').to_a end def test_should_create_singular_without_scope assert @model.respond_to?(:without_state) end def test_should_only_include_records_without_state_in_singular_without_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked], @model.without_state(:idling).to_a end def test_should_create_plural_without_scope assert @model.respond_to?(:without_states) end def test_should_only_include_records_without_states_in_plural_without_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' first_gear = @model.create :state => 'first_gear' assert_equal [parked, idling], @model.without_states(:first_gear).to_a end if defined?(MongoMapper::Version) && !(MongoMapper::Version =~ /^0\.[5-7]\./) def test_should_allow_chaining_scopes parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [idling], @model.without_state(:parked).with_state(:idling).all end end end class MachineWithScopesAndOwnerSubclassTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :state) @subclass = Class.new(@model) @subclass_machine = @subclass.state_machine(:state) {} @subclass_machine.state :parked, :idling, :first_gear end def test_should_only_include_records_with_subclass_states_in_with_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' assert_equal [parked, idling], @subclass.with_states(:parked, :idling).to_a end def test_should_only_include_records_without_subclass_states_in_without_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' first_gear = @subclass.create :state => 'first_gear' assert_equal [parked, idling], @subclass.without_states(:first_gear).to_a end end class MachineWithComplexPluralizationScopesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :status) end def test_should_create_singular_with_scope assert @model.respond_to?(:with_status) end def test_should_create_plural_with_scope assert @model.respond_to?(:with_statuses) end end if defined?(ActiveModel) class MachineWithInternationalizationTest < BaseTestCase def setup I18n.backend = I18n::Backend::Simple.new # Initialize the backend StateMachine::Machine.new(new_model) I18n.backend.translate(:en, 'mongo_mapper.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling') @model = new_model end def test_should_use_defaults I18n.backend.store_translations(:en, { :mongo_mapper => {:errors => {:messages => {:invalid_transition => 'cannot %{event}'}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked, :idling machine.event :ignite record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_error_key I18n.backend.store_translations(:en, { :mongo_mapper => {:errors => {:messages => {:bad_transition => 'cannot %{event}'}}} }) machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => :bad_transition}) machine.state :parked, :idling record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_error_string machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => 'cannot %{event}'}) machine.state :parked, :idling record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_state_key_scoped_to_class_and_machine I18n.backend.store_translations(:en, { :mongo_mapper => {:state_machines => {:'mongo_mapper_test/foo' => {:state => {:states => {:parked => 'shutdown'}}}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_scoped_to_class I18n.backend.store_translations(:en, { :mongo_mapper => {:state_machines => {:'mongo_mapper_test/foo' => {:states => {:parked => 'shutdown'}}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_scoped_to_machine I18n.backend.store_translations(:en, { :mongo_mapper => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_unscoped I18n.backend.store_translations(:en, { :mongo_mapper => {:state_machines => {:states => {:parked => 'shutdown'}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_support_nil_state_key I18n.backend.store_translations(:en, { :mongo_mapper => {:state_machines => {:states => {:nil => 'empty'}}} }) machine = StateMachine::Machine.new(@model) assert_equal 'empty', machine.state(nil).human_name end def test_should_allow_customized_event_key_scoped_to_class_and_machine I18n.backend.store_translations(:en, { :mongo_mapper => {:state_machines => {:'mongo_mapper_test/foo' => {:state => {:events => {:park => 'stop'}}}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_scoped_to_class I18n.backend.store_translations(:en, { :mongo_mapper => {:state_machines => {:'mongo_mapper_test/foo' => {:events => {:park => 'stop'}}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_scoped_to_machine I18n.backend.store_translations(:en, { :mongo_mapper => {:state_machines => {:state => {:events => {:park => 'stop'}}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_unscoped I18n.backend.store_translations(:en, { :mongo_mapper => {:state_machines => {:events => {:park => 'stop'}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_only_add_locale_once_in_load_path assert_equal 1, I18n.load_path.select {|path| path =~ %r{mongo_mapper/locale\.rb$}}.length # Create another MongoMapper model that will triger the i18n feature new_model assert_equal 1, I18n.load_path.select {|path| path =~ %r{mongo_mapper/locale\.rb$}}.length end def test_should_add_locale_to_beginning_of_load_path @original_load_path = I18n.load_path I18n.backend = I18n::Backend::Simple.new app_locale = File.dirname(__FILE__) + '/../../files/en.yml' default_locale = File.dirname(__FILE__) + '/../../../lib/state_machine/integrations/mongo_mapper/locale.rb' I18n.load_path = [app_locale] StateMachine::Machine.new(@model) assert_equal [default_locale, app_locale].map {|path| File.expand_path(path)}, I18n.load_path.map {|path| File.expand_path(path)} ensure I18n.load_path = @original_load_path end def test_should_prefer_other_locales_first @original_load_path = I18n.load_path I18n.backend = I18n::Backend::Simple.new I18n.load_path = [File.dirname(__FILE__) + '/../../files/en.yml'] machine = StateMachine::Machine.new(@model) machine.state :parked, :idling machine.event :ignite record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot transition'], record.errors.full_messages ensure I18n.load_path = @original_load_path end end else $stderr.puts 'Skipping MongoMapper I18n tests.' end end state-machine-1.2.0/test/unit/integrations/active_model_test.rb0000644000175000017500000011365112305405267024310 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../../test_helper') require 'active_model' if defined?(ActiveModel::VERSION) && ActiveModel::VERSION::MAJOR >= 4 require 'rails/observers/active_model/active_model' require 'active_model/mass_assignment_security' else require 'active_model/observing' end require 'active_support/all' module ActiveModelTest class BaseTestCase < Test::Unit::TestCase def default_test end protected # Creates a new ActiveModel model (and the associated table) def new_model(&block) # Simple ActiveModel superclass parent = Class.new do def self.model_attribute(name) define_method(name) { instance_variable_defined?("@#{name}") ? instance_variable_get("@#{name}") : nil } define_method("#{name}=") do |value| send("#{name}_will_change!") if self.class <= ActiveModel::Dirty && value != send(name) instance_variable_set("@#{name}", value) end end def self.create object = new object.save object end def initialize(attrs = {}) attrs.each {|attr, value| send("#{attr}=", value)} @changed_attributes = {} end def attributes @attributes ||= {} end def save @changed_attributes = {} true end end model = Class.new(parent) do def self.name 'ActiveModelTest::Foo' end model_attribute :state end model.class_eval(&block) if block_given? model end # Creates a new ActiveModel observer def new_observer(model, &block) observer = Class.new(ActiveModel::Observer) do attr_accessor :notifications def initialize super @notifications = [] end end observer.observe(model) observer.class_eval(&block) if block_given? observer end end class IntegrationTest < BaseTestCase def test_should_have_an_integration_name assert_equal :active_model, StateMachine::Integrations::ActiveModel.integration_name end def test_should_be_available assert StateMachine::Integrations::ActiveModel.available? end def test_should_match_if_class_includes_observing_feature assert StateMachine::Integrations::ActiveModel.matches?(new_model { include ActiveModel::Observing }) end def test_should_match_if_class_includes_validations_feature assert StateMachine::Integrations::ActiveModel.matches?(new_model { include ActiveModel::Validations }) end def test_should_not_match_if_class_does_not_include_active_model_features assert !StateMachine::Integrations::ActiveModel.matches?(new_model) end def test_should_have_no_defaults assert_equal({}, StateMachine::Integrations::ActiveModel.defaults) end def test_should_have_a_locale_path assert_not_nil StateMachine::Integrations::ActiveModel.locale_path end end class MachineByDefaultTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :integration => :active_model) end def test_should_not_have_action assert_nil @machine.action end def test_should_use_transactions assert_equal true, @machine.use_transactions end def test_should_not_have_any_before_callbacks assert_equal 0, @machine.callbacks[:before].size end def test_should_not_have_any_after_callbacks assert_equal 0, @machine.callbacks[:after].size end end class MachineWithStatesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :first_gear end def test_should_humanize_name assert_equal 'first gear', @machine.state(:first_gear).human_name end end class MachineWithStaticInitialStateTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked, :integration => :active_model) end def test_should_set_initial_state_on_created_object record = @model.new assert_equal 'parked', record.state end end class MachineWithDynamicInitialStateTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked}, :integration => :active_model) @machine.state :parked end def test_should_set_initial_state_on_created_object record = @model.new assert_equal 'parked', record.state end end class MachineWithEventsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :shift_up end def test_should_humanize_name assert_equal 'shift up', @machine.event(:shift_up).human_name end end class MachineWithModelStateAttributeTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked, :integration => :active_model) @machine.other_states(:idling) @record = @model.new end def test_should_have_an_attribute_predicate assert @record.respond_to?(:state?) end def test_should_raise_exception_for_predicate_without_parameters assert_raise(ArgumentError) { @record.state? } end def test_should_return_false_for_predicate_if_does_not_match_current_value assert !@record.state?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.state?(:parked) end def test_should_raise_exception_for_predicate_if_invalid_state_specified assert_raise(IndexError) { @record.state?(:invalid) } end end class MachineWithNonModelStateAttributeUndefinedTest < BaseTestCase def setup @model = new_model do def initialize end end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked, :integration => :active_model) @machine.other_states(:idling) @record = @model.new end def test_should_not_define_a_reader_attribute_for_the_attribute assert !@record.respond_to?(:status) end def test_should_not_define_a_writer_attribute_for_the_attribute assert !@record.respond_to?(:status=) end def test_should_define_an_attribute_predicate assert @record.respond_to?(:status?) end end class MachineWithInitializedStateTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked, :integration => :active_model) @machine.state :idling end def test_should_allow_nil_initial_state_when_static @machine.state nil record = @model.new(:state => nil) assert_nil record.state end def test_should_allow_nil_initial_state_when_dynamic @machine.state nil @machine.initial_state = lambda {:parked} record = @model.new(:state => nil) assert_nil record.state end def test_should_allow_different_initial_state_when_static record = @model.new(:state => 'idling') assert_equal 'idling', record.state end def test_should_allow_different_initial_state_when_dynamic @machine.initial_state = lambda {:parked} record = @model.new(:state => 'idling') assert_equal 'idling', record.state end def test_should_use_default_state_if_protected @model.class_eval do include ActiveModel::MassAssignmentSecurity attr_protected :state def initialize(attrs = {}) initialize_state_machines do sanitize_for_mass_assignment(attrs).each {|attr, value| send("#{attr}=", value)} if attrs @changed_attributes = {} end end end record = @model.new(:state => 'idling') assert_equal 'parked', record.state record = @model.new(nil) assert_equal 'parked', record.state end end class MachineMultipleTest < BaseTestCase def setup @model = new_model do model_attribute :status end @state_machine = StateMachine::Machine.new(@model, :initial => :parked, :integration => :active_model) @status_machine = StateMachine::Machine.new(@model, :status, :initial => :idling, :integration => :active_model) end def test_should_should_initialize_each_state record = @model.new assert_equal 'parked', record.state assert_equal 'idling', record.status end end class MachineWithDirtyAttributesTest < BaseTestCase def setup @model = new_model do include ActiveModel::Dirty define_attribute_methods [:state] end @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform end def test_should_include_state_in_changed_attributes assert_equal %w(state), @record.changed end def test_should_track_attribute_change assert_equal %w(parked idling), @record.changes['state'] end def test_should_not_reset_changes_on_multiple_transitions transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform assert_equal %w(parked idling), @record.changes['state'] end end class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model do include ActiveModel::Dirty define_attribute_methods [:state] end @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_changes assert_equal nil, @record.changes['state'] end end class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase def setup @model = new_model do include ActiveModel::Dirty model_attribute :status define_attribute_methods [:status] end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform end def test_should_include_state_in_changed_attributes assert_equal %w(status), @record.changed end def test_should_track_attribute_change assert_equal %w(parked idling), @record.changes['status'] end def test_should_not_reset_changes_on_multiple_transitions transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform assert_equal %w(parked idling), @record.changes['status'] end end class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model do include ActiveModel::Dirty model_attribute :status define_attribute_methods [:status] end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.event :park @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_changes assert_equal nil, @record.changes['status'] end end class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase def setup @model = new_model do include ActiveModel::Dirty define_attribute_methods [:state] end @machine = StateMachine::Machine.new(@model, :action => :save, :initial => :parked) @machine.event :ignite @record = @model.create @record.state_event = 'ignite' end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_change assert_equal nil, @record.changes['state'] end end class MachineWithCallbacksTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked, :integration => :active_model) @machine.other_states :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) end def test_should_run_before_callbacks called = false @machine.before_transition {called = true} @transition.perform assert called end def test_should_pass_record_to_before_callbacks_with_one_argument record = nil @machine.before_transition {|arg| record = arg} @transition.perform assert_equal @record, record end def test_should_pass_record_and_transition_to_before_callbacks_with_multiple_arguments callback_args = nil @machine.before_transition {|*args| callback_args = args} @transition.perform assert_equal [@record, @transition], callback_args end def test_should_run_before_callbacks_outside_the_context_of_the_record context = nil @machine.before_transition {context = self} @transition.perform assert_equal self, context end def test_should_run_after_callbacks called = false @machine.after_transition {called = true} @transition.perform assert called end def test_should_pass_record_to_after_callbacks_with_one_argument record = nil @machine.after_transition {|arg| record = arg} @transition.perform assert_equal @record, record end def test_should_pass_record_and_transition_to_after_callbacks_with_multiple_arguments callback_args = nil @machine.after_transition {|*args| callback_args = args} @transition.perform assert_equal [@record, @transition], callback_args end def test_should_run_after_callbacks_outside_the_context_of_the_record context = nil @machine.after_transition {context = self} @transition.perform assert_equal self, context end def test_should_run_around_callbacks before_called = false after_called = false ensure_called = 0 @machine.around_transition do |block| before_called = true begin block.call ensure ensure_called += 1 end after_called = true end @transition.perform assert before_called assert after_called assert_equal ensure_called, 1 end def test_should_include_transition_states_in_known_states @machine.before_transition :to => :first_gear, :do => lambda {} assert_equal [:parked, :idling, :first_gear], @machine.states.map {|state| state.name} end def test_should_allow_symbolic_callbacks callback_args = nil klass = class << @record; self; end klass.send(:define_method, :after_ignite) do |*args| callback_args = args end @machine.before_transition(:after_ignite) @transition.perform assert_equal [@transition], callback_args end def test_should_allow_string_callbacks class << @record attr_reader :callback_result end @machine.before_transition('@callback_result = [1, 2, 3]') @transition.perform assert_equal [1, 2, 3], @record.callback_result end end class MachineWithFailedBeforeCallbacksTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachine::Machine.new(@model, :integration => :active_model) @machine.state :parked, :idling @machine.event :ignite @machine.before_transition {@callbacks << :before_1; false} @machine.before_transition {@callbacks << :before_2} @machine.after_transition {@callbacks << :after} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_not_be_successful assert !@result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_run_further_callbacks assert_equal [:before_1], @callbacks end end class MachineWithFailedAfterCallbacksTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachine::Machine.new(@model, :integration => :active_model) @machine.state :parked, :idling @machine.event :ignite @machine.after_transition {@callbacks << :after_1; false} @machine.after_transition {@callbacks << :after_2} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_be_successful assert @result end def test_should_change_current_state assert_equal 'idling', @record.state end def test_should_not_run_further_after_callbacks assert_equal [:around_before, :around_after, :after_1], @callbacks end end class MachineWithValidationsTest < BaseTestCase def setup @model = new_model { include ActiveModel::Validations } @machine = StateMachine::Machine.new(@model, :action => :save) @machine.state :parked @record = @model.new end def test_should_invalidate_using_errors I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:I18n) @record.state = 'parked' @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']]) assert_equal ['State cannot transition via "park"'], @record.errors.full_messages end def test_should_auto_prefix_custom_attributes_on_invalidation @machine.invalidate(@record, :event, :invalid) assert_equal ['State event is invalid'], @record.errors.full_messages end def test_should_clear_errors_on_reset @record.state = 'parked' @record.errors.add(:state, 'is invalid') @machine.reset(@record) assert_equal [], @record.errors.full_messages end def test_should_be_valid_if_state_is_known @record.state = 'parked' assert @record.valid? end def test_should_not_be_valid_if_state_is_unknown @record.state = 'invalid' assert !@record.valid? assert_equal ['State is invalid'], @record.errors.full_messages end end class MachineWithValidationsAndCustomAttributeTest < BaseTestCase def setup @model = new_model { include ActiveModel::Validations } @machine = StateMachine::Machine.new(@model, :status, :attribute => :state) @machine.state :parked @record = @model.new end def test_should_add_validation_errors_to_custom_attribute @record.state = 'invalid' assert !@record.valid? assert_equal ['State is invalid'], @record.errors.full_messages @record.state = 'parked' assert @record.valid? end end class MachineErrorsTest < BaseTestCase def setup @model = new_model { include ActiveModel::Validations } @machine = StateMachine::Machine.new(@model) @record = @model.new end def test_should_be_able_to_describe_current_errors @record.errors.add(:id, 'cannot be blank') @record.errors.add(:state, 'is invalid') assert_equal ['Id cannot be blank', 'State is invalid'], @machine.errors_for(@record).split(', ').sort end def test_should_describe_as_halted_with_no_errors assert_equal 'Transition halted', @machine.errors_for(@record) end end class MachineWithStateDrivenValidationsTest < BaseTestCase def setup @model = new_model do include ActiveModel::Validations attr_accessor :seatbelt end @machine = StateMachine::Machine.new(@model) @machine.state :first_gear, :second_gear do validates_presence_of :seatbelt end @machine.other_states :parked end def test_should_be_valid_if_validation_fails_outside_state_scope record = @model.new(:state => 'parked', :seatbelt => nil) assert record.valid? end def test_should_be_invalid_if_validation_fails_within_state_scope record = @model.new(:state => 'first_gear', :seatbelt => nil) assert !record.valid? end def test_should_be_valid_if_validation_succeeds_within_state_scope record = @model.new(:state => 'second_gear', :seatbelt => true) assert record.valid? end end class ObserverUpdateTest < BaseTestCase def setup @model = new_model { include ActiveModel::Observing } @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @observer_update = StateMachine::Integrations::ActiveModel::ObserverUpdate.new(:before_transition, @record, @transition) end def test_should_have_method assert_equal :before_transition, @observer_update.method end def test_should_have_object assert_equal @record, @observer_update.object end def test_should_have_transition assert_equal @transition, @observer_update.transition end def test_should_include_object_and_transition_in_args assert_equal [@record, @transition], @observer_update.args end def test_should_use_record_class_as_class assert_equal @model, @observer_update.class end end class MachineWithObserversTest < BaseTestCase def setup @model = new_model { include ActiveModel::Observing } @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) end def test_should_call_all_transition_callback_permutations callbacks = [ :before_ignite_from_parked_to_idling, :before_ignite_from_parked, :before_ignite_to_idling, :before_ignite, :before_transition_state_from_parked_to_idling, :before_transition_state_from_parked, :before_transition_state_to_idling, :before_transition_state, :before_transition ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance @transition.perform assert_equal callbacks, instance.notifications end def test_should_call_no_transition_callbacks_when_observers_disabled return unless ActiveModel::VERSION::MAJOR >= 3 && ActiveModel::VERSION::MINOR >= 1 callbacks = [ :before_ignite, :before_transition ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance @model.observers.disable(observer) do @transition.perform end assert_equal [], instance.notifications end def test_should_pass_record_and_transition_to_before_callbacks observer = new_observer(@model) do def before_transition(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end def test_should_pass_record_and_transition_to_after_callbacks observer = new_observer(@model) do def after_transition(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end def test_should_call_methods_outside_the_context_of_the_record observer = new_observer(@model) do def before_ignite(*args) notifications << self end end instance = observer.instance @transition.perform assert_equal [instance], instance.notifications end def test_should_support_nil_from_states callbacks = [ :before_ignite_from_nil_to_idling, :before_ignite_from_nil, :before_transition_state_from_nil_to_idling, :before_transition_state_from_nil ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance transition = StateMachine::Transition.new(@record, @machine, :ignite, nil, :idling) transition.perform assert_equal callbacks, instance.notifications end def test_should_support_nil_to_states callbacks = [ :before_ignite_from_parked_to_nil, :before_ignite_to_nil, :before_transition_state_from_parked_to_nil, :before_transition_state_to_nil ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, nil) transition.perform assert_equal callbacks, instance.notifications end end class MachineWithNamespacedObserversTest < BaseTestCase def setup @model = new_model { include ActiveModel::Observing } @machine = StateMachine::Machine.new(@model, :state, :namespace => 'alarm') @machine.state :active, :off @machine.event :enable @record = @model.new(:state => 'off') @transition = StateMachine::Transition.new(@record, @machine, :enable, :off, :active) end def test_should_call_namespaced_before_event_method observer = new_observer(@model) do def before_enable_alarm(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end def test_should_call_namespaced_after_event_method observer = new_observer(@model) do def after_enable_alarm(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end end class MachineWithFailureCallbacksTest < BaseTestCase def setup @model = new_model { include ActiveModel::Observing } @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @notifications = [] # Create callbacks @machine.before_transition {false} @machine.after_failure {@notifications << :callback_after_failure} # Create observer callbacks observer = new_observer(@model) do def after_failure_to_ignite(*args) notifications << :observer_after_failure_ignite end def after_failure_to_transition(*args) notifications << :observer_after_failure_transition end end instance = observer.instance instance.notifications = @notifications @transition.perform end def test_should_invoke_callbacks_in_specific_order expected = [ :callback_after_failure, :observer_after_failure_ignite, :observer_after_failure_transition ] assert_equal expected, @notifications end end class MachineWithMixedCallbacksTest < BaseTestCase def setup @model = new_model { include ActiveModel::Observing } @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @notifications = [] # Create callbacks @machine.before_transition {@notifications << :callback_before_transition} @machine.after_transition {@notifications << :callback_after_transition} @machine.around_transition {|block| @notifications << :callback_around_before_transition; block.call; @notifications << :callback_around_after_transition} # Create observer callbacks observer = new_observer(@model) do def before_ignite(*args) notifications << :observer_before_ignite end def before_transition(*args) notifications << :observer_before_transition end def after_ignite(*args) notifications << :observer_after_ignite end def after_transition(*args) notifications << :observer_after_transition end end instance = observer.instance instance.notifications = @notifications @transition.perform end def test_should_invoke_callbacks_in_specific_order expected = [ :callback_before_transition, :callback_around_before_transition, :observer_before_ignite, :observer_before_transition, :callback_around_after_transition, :callback_after_transition, :observer_after_ignite, :observer_after_transition ] assert_equal expected, @notifications end end class MachineWithInternationalizationTest < BaseTestCase def setup I18n.backend = I18n::Backend::Simple.new # Initialize the backend I18n.backend.translate(:en, 'activemodel.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling') @model = new_model { include ActiveModel::Validations } end def test_should_use_defaults I18n.backend.store_translations(:en, { :activemodel => {:errors => {:messages => {:invalid_transition => 'cannot %{event}'}}} }) machine = StateMachine::Machine.new(@model, :action => :save) machine.state :parked, :idling machine.event :ignite record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_error_key I18n.backend.store_translations(:en, { :activemodel => {:errors => {:messages => {:bad_transition => 'cannot %{event}'}}} }) machine = StateMachine::Machine.new(@model, :action => :save, :messages => {:invalid_transition => :bad_transition}) machine.state :parked, :idling record = @model.new record.state = 'idling' machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_error_string machine = StateMachine::Machine.new(@model, :action => :save, :messages => {:invalid_transition => 'cannot %{event}'}) machine.state :parked, :idling record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_state_key_scoped_to_class_and_machine I18n.backend.store_translations(:en, { :activemodel => {:state_machines => {:'active_model_test/foo' => {:state => {:states => {:parked => 'shutdown'}}}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_scoped_to_class I18n.backend.store_translations(:en, { :activemodel => {:state_machines => {:'active_model_test/foo' => {:states => {:parked => 'shutdown'}}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_scoped_to_machine I18n.backend.store_translations(:en, { :activemodel => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_unscoped I18n.backend.store_translations(:en, { :activemodel => {:state_machines => {:states => {:parked => 'shutdown'}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_support_nil_state_key I18n.backend.store_translations(:en, { :activemodel => {:state_machines => {:states => {:nil => 'empty'}}} }) machine = StateMachine::Machine.new(@model) assert_equal 'empty', machine.state(nil).human_name end def test_should_allow_customized_event_key_scoped_to_class_and_machine I18n.backend.store_translations(:en, { :activemodel => {:state_machines => {:'active_model_test/foo' => {:state => {:events => {:park => 'stop'}}}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_scoped_to_class I18n.backend.store_translations(:en, { :activemodel => {:state_machines => {:'active_model_test/foo' => {:events => {:park => 'stop'}}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_scoped_to_machine I18n.backend.store_translations(:en, { :activemodel => {:state_machines => {:state => {:events => {:park => 'stop'}}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_unscoped I18n.backend.store_translations(:en, { :activemodel => {:state_machines => {:events => {:park => 'stop'}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_only_add_locale_once_in_load_path assert_equal 1, I18n.load_path.select {|path| path =~ %r{active_model/locale\.rb$}}.length # Create another ActiveModel model that will triger the i18n feature new_model assert_equal 1, I18n.load_path.select {|path| path =~ %r{active_model/locale\.rb$}}.length end def test_should_add_locale_to_beginning_of_load_path @original_load_path = I18n.load_path I18n.backend = I18n::Backend::Simple.new app_locale = File.dirname(__FILE__) + '/../../files/en.yml' default_locale = File.dirname(__FILE__) + '/../../../lib/state_machine/integrations/active_model/locale.rb' I18n.load_path = [app_locale] StateMachine::Machine.new(@model) assert_equal [default_locale, app_locale].map {|path| File.expand_path(path)}, I18n.load_path.map {|path| File.expand_path(path)} ensure I18n.load_path = @original_load_path end def test_should_prefer_other_locales_first @original_load_path = I18n.load_path I18n.backend = I18n::Backend::Simple.new I18n.load_path = [File.dirname(__FILE__) + '/../../files/en.yml'] machine = StateMachine::Machine.new(@model) machine.state :parked, :idling machine.event :ignite record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages ensure I18n.load_path = @original_load_path end end end state-machine-1.2.0/test/unit/integrations/active_record_test.rb0000644000175000017500000022676212305405267024476 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../../test_helper') require 'active_record' FIXTURES_ROOT = File.dirname(__FILE__) + '/../../fixtures/' # Load TestCase helpers require 'active_support/test_case' require 'active_record/fixtures' begin require 'active_record/test_case' rescue LoadError class ActiveRecord::TestCase < ActiveSupport::TestCase self.fixture_path = FIXTURES_ROOT self.use_instantiated_fixtures = false self.use_transactional_fixtures = true end end require 'active_record/version' if ActiveRecord::VERSION::MAJOR >= 4 require 'rails/observers/activerecord/active_record' require 'active_record/mass_assignment_security' end # Establish database connection ActiveRecord::Base.establish_connection('adapter' => RUBY_PLATFORM == 'java' ? 'jdbcsqlite3' : 'sqlite3', 'database' => ':memory:') ActiveRecord::Base.logger = Logger.new("#{File.dirname(__FILE__)}/../../active_record.log") module ActiveRecordTest class BaseTestCase < ActiveRecord::TestCase def default_test end protected # Creates a new ActiveRecord model (and the associated table) def new_model(create_table = :foo, &block) name = create_table || :foo table_name = "#{name}_#{rand(1000000)}" model = Class.new(ActiveRecord::Base) do self.table_name = table_name.to_s connection.create_table(table_name, :force => true) {|t| t.string(:state)} if create_table (class << self; self; end).class_eval do define_method(:name) { "ActiveRecordTest::#{name.to_s.capitalize}" } end end model.class_eval(&block) if block_given? model.reset_column_information if create_table model end # Creates a new ActiveRecord observer def new_observer(model, &block) observer = Class.new(ActiveRecord::Observer) do attr_accessor :notifications def initialize super @notifications = [] end end (class << observer; self; end).class_eval do define_method(:name) do "#{model.name}Observer" end end observer.observe(model) observer.class_eval(&block) if block_given? observer end end class IntegrationTest < BaseTestCase def test_should_have_an_integration_name assert_equal :active_record, StateMachine::Integrations::ActiveRecord.integration_name end def test_should_be_available assert StateMachine::Integrations::ActiveRecord.available? end def test_should_match_if_class_inherits_from_active_record assert StateMachine::Integrations::ActiveRecord.matches?(new_model) end def test_should_not_match_if_class_does_not_inherit_from_active_record assert !StateMachine::Integrations::ActiveRecord.matches?(Class.new) end def test_should_have_defaults assert_equal({:action => :save}, StateMachine::Integrations::ActiveRecord.defaults) end def test_should_have_a_locale_path assert_not_nil StateMachine::Integrations::ActiveRecord.locale_path end end class MachineWithoutDatabaseTest < BaseTestCase def setup @model = new_model(false) do # Simulate the database not being available entirely def self.connection raise ActiveRecord::ConnectionNotEstablished end def self.connected? false end end end def test_should_allow_machine_creation assert_nothing_raised { StateMachine::Machine.new(@model) } end end class MachineUnmigratedTest < BaseTestCase def setup @model = new_model(false) # Drop the table so that it definitely doesn't exist @model.connection.drop_table(@model.table_name) if @model.table_exists? end def test_should_allow_machine_creation assert_nothing_raised { StateMachine::Machine.new(@model) } end end class MachineByDefaultTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) end def test_should_use_save_as_action assert_equal :save, @machine.action end def test_should_use_transactions assert_equal true, @machine.use_transactions end def test_should_create_notifier_before_callback assert_equal 1, @machine.callbacks[:before].size end def test_should_create_notifier_after_callback assert_equal 1, @machine.callbacks[:after].size end end class MachineWithStatesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :first_gear end def test_should_humanize_name assert_equal 'first gear', @machine.state(:first_gear).human_name end end class MachineWithStaticInitialStateTest < BaseTestCase def setup @model = new_model(:vehicle) do attr_accessor :value end @machine = StateMachine::Machine.new(@model, :initial => :parked) end def test_should_set_initial_state_on_created_object record = @model.new assert_equal 'parked', record.state end def test_should_set_initial_state_with_nil_attributes record = @model.new(nil) assert_equal 'parked', record.state end def test_should_still_set_attributes record = @model.new(:value => 1) assert_equal 1, record.value end def test_should_still_allow_initialize_blocks block_args = nil record = @model.new do |*args| block_args = args end assert_equal [record], block_args end def test_should_set_attributes_prior_to_initialize_block state = nil @model.new do |record| state = record.state end assert_equal 'parked', state end def test_should_set_attributes_prior_to_after_initialize_hook state = nil @model.class_eval {define_method(:after_initialize) {}} if ActiveRecord::VERSION::MAJOR <= 2 @model.after_initialize do |record| state = record.state end @model.new assert_equal 'parked', state end def test_should_set_initial_state_before_setting_attributes @model.class_eval do attr_accessor :state_during_setter remove_method :value= define_method(:value=) do |value| self.state_during_setter = state end end record = @model.new(:value => 1) assert_equal 'parked', record.state_during_setter end def test_should_not_set_initial_state_after_already_initialized record = @model.new(:value => 1) assert_equal 'parked', record.state record.state = 'idling' record.attributes = {} assert_equal 'idling', record.state end def test_should_persist_initial_state record = @model.new record.save record.reload assert_equal 'parked', record.state end unless ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0 def test_should_persist_initial_state_on_dup record = @model.create.dup record.save record.reload assert_equal 'parked', record.state end end def test_should_use_stored_values_when_loading_from_database @machine.state :idling record = @model.find(@model.create(:state => 'idling').id) assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database_with_nil_state @machine.state nil record = @model.find(@model.create(:state => nil).id) assert_nil record.state end def test_should_use_stored_values_when_loading_for_many_association @machine.state :idling @model.connection.add_column @model.table_name, :owner_id, :integer @model.reset_column_information ActiveRecordTest.const_set('Vehicle', @model) owner_model = new_model(:owner) do has_many :vehicles, :class_name => 'ActiveRecordTest::Vehicle' end ActiveRecordTest.const_set('Owner', owner_model) owner = owner_model.create record = @model.create(:state => 'idling', :owner_id => owner.id) assert_equal 'idling', owner.vehicles[0].state end def test_should_use_stored_values_when_loading_for_one_association @machine.state :idling @model.connection.add_column @model.table_name, :owner_id, :integer @model.reset_column_information ActiveRecordTest.const_set('Vehicle', @model) owner_model = new_model(:owner) do has_one :vehicle, :class_name => 'ActiveRecordTest::Vehicle' end ActiveRecordTest.const_set('Owner', owner_model) owner = owner_model.create record = @model.create(:state => 'idling', :owner_id => owner.id) assert_equal 'idling', owner.vehicle.state end def test_should_use_stored_values_when_loading_for_belongs_to_association @machine.state :idling ActiveRecordTest.const_set('Vehicle', @model) driver_model = new_model(:driver) do connection.add_column table_name, :vehicle_id, :integer belongs_to :vehicle, :class_name => 'ActiveRecordTest::Vehicle' end ActiveRecordTest.const_set('Driver', driver_model) record = @model.create(:state => 'idling') driver = driver_model.create(:vehicle_id => record.id) assert_equal 'idling', driver.vehicle.state end def teardown ActiveRecordTest.class_eval do remove_const('Vehicle') if defined?(ActiveRecordTest::Vehicle) remove_const('Owner') if defined?(ActiveRecordTest::Owner) remove_const('Driver') if defined?(ActiveRecordTest::Driver) end ActiveSupport::Dependencies.clear if defined?(ActiveSupport::Dependencies) super end end class MachineWithDynamicInitialStateTest < BaseTestCase def setup @model = new_model do attr_accessor :value end @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked}) @machine.state :parked end def test_should_set_initial_state_on_created_object record = @model.new assert_equal 'parked', record.state end def test_should_still_set_attributes record = @model.new(:value => 1) assert_equal 1, record.value end def test_should_still_allow_initialize_blocks block_args = nil record = @model.new do |*args| block_args = args end assert_equal [record], block_args end def test_should_set_attributes_prior_to_initialize_block state = nil @model.new do |record| state = record.state end assert_equal 'parked', state end def test_should_set_attributes_prior_to_after_initialize_hook state = nil @model.class_eval {define_method(:after_initialize) {}} if ActiveRecord::VERSION::MAJOR <= 2 @model.after_initialize do |record| state = record.state end @model.new assert_equal 'parked', state end def test_should_set_initial_state_after_setting_attributes @model.class_eval do attr_accessor :state_during_setter remove_method :value= define_method(:value=) do |value| self.state_during_setter = state || 'nil' end end record = @model.new(:value => 1) assert_equal 'nil', record.state_during_setter end def test_should_not_set_initial_state_after_already_initialized record = @model.new(:value => 1) assert_equal 'parked', record.state record.state = 'idling' record.attributes = {} assert_equal 'idling', record.state end def test_should_persist_initial_state record = @model.new record.save record.reload assert_equal 'parked', record.state end unless ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0 def test_should_persist_initial_state_on_dup record = @model.create.dup record.save record.reload assert_equal 'parked', record.state end end def test_should_use_stored_values_when_loading_from_database @machine.state :idling record = @model.find(@model.create(:state => 'idling').id) assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database_with_nil_state @machine.state nil record = @model.find(@model.create(:state => nil).id) assert_nil record.state end end class MachineWithEventsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :shift_up end def test_should_humanize_name assert_equal 'shift up', @machine.event(:shift_up).human_name end end class MachineWithSameColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do connection.add_column table_name, :status, :string, :default => 'parked' end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @record = @model.new end def test_should_use_machine_default assert_equal 'parked', @record.status end def test_should_not_generate_a_warning assert_no_match(/have defined a different default/, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithDifferentColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do connection.add_column table_name, :status, :string, :default => 'idling' end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @record = @model.new end def test_should_use_machine_default assert_equal 'parked', @record.status end def test_should_generate_a_warning assert_match(/Both ActiveRecordTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithDifferentIntegerColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model do connection.add_column table_name, :status, :integer, :default => 0 end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.state :parked, :value => 1 @record = @model.new end def test_should_use_machine_default assert_equal 1, @record.status end def test_should_generate_a_warning assert_match(/Both ActiveRecordTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithConflictingPredicateTest < BaseTestCase def setup @model = new_model do def state?(*args) true end end @machine = StateMachine::Machine.new(@model) @record = @model.new end def test_should_not_define_attribute_predicate assert @record.state? end end class MachineWithConflictingStateNameTest < BaseTestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @model = new_model end def test_should_output_warning_with_same_machine_name @machine = StateMachine::Machine.new(@model) @machine.state :state assert_match(/^Instance method "state\?" is already defined in ActiveRecordTest::Foo, use generic helper instead.*\n$/, $stderr.string) end def test_should_output_warning_with_same_machine_attribute @machine = StateMachine::Machine.new(@model, :public_state, :attribute => :state) @machine.state :state assert_match(/^Instance method "state\?" is already defined in ActiveRecordTest::Foo, use generic helper instead.*\n$/, $stderr.string) end def teardown $stderr = @original_stderr super end end class MachineWithColumnStateAttributeTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_not_override_the_column_reader @record[:state] = 'parked' assert_equal 'parked', @record.state end def test_should_not_override_the_column_writer @record.state = 'parked' assert_equal 'parked', @record[:state] end def test_should_have_an_attribute_predicate assert @record.respond_to?(:state?) end def test_should_test_for_existence_on_predicate_without_parameters assert @record.state? @record.state = nil assert !@record.state? end def test_should_return_false_for_predicate_if_does_not_match_current_value assert !@record.state?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.state?(:parked) end def test_should_raise_exception_for_predicate_if_invalid_state_specified assert_raise(IndexError) { @record.state?(:invalid) } end end class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase def setup @model = new_model do def initialize # Skip attribute initialization @initialized_state_machines = true super end end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_not_define_a_column_for_the_attribute assert_nil @model.columns_hash['status'] end def test_should_define_a_reader_attribute_for_the_attribute assert @record.respond_to?(:status) end def test_should_define_a_writer_attribute_for_the_attribute assert @record.respond_to?(:status=) end def test_should_define_an_attribute_predicate assert @record.respond_to?(:status?) end end class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase def setup @model = new_model do def status=(value) self['status'] = value end def status self['status'] end end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_return_false_for_predicate_if_does_not_match_current_value assert !@record.status?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.status?(:parked) end def test_should_raise_exception_for_predicate_if_invalid_state_specified assert_raise(IndexError) { @record.status?(:invalid) } end def test_should_set_initial_state_on_created_object assert_equal 'parked', @record.status end end class MachineWithAliasedAttributeTest < BaseTestCase def setup @model = new_model do alias_attribute :vehicle_status, :state end @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status) @machine.state :parked @record = @model.new end def test_should_check_custom_attribute_for_predicate @record.vehicle_status = nil assert !@record.status?(:parked) @record.vehicle_status = 'parked' assert @record.status?(:parked) end end class MachineWithCustomAttributeTest < BaseTestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @model = new_model @machine = StateMachine::Machine.new(@model, :public_state, :attribute => :state) @record = @model.new end def test_should_not_delegate_attribute_predicate_with_different_attribute assert_raise(ArgumentError) { @record.public_state? } end def teardown $stderr = @original_stderr super end end class MachineWithInitializedStateTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.state :idling end def test_should_allow_nil_initial_state_when_static @machine.state nil record = @model.new(:state => nil) assert_nil record.state end def test_should_allow_nil_initial_state_when_dynamic @machine.state nil @machine.initial_state = lambda {:parked} record = @model.new(:state => nil) assert_nil record.state end def test_should_allow_different_initial_state_when_static record = @model.new(:state => 'idling') assert_equal 'idling', record.state end def test_should_allow_different_initial_state_when_dynamic @machine.initial_state = lambda {:parked} record = @model.new(:state => 'idling') assert_equal 'idling', record.state end def test_should_use_default_state_if_protected @model.class_eval do attr_protected :state end record = @model.new(:state => 'idling') assert_equal 'parked', record.state end end class MachineMultipleTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :status, :string end @state_machine = StateMachine::Machine.new(@model, :initial => :parked) @status_machine = StateMachine::Machine.new(@model, :status, :initial => :idling) end def test_should_should_initialize_each_state record = @model.new assert_equal 'parked', record.state assert_equal 'idling', record.status end end class MachineWithLoopbackTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :updated_at, :datetime end @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create(:updated_at => Time.now - 1) @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @timestamp = @record.updated_at @transition.perform end if ActiveRecord.const_defined?(:Dirty) || ActiveRecord::AttributeMethods.const_defined?(:Dirty) def test_should_not_update_record assert_equal @timestamp, @record.updated_at end else def test_should_update_record assert_not_equal @timestamp, @record.updated_at end end end if ActiveRecord.const_defined?(:Dirty) || ActiveRecord::AttributeMethods.const_defined?(:Dirty) class MachineWithDirtyAttributesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal %w(state), @record.changed end def test_should_track_attribute_change assert_equal %w(parked idling), @record.changes['state'] end def test_should_not_reset_changes_on_multiple_transitions transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform(false) assert_equal %w(parked idling), @record.changes['state'] end def test_should_not_have_changes_when_loaded_from_database record = @model.find(@record.id) assert !record.changed? end end class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform(false) end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_changes assert_equal nil, @record.changes['state'] end end class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :status, :string end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal %w(status), @record.changed end def test_should_track_attribute_change assert_equal %w(parked idling), @record.changes['status'] end def test_should_not_reset_changes_on_multiple_transitions transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling) transition.perform(false) assert_equal %w(parked idling), @record.changes['status'] end end class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model do connection.add_column table_name, :status, :string end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.event :park @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform(false) end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_changes assert_equal nil, @record.changes['status'] end end class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :ignite @record = @model.create @record.state_event = 'ignite' end def test_should_not_include_state_in_changed_attributes assert_equal [], @record.changed end def test_should_not_track_attribute_change assert_equal nil, @record.changes['state'] end end else $stderr.puts 'Skipping ActiveRecord Dirty tests.' end class MachineWithoutTransactionsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :use_transactions => false) end def test_should_not_rollback_transaction_if_false @machine.within_transaction(@model.new) do @model.create false end assert_equal 1, @model.count end def test_should_not_rollback_transaction_if_true @machine.within_transaction(@model.new) do @model.create true end assert_equal 1, @model.count end end class MachineWithTransactionsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :use_transactions => true) end def test_should_rollback_transaction_if_false @machine.within_transaction(@model.new) do @model.create false end assert_equal 0, @model.count end def test_should_not_rollback_transaction_if_true @machine.within_transaction(@model.new) do @model.create true end assert_equal 1, @model.count end end class MachineWithCallbacksTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.other_states :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) end def test_should_run_before_callbacks called = false @machine.before_transition {called = true} @transition.perform assert called end def test_should_pass_record_to_before_callbacks_with_one_argument record = nil @machine.before_transition {|arg| record = arg} @transition.perform assert_equal @record, record end def test_should_pass_record_and_transition_to_before_callbacks_with_multiple_arguments callback_args = nil @machine.before_transition {|*args| callback_args = args} @transition.perform assert_equal [@record, @transition], callback_args end def test_should_run_before_callbacks_outside_the_context_of_the_record context = nil @machine.before_transition {context = self} @transition.perform assert_equal self, context end def test_should_run_after_callbacks called = false @machine.after_transition {called = true} @transition.perform assert called end def test_should_pass_record_to_after_callbacks_with_one_argument record = nil @machine.after_transition {|arg| record = arg} @transition.perform assert_equal @record, record end def test_should_pass_record_and_transition_to_after_callbacks_with_multiple_arguments callback_args = nil @machine.after_transition {|*args| callback_args = args} @transition.perform assert_equal [@record, @transition], callback_args end def test_should_run_after_callbacks_outside_the_context_of_the_record context = nil @machine.after_transition {context = self} @transition.perform assert_equal self, context end def test_should_run_after_callbacks_if_model_callback_added_prior_to_state_machine_definition model = new_model do after_save { nil } end machine = StateMachine::Machine.new(model, :initial => :parked) machine.other_states :idling machine.event :ignite after_called = false machine.after_transition {after_called = true} record = model.new(:state => 'parked') transition = StateMachine::Transition.new(record, machine, :ignite, :parked, :idling) transition.perform assert_equal true, after_called end def test_should_run_around_callbacks before_called = false after_called = false ensure_called = 0 @machine.around_transition do |block| before_called = true begin block.call ensure ensure_called += 1 end after_called = true end @transition.perform assert before_called assert after_called assert_equal ensure_called, 1 end def test_should_include_transition_states_in_known_states @machine.before_transition :to => :first_gear, :do => lambda {} assert_equal [:parked, :idling, :first_gear], @machine.states.map {|state| state.name} end def test_should_allow_symbolic_callbacks callback_args = nil klass = class << @record; self; end klass.send(:define_method, :after_ignite) do |*args| callback_args = args end @machine.before_transition(:after_ignite) @transition.perform assert_equal [@transition], callback_args end def test_should_allow_string_callbacks class << @record attr_reader :callback_result end @machine.before_transition('@callback_result = [1, 2, 3]') @transition.perform assert_equal [1, 2, 3], @record.callback_result end def test_should_run_in_expected_order expected = [ :before_transition, :before_validation, :after_validation, :before_save, :before_create, :after_create, :after_save, :after_transition ] callbacks = [] @model.before_validation { callbacks << :before_validation } @model.after_validation { callbacks << :after_validation } @model.before_save { callbacks << :before_save } @model.before_create { callbacks << :before_create } @model.after_create { callbacks << :after_create } @model.after_save { callbacks << :after_save } if @model.respond_to?(:after_commit) @model.after_commit { callbacks << :after_commit } expected << :after_commit end @machine.before_transition { callbacks << :before_transition } @machine.after_transition { callbacks << :after_transition } @transition.perform assert_equal expected, callbacks end end class MachineWithFailedBeforeCallbacksTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @machine.before_transition {@callbacks << :before_1; false} @machine.before_transition {@callbacks << :before_2} @machine.after_transition {@callbacks << :after} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_not_be_successful assert !@result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_run_action assert @record.new_record? end def test_should_not_run_further_callbacks assert_equal [:before_1], @callbacks end end class MachineNestedActionTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new(:state => 'parked') end def test_should_allow_transition_prior_to_creation_if_skipping_action record = @record @model.before_create { record.ignite(false) } result = @record.save assert_equal true, result assert_equal "idling", @record.state @record.reload assert_equal "idling", @record.state end def test_should_allow_transition_after_creation record = @record @model.after_create { record.ignite } result = @record.save assert_equal true, result assert_equal "idling", @record.state @record.reload assert_equal "idling", @record.state end end class MachineWithFailedActionTest < BaseTestCase def setup @model = new_model do validates_inclusion_of :state, :in => %w(first_gear) end @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @callbacks = [] @machine.before_transition {@callbacks << :before} @machine.after_transition {@callbacks << :after} @machine.after_failure {@callbacks << :after_failure} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_not_be_successful assert !@result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_save_record assert @record.new_record? end def test_should_run_before_callbacks_and_after_callbacks_with_failures assert_equal [:before, :around_before, :after_failure], @callbacks end end class MachineWithFailedAfterCallbacksTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @machine.after_transition {@callbacks << :after_1; false} @machine.after_transition {@callbacks << :after_2} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_be_successful assert @result end def test_should_change_current_state assert_equal 'idling', @record.state end def test_should_save_record assert !@record.new_record? end def test_should_not_run_further_after_callbacks assert_equal [:around_before, :around_after, :after_1], @callbacks end end class MachineWithValidationsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked @record = @model.new end def test_should_invalidate_using_errors I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:I18n) @record.state = 'parked' @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']]) assert_equal ['State cannot transition via "park"'], @record.errors.full_messages end def test_should_auto_prefix_custom_attributes_on_invalidation @machine.invalidate(@record, :event, :invalid) assert_equal ['State event is invalid'], @record.errors.full_messages end def test_should_clear_errors_on_reset @record.state = 'parked' @record.errors.add(:state, 'is invalid') @machine.reset(@record) assert_equal [], @record.errors.full_messages end def test_should_be_valid_if_state_is_known @record.state = 'parked' assert @record.valid? end def test_should_not_be_valid_if_state_is_unknown @record.state = 'invalid' assert !@record.valid? assert_equal ['State is invalid'], @record.errors.full_messages end end class MachineWithValidationsAndCustomAttributeTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :status, :attribute => :state) @machine.state :parked @record = @model.new end def test_should_add_validation_errors_to_custom_attribute @record.state = 'invalid' assert !@record.valid? assert_equal ['State is invalid'], @record.errors.full_messages @record.state = 'parked' assert @record.valid? end end class MachineErrorsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @record = @model.new end def test_should_be_able_to_describe_current_errors @record.errors.add(:id, 'cannot be blank') @record.errors.add(:state, 'is invalid') assert_equal ['Id cannot be blank', 'State is invalid'], @machine.errors_for(@record).split(', ').sort end def test_should_describe_as_halted_with_no_errors assert_equal 'Transition halted', @machine.errors_for(@record) end end class MachineWithStateDrivenValidationsTest < BaseTestCase def setup @model = new_model do attr_accessor :seatbelt end @machine = StateMachine::Machine.new(@model) @machine.state :first_gear, :second_gear do validates_presence_of :seatbelt end @machine.other_states :parked end def test_should_be_valid_if_validation_fails_outside_state_scope record = @model.new(:state => 'parked', :seatbelt => nil) assert record.valid? end def test_should_be_invalid_if_validation_fails_within_state_scope record = @model.new(:state => 'first_gear', :seatbelt => nil) assert !record.valid? end def test_should_be_valid_if_validation_succeeds_within_state_scope record = @model.new(:state => 'second_gear', :seatbelt => true) assert record.valid? end end class MachineWithEventAttributesOnValidationTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert !@record.valid? assert_equal ['State event is invalid'], @record.errors.full_messages end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert !@record.valid? assert_equal ['State event cannot transition when idling'], @record.errors.full_messages end def test_should_be_successful_if_event_has_transition assert @record.valid? end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.valid? assert ran_callback end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } begin @record.valid? rescue ArgumentError raise if StateMachine::Transition.pause_supported? end assert ran_callback end def test_should_persist_new_state @record.valid? assert_equal 'idling', @record.state end def test_should_not_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.valid? assert !ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates_presence_of :seatbelt end ran_callback = false @machine.after_transition { ran_callback = true } @record.valid? assert !ran_callback end def test_should_run_after_callbacks_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates_presence_of :seatbelt end ran_callback = false @machine.after_failure { ran_callback = true } @record.valid? assert ran_callback end def test_should_not_run_around_callbacks_after_yield ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } begin @record.valid? rescue ArgumentError raise if StateMachine::Transition.pause_supported? end assert !ran_callback end def test_should_not_run_around_callbacks_after_yield_with_failures_disabled_if_validation_fails @model.class_eval do attr_accessor :seatbelt validates_presence_of :seatbelt end ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } @record.valid? assert !ran_callback end def test_should_not_run_before_transitions_within_transaction @machine.before_transition { @model.create; raise ActiveRecord::Rollback } begin @record.valid? rescue Exception end assert_equal 1, @model.count end end class MachineWithEventAttributesOnSaveTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert_equal false, @record.save end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert_equal false, @record.save end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.save assert ran_callback end def test_should_run_before_callbacks_once before_count = 0 @machine.before_transition { before_count += 1 } @record.save assert_equal 1, before_count end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } @record.save assert ran_callback end def test_should_run_around_callbacks_before_yield_once around_before_count = 0 @machine.around_transition {|block| around_before_count += 1; block.call } @record.save assert_equal 1, around_before_count end def test_should_persist_new_state @record.save assert_equal 'idling', @record.state end def test_should_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.save assert ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_fails @model.before_create {|record| false} ran_callback = false @machine.after_transition { ran_callback = true } begin; @record.save; rescue; end assert !ran_callback end def test_should_run_failure_callbacks__if_fails @model.before_create {|record| false} ran_callback = false @machine.after_failure { ran_callback = true } begin; @record.save; rescue; end assert ran_callback end def test_should_not_run_around_callbacks_if_fails @model.before_create {|record| false} ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } begin; @record.save; rescue; end assert !ran_callback end def test_should_run_around_callbacks_after_yield ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } @record.save assert ran_callback end def test_should_run_before_transitions_within_transaction @machine.before_transition { @model.create; raise ActiveRecord::Rollback } begin @record.save rescue Exception end assert_equal 0, @model.count end def test_should_run_after_transitions_within_transaction @machine.after_transition { @model.create; raise ActiveRecord::Rollback } begin @record.save rescue Exception end assert_equal 0, @model.count end def test_should_run_around_transition_within_transaction @machine.around_transition { @model.create; raise ActiveRecord::Rollback } begin @record.save rescue Exception end assert_equal 0, @model.count end def test_should_allow_additional_transitions_to_new_state_in_after_transitions @machine.event :park do transition :idling => :parked end @machine.after_transition(:on => :ignite) { @record.park } @record.save assert_equal 'parked', @record.state @record.reload assert_equal 'parked', @record.state end def test_should_allow_additional_transitions_to_previous_state_in_after_transitions @machine.event :shift_up do transition :idling => :first_gear end @machine.after_transition(:on => :ignite) { @record.shift_up } @record.save assert_equal 'first_gear', @record.state @record.reload assert_equal 'first_gear', @record.state end def test_should_return_nil_on_manual_rollback @machine.before_transition { raise ActiveRecord::Rollback } assert_equal nil, @record.save end end if ActiveRecord::VERSION::MAJOR >= 3 || ActiveRecord::VERSION::MINOR >= 3 class MachineWithEventAttributesOnAutosaveTest < BaseTestCase def setup @vehicle_model = new_model(:vehicle) do connection.add_column table_name, :owner_id, :integer end ActiveRecordTest.const_set('Vehicle', @vehicle_model) @owner_model = new_model(:owner) ActiveRecordTest.const_set('Owner', @owner_model) machine = StateMachine::Machine.new(@vehicle_model) machine.event :ignite do transition :parked => :idling end @owner = @owner_model.create @vehicle = @vehicle_model.create(:state => 'parked', :owner_id => @owner.id) end def test_should_persist_has_one_autosave @owner_model.has_one :vehicle, :class_name => 'ActiveRecordTest::Vehicle', :autosave => true @owner.vehicle.state_event = 'ignite' @owner.save @vehicle.reload assert_equal 'idling', @vehicle.state end def test_should_persist_has_many_autosave @owner_model.has_many :vehicles, :class_name => 'ActiveRecordTest::Vehicle', :autosave => true @owner.vehicles[0].state_event = 'ignite' @owner.save @vehicle.reload assert_equal 'idling', @vehicle.state end def teardown ActiveRecordTest.class_eval do remove_const('Vehicle') remove_const('Owner') end ActiveSupport::Dependencies.clear if defined?(ActiveSupport::Dependencies) super end end end class MachineWithEventAttributesOnSaveBangTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert_raise(ActiveRecord::RecordInvalid) { @record.save! } end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert_raise(ActiveRecord::RecordInvalid) { @record.save! } end def test_should_be_successful_if_event_has_transition assert_equal true, @record.save! end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.save! assert ran_callback end def test_should_run_before_callbacks_once before_count = 0 @machine.before_transition { before_count += 1 } @record.save! assert_equal 1, before_count end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } @record.save! assert ran_callback end def test_should_run_around_callbacks_before_yield_once around_before_count = 0 @machine.around_transition {|block| around_before_count += 1; block.call } @record.save! assert_equal 1, around_before_count end def test_should_persist_new_state @record.save! assert_equal 'idling', @record.state end def test_should_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.save! assert ran_callback end def test_should_run_around_callbacks_after_yield ran_callback = false @machine.around_transition {|block| block.call; ran_callback = true } @record.save! assert ran_callback end end class MachineWithEventAttributesOnCustomActionTest < BaseTestCase def setup @superclass = new_model do def persist create_or_update end end @model = Class.new(@superclass) @machine = StateMachine::Machine.new(@model, :action => :persist) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_not_transition_on_valid? @record.valid? assert_equal 'parked', @record.state end def test_should_not_transition_on_save @record.save assert_equal 'parked', @record.state end def test_should_not_transition_on_save! @record.save! assert_equal 'parked', @record.state end def test_should_transition_on_custom_action @record.persist assert_equal 'idling', @record.state end end class MachineWithObserversTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) end def test_should_call_all_transition_callback_permutations callbacks = [ :before_ignite_from_parked_to_idling, :before_ignite_from_parked, :before_ignite_to_idling, :before_ignite, :before_transition_state_from_parked_to_idling, :before_transition_state_from_parked, :before_transition_state_to_idling, :before_transition_state, :before_transition ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance @transition.perform assert_equal callbacks, instance.notifications end def test_should_call_no_transition_callbacks_when_observers_disabled return unless ActiveRecord::VERSION::MAJOR >= 3 && ActiveRecord::VERSION::MINOR >= 1 callbacks = [ :before_ignite, :before_transition ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance @model.observers.disable(observer) do @transition.perform end assert_equal [], instance.notifications end def test_should_pass_record_and_transition_to_before_callbacks observer = new_observer(@model) do def before_transition(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end def test_should_pass_record_and_transition_to_after_callbacks observer = new_observer(@model) do def after_transition(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end def test_should_call_methods_outside_the_context_of_the_record observer = new_observer(@model) do def before_ignite(*args) notifications << self end end instance = observer.instance @transition.perform assert_equal [instance], instance.notifications end def test_should_continue_to_handle_non_state_machine_callbacks observer = new_observer(@model) do def before_save(object) notifications << [:before_save, object] end def before_ignite(*args) notifications << :before_ignite end end instance = observer.instance @transition.perform assert_equal [:before_ignite, [:before_save, @record]], instance.notifications end def test_should_support_nil_from_states callbacks = [ :before_ignite_from_nil_to_idling, :before_ignite_from_nil, :before_transition_state_from_nil_to_idling, :before_transition_state_from_nil ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance transition = StateMachine::Transition.new(@record, @machine, :ignite, nil, :idling) transition.perform assert_equal callbacks, instance.notifications end def test_should_support_nil_to_states callbacks = [ :before_ignite_from_parked_to_nil, :before_ignite_to_nil, :before_transition_state_from_parked_to_nil, :before_transition_state_to_nil ] observer = new_observer(@model) do callbacks.each do |callback| define_method(callback) do |*args| notifications << callback end end end instance = observer.instance transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, nil) transition.perform assert_equal callbacks, instance.notifications end end class MachineWithNamespacedObserversTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :state, :namespace => 'alarm') @machine.state :active, :off @machine.event :enable @record = @model.new(:state => 'off') @transition = StateMachine::Transition.new(@record, @machine, :enable, :off, :active) end def test_should_call_namespaced_before_event_method observer = new_observer(@model) do def before_enable_alarm(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end def test_should_call_namespaced_after_event_method observer = new_observer(@model) do def after_enable_alarm(*args) notifications << args end end instance = observer.instance @transition.perform assert_equal [[@record, @transition]], instance.notifications end end class MachineWithFailureCallbacksTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @notifications = [] # Create callbacks @machine.before_transition {false} @machine.after_failure {@notifications << :callback_after_failure} # Create observer callbacks observer = new_observer(@model) do def after_failure_to_ignite(*args) notifications << :observer_after_failure_ignite end def after_failure_to_transition(*args) notifications << :observer_after_failure_transition end end instance = observer.instance instance.notifications = @notifications @transition.perform end def test_should_invoke_callbacks_in_specific_order expected = [ :callback_after_failure, :observer_after_failure_ignite, :observer_after_failure_transition ] assert_equal expected, @notifications end end class MachineWithMixedCallbacksTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @notifications = [] # Create callbacks @machine.before_transition {@notifications << :callback_before_transition} @machine.after_transition {@notifications << :callback_after_transition} @machine.around_transition do |block| @notifications << :callback_around_before_transition block.call @notifications << :callback_arond_after_transition end # Create observer callbacks observer = new_observer(@model) do def before_ignite(*args) notifications << :observer_before_ignite end def before_transition(*args) notifications << :observer_before_transition end def after_ignite(*args) notifications << :observer_after_ignite end def after_transition(*args) notifications << :observer_after_transition end end instance = observer.instance instance.notifications = @notifications @transition.perform end def test_should_invoke_callbacks_in_specific_order expected = [ :callback_before_transition, :callback_around_before_transition, :observer_before_ignite, :observer_before_transition, :callback_arond_after_transition, :callback_after_transition, :observer_after_ignite, :observer_after_transition ] assert_equal expected, @notifications end end if ActiveRecord.const_defined?(:NamedScope) class MachineWithScopesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :first_gear @machine.state :idling, :value => lambda {'idling'} end def test_should_create_singular_with_scope assert @model.respond_to?(:with_state) end def test_should_only_include_records_with_state_in_singular_with_scope parked = @model.create :state => 'parked' @model.create :state => 'idling' assert_equal [parked], @model.with_state(:parked).find(:all) end def test_should_create_plural_with_scope assert @model.respond_to?(:with_states) end def test_should_only_include_records_with_states_in_plural_with_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked, idling], @model.with_states(:parked, :idling).find(:all) end def test_should_allow_lookup_by_string_name parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked, idling], @model.with_states('parked', 'idling').find(:all) end def test_should_create_singular_without_scope assert @model.respond_to?(:without_state) end def test_should_only_include_records_without_state_in_singular_without_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked], @model.without_state(:idling).find(:all) end def test_should_create_plural_without_scope assert @model.respond_to?(:without_states) end def test_should_only_include_records_without_states_in_plural_without_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' first_gear = @model.create :state => 'first_gear' assert_equal [parked, idling], @model.without_states(:first_gear).find(:all) end def test_should_allow_chaining_scopes parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [idling], @model.without_state(:parked).with_state(:idling).find(:all) end end class MachineWithScopesAndOwnerSubclassTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :state) @subclass = Class.new(@model) @subclass_machine = @subclass.state_machine(:state) {} @subclass_machine.state :parked, :idling, :first_gear end def test_should_only_include_records_with_subclass_states_in_with_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' assert_equal [parked, idling], @subclass.with_states(:parked, :idling).find(:all) end def test_should_only_include_records_without_subclass_states_in_without_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' first_gear = @subclass.create :state => 'first_gear' assert_equal [parked, idling], @subclass.without_states(:first_gear).find(:all) end end class MachineWithComplexPluralizationScopesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :status) end def test_should_create_singular_with_scope assert @model.respond_to?(:with_status) end def test_should_create_plural_with_scope assert @model.respond_to?(:with_statuses) end end class MachineWithScopesAndJoinsTest < BaseTestCase def setup @company = new_model(:company) ActiveRecordTest.const_set('Company', @company) @vehicle = new_model(:vehicle) do connection.add_column table_name, :company_id, :integer belongs_to :company, :class_name => 'ActiveRecordTest::Company' end ActiveRecordTest.const_set('Vehicle', @vehicle) @company_machine = StateMachine::Machine.new(@company, :initial => :active) @vehicle_machine = StateMachine::Machine.new(@vehicle, :initial => :parked) @vehicle_machine.state :idling @ford = @company.create @mustang = @vehicle.create(:company => @ford) end def test_should_find_records_in_with_scope assert_equal [@mustang], @vehicle.with_states(:parked).find(:all, :joins => :company, :conditions => "#{@company.table_name}.state = \"active\"") end def test_should_find_records_in_without_scope assert_equal [@mustang], @vehicle.without_states(:idling).find(:all, :joins => :company, :conditions => "#{@company.table_name}.state = \"active\"") end def teardown ActiveRecordTest.class_eval do remove_const('Vehicle') remove_const('Company') end ActiveSupport::Dependencies.clear if defined?(ActiveSupport::Dependencies) super end end else $stderr.puts 'Skipping ActiveRecord Scope tests.' end if ActiveRecord.const_defined?(:Relation) class MachineWithDefaultScope < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.state :idling @model.class_eval do default_scope { with_state(:parked, :idling) } end end def test_should_set_initial_state_on_created_object object = @model.new assert_equal 'parked', object.state end end else $stderr.puts 'Skipping ActiveRecord Default Scope tests.' end if Object.const_defined?(:I18n) class MachineWithInternationalizationTest < BaseTestCase def setup I18n.backend = I18n::Backend::Simple.new # Initialize the backend StateMachine::Machine.new(new_model) I18n.backend.translate(:en, 'activerecord.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling') @model = new_model end def test_should_use_defaults I18n.backend.store_translations(:en, { :activerecord => {:errors => {:messages => {:invalid_transition => "cannot #{interpolation_key('event')}"}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked, :idling machine.event :ignite record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_error_key I18n.backend.store_translations(:en, { :activerecord => {:errors => {:messages => {:bad_transition => "cannot #{interpolation_key('event')}"}}} }) machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => :bad_transition}) machine.state :parked, :idling record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_error_string machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => "cannot #{interpolation_key('event')}"}) machine.state :parked, :idling record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot ignite'], record.errors.full_messages end def test_should_allow_customized_state_key_scoped_to_class_and_machine I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:'active_record_test/foo' => {:state => {:states => {:parked => 'shutdown'}}}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_scoped_to_class I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:'active_record_test/foo' => {:states => {:parked => 'shutdown'}}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_scoped_to_machine I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_allow_customized_state_key_unscoped I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:states => {:parked => 'shutdown'}}} }) machine = StateMachine::Machine.new(@model) machine.state :parked assert_equal 'shutdown', machine.state(:parked).human_name end def test_should_support_nil_state_key I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:states => {:nil => 'empty'}}} }) machine = StateMachine::Machine.new(@model) assert_equal 'empty', machine.state(nil).human_name end def test_should_allow_customized_event_key_scoped_to_class_and_machine I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:'active_record_test/foo' => {:state => {:events => {:park => 'stop'}}}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_scoped_to_class I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:'active_record_test/foo' => {:events => {:park => 'stop'}}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_scoped_to_machine I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:state => {:events => {:park => 'stop'}}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_allow_customized_event_key_unscoped I18n.backend.store_translations(:en, { :activerecord => {:state_machines => {:events => {:park => 'stop'}}} }) machine = StateMachine::Machine.new(@model) machine.event :park assert_equal 'stop', machine.event(:park).human_name end def test_should_only_add_locale_once_in_load_path assert_equal 1, I18n.load_path.select {|path| path =~ %r{active_record/locale\.rb$}}.length # Create another ActiveRecord model that will triger the i18n feature new_model assert_equal 1, I18n.load_path.select {|path| path =~ %r{active_record/locale\.rb$}}.length end def test_should_add_locale_to_beginning_of_load_path @original_load_path = I18n.load_path I18n.backend = I18n::Backend::Simple.new app_locale = File.dirname(__FILE__) + '/../../files/en.yml' default_locale = File.dirname(__FILE__) + '/../../../lib/state_machine/integrations/active_record/locale.rb' I18n.load_path = [app_locale] StateMachine::Machine.new(@model) assert_equal [default_locale, app_locale].map {|path| File.expand_path(path)}, I18n.load_path.map {|path| File.expand_path(path)} ensure I18n.load_path = @original_load_path end def test_should_prefer_other_locales_first @original_load_path = I18n.load_path I18n.backend = I18n::Backend::Simple.new I18n.load_path = [File.dirname(__FILE__) + '/../../files/en.yml'] machine = StateMachine::Machine.new(@model) machine.state :parked, :idling machine.event :ignite record = @model.new(:state => 'idling') machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']]) assert_equal ['State cannot transition'], record.errors.full_messages ensure I18n.load_path = @original_load_path end private def interpolation_key(key) !defined?(I18n::VERSION) || I18n::VERSION < '0.4.0' ? "{{#{key}}}" : "%{#{key}}" end end else $stderr.puts 'Skipping ActiveRecord I18n tests.' end end state-machine-1.2.0/test/unit/integrations/sequel_test.rb0000644000175000017500000015404712305405267023157 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../../test_helper') require 'sequel' require 'logger' require 'stringio' # Establish database connection DB = Sequel.connect(RUBY_PLATFORM == 'java' ? 'jdbc:sqlite::memory:' : 'sqlite:///', :loggers => [Logger.new("#{File.dirname(__FILE__)}/../../sequel.log")]) module SequelTest class BaseTestCase < Test::Unit::TestCase def default_test end protected # Creates a new Sequel model (and the associated table) def new_model(create_table = :foo, &block) name = create_table || :foo table_name = "#{name}_#{rand(1000000)}" table_identifier = Sequel::SQL::Identifier.new(table_name) if !defined?(Sequel::VERSION) || Gem::Version.new(Sequel::VERSION) <= Gem::Version.new('3.26.0') class << table_identifier alias_method :original_to_s, :to_s def to_s(*args); args.empty? ? inspect : original_to_s(*args); end end end DB.create_table!(table_identifier) do primary_key :id column :state, :string end if create_table model = Class.new(Sequel::Model) do self.raise_on_save_failure = false (class << self; self; end).class_eval do define_method(:name) { "SequelTest::#{name.to_s.capitalize}" } define_method(:table_identifier) { table_identifier } end set_dataset(DB[table_identifier]) end model.set_dataset(DB[table_identifier]) model.class_eval(&block) if block_given? model end end class IntegrationTest < BaseTestCase def test_should_have_an_integration_name assert_equal :sequel, StateMachine::Integrations::Sequel.integration_name end def test_should_be_available assert StateMachine::Integrations::Sequel.available? end def test_should_match_if_class_inherits_from_sequel assert StateMachine::Integrations::Sequel.matches?(new_model) end def test_should_not_match_if_class_does_not_inherit_from_sequel assert !StateMachine::Integrations::Sequel.matches?(Class.new) end def test_should_have_defaults assert_equal({:action => :save}, StateMachine::Integrations::Sequel.defaults) end def test_should_not_have_a_locale_path assert_nil StateMachine::Integrations::Sequel.locale_path end end class MachineWithoutDatabaseTest < BaseTestCase def setup @model = new_model(false) end def test_should_allow_machine_creation assert_nothing_raised { StateMachine::Machine.new(@model) } end end class MachineUnmigratedTest < BaseTestCase def setup @model = new_model(false) end def test_should_allow_machine_creation assert_nothing_raised { StateMachine::Machine.new(@model) } end end class MachineByDefaultTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) end def test_should_use_save_as_action assert_equal :save, @machine.action end def test_should_use_transactions assert_equal true, @machine.use_transactions end def test_should_not_have_any_before_callbacks assert_equal 0, @machine.callbacks[:before].size end def test_should_not_have_any_after_callbacks assert_equal 0, @machine.callbacks[:after].size end end class MachineWithStatesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :first_gear end def test_should_humanize_name assert_equal 'first gear', @machine.state(:first_gear).human_name end end class MachineWithStaticInitialStateTest < BaseTestCase def setup @model = new_model(:vehicle) do attr_accessor :value end @machine = StateMachine::Machine.new(@model, :initial => :parked) end def test_should_set_initial_state_on_created_object record = @model.new assert_equal 'parked', record.state end def test_should_still_set_attributes record = @model.new(:value => 1) assert_equal 1, record.value end def test_should_still_allow_initialize_blocks block_args = nil record = @model.new do |*args| block_args = args end assert_equal [record], block_args end def test_should_set_attributes_prior_to_initialize_block state = nil @model.new do |record| state = record.state end assert_equal 'parked', state end def test_should_set_attributes_prior_to_after_initialize_hook state = nil @model.class_eval do define_method(:after_initialize) do state = self.state end end @model.new assert_equal 'parked', state end def test_should_set_initial_state_before_setting_attributes @model.class_eval do attr_accessor :state_during_setter remove_method :value= define_method(:value=) do |value| self.state_during_setter = state end end record = @model.new(:value => 1) assert_equal 'parked', record.state_during_setter end def test_should_not_set_initial_state_after_already_initialized record = @model.new(:value => 1) assert_equal 'parked', record.state record.state = 'idling' record.set({}) assert_equal 'idling', record.state end def test_should_persist_initial_state record = @model.new record.save record.reload assert_equal 'parked', record.state end def test_should_persist_initial_state_on_dup record = @model.create.dup record.save record.reload assert_equal 'parked', record.state end def test_should_use_stored_values_when_loading_from_database @machine.state :idling record = @model[@model.create(:state => 'idling').id] assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database_with_nil_state @machine.state nil record = @model[@model.create(:state => nil).id] assert_nil record.state end def test_should_use_stored_values_when_loading_for_many_association @machine.state :idling DB.alter_table(@model.table_identifier) do add_column :owner_id, :integer end @model.class_eval { get_db_schema(true) } SequelTest.const_set('Vehicle', @model) owner_model = new_model(:owner) do one_to_many :vehicles, :class_name => 'SequelTest::Vehicle' end SequelTest.const_set('Owner', owner_model) owner = owner_model.create record = @model.create(:state => 'idling', :owner_id => owner.id) assert_equal 'idling', owner.vehicles[0].state end if defined?(Sequel::VERSION) && Gem::Version.new(Sequel::VERSION) >= Gem::Version.new('3.10.0') def test_should_use_stored_values_when_loading_for_one_association @machine.state :idling DB.alter_table(@model.table_identifier) do add_column :owner_id, :integer end @model.class_eval { get_db_schema(true) } SequelTest.const_set('Vehicle', @model) owner_model = new_model(:owner) do one_to_one :vehicle, :class_name => 'SequelTest::Vehicle' end SequelTest.const_set('Owner', owner_model) owner = owner_model.create record = @model.create(:state => 'idling', :owner_id => owner.id) owner.reload assert_equal 'idling', owner.vehicle.state end end def test_should_use_stored_values_when_loading_for_belongs_to_association @machine.state :idling SequelTest.const_set('Vehicle', @model) driver_model = new_model(:driver) do DB.alter_table(table_identifier) do add_column :vehicle_id, :integer end get_db_schema(true) many_to_one :vehicle, :class_name => 'SequelTest::Vehicle' end SequelTest.const_set('Driver', driver_model) record = @model.create(:state => 'idling') driver = driver_model.create(:vehicle_id => record.id) assert_equal 'idling', driver.vehicle.state end def teardown SequelTest.class_eval do remove_const('Vehicle') if defined?(SequelTest::Vehicle) remove_const('Owner') if defined?(SequelTest::Owner) remove_const('Driver') if defined?(SequelTest::Driver) end super end end class MachineWithDynamicInitialStateTest < BaseTestCase def setup @model = new_model do attr_accessor :value end @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked}) @machine.state :parked end def test_should_set_initial_state_on_created_object record = @model.new assert_equal 'parked', record.state end def test_should_still_set_attributes record = @model.new(:value => 1) assert_equal 1, record.value end def test_should_still_allow_initialize_blocks block_args = nil record = @model.new do |*args| block_args = args end assert_equal [record], block_args end def test_should_not_have_any_changed_columns record = @model.new assert record.changed_columns.empty? end def test_should_set_attributes_prior_to_initialize_block state = nil @model.new do |record| state = record.state end assert_equal 'parked', state end def test_should_set_attributes_prior_to_after_initialize_hook state = nil @model.class_eval do define_method(:after_initialize) do state = self.state end end @model.new assert_equal 'parked', state end def test_should_set_initial_state_after_setting_attributes @model.class_eval do attr_accessor :state_during_setter remove_method :value= define_method(:value=) do |value| self.state_during_setter = state || 'nil' end end record = @model.new(:value => 1) assert_equal 'nil', record.state_during_setter end def test_should_not_set_initial_state_after_already_initialized record = @model.new(:value => 1) assert_equal 'parked', record.state record.state = 'idling' record.set({}) assert_equal 'idling', record.state end def test_should_persist_initial_state record = @model.new record.save record.reload assert_equal 'parked', record.state end def test_should_persist_initial_state_on_dup record = @model.create.dup record.save record.reload assert_equal 'parked', record.state end def test_should_use_stored_values_when_loading_from_database @machine.state :idling record = @model[@model.create(:state => 'idling').id] assert_equal 'idling', record.state end def test_should_use_stored_values_when_loading_from_database_with_nil_state @machine.state nil record = @model[@model.create(:state => nil).id] assert_nil record.state end end class MachineWithEventsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :shift_up end def test_should_humanize_name assert_equal 'shift up', @machine.event(:shift_up).human_name end end class MachineWithSameColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model(false) DB.create_table!(@model.table_identifier) do primary_key :id column :status, :string, :default => 'parked' end @model.class_eval do set_dataset(DB[table_identifier]) get_db_schema(true) end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @record = @model.new end def test_should_use_machine_default assert_equal 'parked', @record.status end def test_should_not_generate_a_warning assert_no_match(/have defined a different default/, $stderr.string) end def teardown $stderr = @original_stderr end end class MachineWithDifferentColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model(false) DB.create_table!(@model.table_identifier) do primary_key :id column :status, :string, :default => 'idling' end @model.class_eval do set_dataset(DB[table_identifier]) get_db_schema(true) end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @record = @model.new end def test_should_use_machine_default assert_equal 'parked', @record.status end def test_should_generate_a_warning assert_match(/Both SequelTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string) end def teardown $stderr = @original_stderr end end class MachineWithDifferentIntegerColumnDefaultTest < BaseTestCase def setup @original_stderr, $stderr = $stderr, StringIO.new @model = new_model(false) DB.create_table!(@model.table_identifier) do primary_key :id column :status, :integer, :default => 0 end @model.class_eval do set_dataset(DB[table_identifier]) get_db_schema(true) end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.state :parked, :value => 1 @record = @model.new end def test_should_use_machine_default assert_equal 1, @record.status end def test_should_generate_a_warning assert_match(/Both SequelTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string) end def teardown $stderr = @original_stderr end end class MachineWithConflictingPredicateTest < BaseTestCase def setup @model = new_model do def state?(*args) true end end @machine = StateMachine::Machine.new(@model) @record = @model.new end def test_should_not_define_attribute_predicate assert @record.state? end end class MachineWithColumnStateAttributeTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_not_override_the_column_reader record = @model.new record[:state] = 'parked' assert_equal 'parked', record.state end def test_should_not_override_the_column_writer record = @model.new record.state = 'parked' assert_equal 'parked', record[:state] end def test_should_have_an_attribute_predicate assert @record.respond_to?(:state?) end def test_should_raise_exception_for_predicate_without_parameters assert_raise(ArgumentError) { @record.state? } end def test_should_return_false_for_predicate_if_does_not_match_current_value assert !@record.state?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.state?(:parked) end def test_should_raise_exception_for_predicate_if_invalid_state_specified assert_raise(IndexError) { @record.state?(:invalid) } end end class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase def setup @model = new_model do # Prevent attempts to access the status field def method_missing(method, *args) super unless %w(status status=).include?(method.to_s) end end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_not_define_a_reader_attribute_for_the_attribute assert !@record.respond_to?(:status) end def test_should_not_define_a_writer_attribute_for_the_attribute assert !@record.respond_to?(:status=) end def test_should_define_an_attribute_predicate assert @record.respond_to?(:status?) end end class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase def setup @model = new_model do attr_accessor :status end @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.other_states(:idling) @record = @model.new end def test_should_return_false_for_predicate_if_does_not_match_current_value assert !@record.status?(:idling) end def test_should_return_true_for_predicate_if_matches_current_value assert @record.status?(:parked) end def test_should_set_initial_state_on_created_object assert_equal 'parked', @record.status end end class MachineWithInitializedStateTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.state :idling end def test_should_allow_nil_initial_state_when_static @machine.state nil record = @model.new(:state => nil) assert_nil record.state end def test_should_allow_nil_initial_state_when_dynamic @machine.state nil @machine.initial_state = lambda {:parked} record = @model.new(:state => nil) assert_nil record.state end def test_should_allow_different_initial_state_when_static record = @model.new(:state => 'idling') assert_equal 'idling', record.state end def test_should_allow_different_initial_state_when_dynamic @machine.initial_state = lambda {:parked} record = @model.new(:state => 'idling') assert_equal 'idling', record.state end def test_should_use_default_state_if_protected @model.class_eval do self.strict_param_setting = false set_restricted_columns :state end record = @model.new(:state => 'idling') assert_equal 'parked', record.state end end class MachineMultipleTest < BaseTestCase def setup @model = new_model DB.alter_table(@model.table_identifier) do add_column :status, :string end @model.class_eval { get_db_schema(true) } @state_machine = StateMachine::Machine.new(@model, :initial => :parked) @status_machine = StateMachine::Machine.new(@model, :status, :initial => :idling) end def test_should_should_initialize_each_state record = @model.new assert_equal 'parked', record.state assert_equal 'idling', record.status end end class MachineWithAliasedAttributeTest < BaseTestCase def setup @model = new_model do alias_method :vehicle_status, :state alias_method :vehicle_status=, :state= end @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status) @machine.state :parked @record = @model.new end def test_should_add_validation_errors_to_custom_attribute @record.vehicle_status = 'invalid' assert !@record.valid? assert_equal ['is invalid'], @record.errors.on(:vehicle_status) @record.vehicle_status = 'parked' assert @record.valid? end end class MachineWithLoopbackTest < BaseTestCase def setup @model = new_model do # Simulate timestamps plugin define_method(:before_update) do changed_columns = self.changed_columns.dup super() self.updated_at = Time.now if changed_columns.any? end end DB.alter_table(@model.table_identifier) do add_column :updated_at, :datetime end @model.class_eval { get_db_schema(true) } @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create(:updated_at => Time.now - 1) @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @timestamp = @record.updated_at @transition.perform end def test_should_update_record assert_not_equal @timestamp, @record.updated_at end end class MachineWithDirtyAttributesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal [:state], @record.changed_columns end def test_should_not_have_changes_when_loaded_from_database record = @model[@record.id] assert record.changed_columns.empty? end end class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :park @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal [:state], @record.changed_columns end end class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase def setup @model = new_model DB.alter_table(@model.table_identifier) do add_column :status, :string end @model.class_eval { get_db_schema(true) } @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.event :ignite @machine.state :idling @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal [:status], @record.changed_columns end end class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase def setup @model = new_model DB.alter_table(@model.table_identifier) do add_column :status, :string end @model.class_eval { get_db_schema(true) } @machine = StateMachine::Machine.new(@model, :status, :initial => :parked) @machine.event :park @record = @model.create @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked) @transition.perform(false) end def test_should_include_state_in_changed_attributes assert_equal [:status], @record.changed_columns end end class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :initial => :parked) @machine.event :ignite @record = @model.create @record.state_event = 'ignite' end def test_should_include_state_in_changed_attributes assert_equal [:state], @record.changed_columns end def test_should_not_include_state_in_changed_attributes_if_nil @record = @model.create @record.state_event = nil assert_equal [], @record.changed_columns end end class MachineWithoutTransactionsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :use_transactions => false) end def test_should_not_rollback_transaction_if_false @machine.within_transaction(@model.new) do @model.create false end assert_equal 1, @model.count end def test_should_not_rollback_transaction_if_true @machine.within_transaction(@model.new) do @model.create true end assert_equal 1, @model.count end end class MachineWithTransactionsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :use_transactions => true) end def test_should_rollback_transaction_if_false @machine.within_transaction(@model.new) do @model.create false end assert_equal 0, @model.count end def test_should_not_rollback_transaction_if_true @machine.within_transaction(@model.new) do @model.create true end assert_equal 1, @model.count end end class MachineWithCallbacksTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) end def test_should_run_before_callbacks called = false @machine.before_transition {called = true} @transition.perform assert called end def test_should_pass_transition_to_before_callbacks_with_one_argument transition = nil @machine.before_transition {|arg| transition = arg} @transition.perform assert_equal @transition, transition end def test_should_pass_transition_to_before_callbacks_with_multiple_arguments callback_args = nil @machine.before_transition {|*args| callback_args = args} @transition.perform assert_equal [@transition], callback_args end def test_should_run_before_callbacks_within_the_context_of_the_record context = nil @machine.before_transition {context = self} @transition.perform assert_equal @record, context end def test_should_run_after_callbacks called = false @machine.after_transition {called = true} @transition.perform assert called end def test_should_pass_transition_to_after_callbacks_with_multiple_arguments callback_args = nil @machine.after_transition {|*args| callback_args = args} @transition.perform assert_equal [@transition], callback_args end def test_should_run_after_callbacks_with_the_context_of_the_record context = nil @machine.after_transition {context = self} @transition.perform assert_equal @record, context end def test_should_run_around_callbacks before_called = false after_called = false ensure_called = 0 @machine.around_transition do |block| before_called = true begin block.call ensure ensure_called += 1 end after_called = true end @transition.perform assert before_called assert after_called assert_equal ensure_called, 1 end def test_should_run_around_callbacks_with_the_context_of_the_record context = nil @machine.around_transition {|block| context = self; block.call} @transition.perform assert_equal @record, context end def test_should_allow_symbolic_callbacks callback_args = nil klass = class << @record; self; end klass.send(:define_method, :after_ignite) do |*args| callback_args = args end @machine.before_transition(:after_ignite) @transition.perform assert_equal [@transition], callback_args end def test_should_allow_string_callbacks class << @record attr_reader :callback_result end @machine.before_transition('@callback_result = [1, 2, 3]') @transition.perform assert_equal [1, 2, 3], @record.callback_result end def test_should_run_in_expected_order expected = [ :before_transition, :before_validation, :after_validation, :before_save, :before_create, :after_create, :after_save, :after_transition ] callbacks = [] @model.before_validation { callbacks << :before_validation } @model.after_validation { callbacks << :after_validation } @model.before_save { callbacks << :before_save } @model.before_create { callbacks << :before_create } @model.after_create { callbacks << :after_create } @model.after_save { callbacks << :after_save } if @model.respond_to?(:after_commit) @model.after_commit { callbacks << :after_commit } expected << :after_commit end @machine.before_transition { callbacks << :before_transition } @machine.after_transition { callbacks << :after_transition } @transition.perform assert_equal expected, callbacks end end class MachineWithFailedBeforeCallbacksTest < BaseTestCase def setup callbacks = [] @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @machine.before_transition {callbacks << :before_1; false} @machine.before_transition {callbacks << :before_2} @machine.after_transition {callbacks << :after} @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform @callbacks = callbacks end def test_should_not_be_successful assert !@result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_run_action assert @record.new? end def test_should_not_run_further_callbacks assert_equal [:before_1], @callbacks end end class MachineNestedActionTest < BaseTestCase def setup @callbacks = [] @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new(:state => 'parked') end def test_should_allow_transition_prior_to_creation_if_skipping_action record = @record @model.before_create { record.ignite(false) } result = @record.save assert result assert_equal "idling", @record.state @record.reload assert_equal "idling", @record.state end if !defined?(Sequel::VERSION) || !%w(2.10.0 2.11.0).include?(Sequel::VERSION) def test_should_allow_transition_after_creation record = @record @model.after_create { record.ignite } result = @record.save assert result assert_equal "idling", @record.state @record.reload assert_equal "idling", @record.state end end end class MachineWithFailedActionTest < BaseTestCase def setup @model = new_model do def validate super errors[:state] << 'is invalid' unless %w(first_gear).include?(state) end end @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite callbacks = [] @machine.before_transition {callbacks << :before} @machine.after_transition {callbacks << :after} @machine.after_failure {callbacks << :after_failure} @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform @callbacks = callbacks end def test_should_not_be_successful assert !@result end def test_should_not_change_current_state assert_equal 'parked', @record.state end def test_should_not_save_record assert @record.new? end def test_should_run_before_callbacks_and_after_callbacks_with_failures assert_equal [:before, :around_before, :after_failure], @callbacks end end class MachineWithFailedAfterCallbacksTest < BaseTestCase def setup callbacks = [] @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :idling @machine.event :ignite @machine.after_transition {callbacks << :after_1; false} @machine.after_transition {callbacks << :after_2} @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after} @record = @model.new(:state => 'parked') @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling) @result = @transition.perform @callbacks = callbacks end def test_should_be_successful assert @result end def test_should_change_current_state assert_equal 'idling', @record.state end def test_should_save_record assert !@record.new? end def test_should_not_run_further_after_callbacks assert_equal [:around_before, :around_after, :after_1], @callbacks end end class MachineWithValidationsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked @record = @model.new end def test_should_invalidate_using_errors @record.state = 'parked' @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']]) assert_equal ['cannot transition via "park"'], @record.errors.on(:state) end def test_should_auto_prefix_custom_attributes_on_invalidation @machine.invalidate(@record, :event, :invalid) assert_equal ['is invalid'], @record.errors.on(:state_event) end def test_should_clear_errors_on_reset @record.state = 'parked' @record.errors.add(:state, 'is invalid') @machine.reset(@record) assert_nil @record.errors.on(:id) end def test_should_be_valid_if_state_is_known @record.state = 'parked' assert @record.valid? end def test_should_not_be_valid_if_state_is_unknown @record.state = 'invalid' assert !@record.valid? assert_equal ['state is invalid'], @record.errors.full_messages end end class MachineWithValidationsAndCustomAttributeTest < BaseTestCase def setup @model = new_model do alias_method :status, :state alias_method :status=, :state= end @machine = StateMachine::Machine.new(@model, :status, :attribute => :state) @machine.state :parked @record = @model.new end def test_should_add_validation_errors_to_custom_attribute @record.state = 'invalid' assert !@record.valid? assert_equal ['state is invalid'], @record.errors.full_messages @record.state = 'parked' assert @record.valid? end end class MachineErrorsTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @record = @model.new end def test_should_be_able_to_describe_current_errors @record.errors.add(:id, 'cannot be blank') @record.errors.add(:state, 'is invalid') assert_equal ['id cannot be blank', 'state is invalid'], @machine.errors_for(@record).split(', ').sort end def test_should_describe_as_halted_with_no_errors assert_equal 'Transition halted', @machine.errors_for(@record) end end class MachineWithStateDrivenValidationsTest < BaseTestCase def setup @model = new_model do attr_accessor :seatbelt end @machine = StateMachine::Machine.new(@model) @machine.state :first_gear do def validate super if defined?(super) errors[:seatbelt] << 'is not present' if seatbelt.nil? end end @machine.other_states :parked end def test_should_be_valid_if_validation_fails_outside_state_scope record = @model.new(:state => 'parked', :seatbelt => nil) assert record.valid? end def test_should_be_invalid_if_validation_fails_within_state_scope record = @model.new(:state => 'first_gear', :seatbelt => nil) assert !record.valid? end def test_should_be_valid_if_validation_succeeds_within_state_scope record = @model.new(:state => 'first_gear', :seatbelt => true) assert record.valid? end end class MachineWithEventAttributesOnValidationTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert !@record.valid? assert_equal ['state_event is invalid'], @record.errors.full_messages end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert !@record.valid? assert_equal ['state_event cannot transition when idling'], @record.errors.full_messages end def test_should_be_successful_if_event_has_transition assert @record.valid? end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.valid? assert ran_callback end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } begin @record.valid? rescue ArgumentError raise if StateMachine::Transition.pause_supported? end assert ran_callback end def test_should_persist_new_state @record.valid? assert_equal 'idling', @record.state end def test_should_not_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.valid? assert !ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails @model.class_eval do attr_accessor :seatbelt def validate super errors[:seatbelt] << 'is not present' if seatbelt.nil? end end ran_callback = false @machine.after_transition { ran_callback = true } @record.valid? assert !ran_callback end def test_should_run_failure_callbacks_if_validation_fails @model.class_eval do attr_accessor :seatbelt def validate super errors[:seatbelt] << 'is not present' if seatbelt.nil? end end ran_callback = false @machine.after_failure { ran_callback = true } @record.valid? assert ran_callback end def test_should_not_run_around_callbacks_after_yield ran_callback = [false] @machine.around_transition {|block| block.call; ran_callback[0] = true } begin @record.valid? rescue ArgumentError raise if StateMachine::Transition.pause_supported? end assert !ran_callback[0] end def test_should_not_run_around_callbacks_after_yield_with_failures_disabled_if_validation_fails @model.class_eval do attr_accessor :seatbelt def validate super errors[:seatbelt] << 'is not present' if seatbelt.nil? end end ran_callback = [false] @machine.around_transition {|block| block.call; ran_callback[0] = true } begin @record.valid? rescue ArgumentError raise if StateMachine::Transition.pause_supported? end assert !ran_callback[0] end def test_should_not_run_before_transitions_within_transaction @machine.before_transition { self.class.create; raise Sequel::Error::Rollback } begin @record.valid? rescue Sequel::Error::Rollback end assert_equal 1, @model.count end end class MachineWithEventAttributesOnSaveTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @record.state_event = 'invalid' assert !@record.save end def test_should_raise_exception_when_enabled_if_event_is_invalid @record.state_event = 'invalid' @model.raise_on_save_failure = true if defined?(Sequel::BeforeHookFailed) assert_raise(Sequel::BeforeHookFailed) { @record.save } else assert_raise(Sequel::Error) { @record.save } end end def test_should_fail_if_event_has_no_transition @record.state = 'idling' assert !@record.save end def test_should_raise_exception_when_enabled_if_event_has_no_transition @record.state = 'idling' @model.raise_on_save_failure = true if defined?(Sequel::BeforeHookFailed) assert_raise(Sequel::BeforeHookFailed) { @record.save } else assert_raise(Sequel::Error) { @record.save } end end def test_should_be_successful_if_event_has_transition assert @record.save end def test_should_run_before_callbacks ran_callback = false @machine.before_transition { ran_callback = true } @record.save assert ran_callback end def test_should_run_before_callbacks_once before_count = 0 @machine.before_transition { before_count += 1 } @record.save assert_equal 1, before_count end def test_should_run_around_callbacks_before_yield ran_callback = false @machine.around_transition {|block| ran_callback = true; block.call } @record.save assert ran_callback end def test_should_run_around_callbacks_before_yield_once around_before_count = 0 @machine.around_transition {|block| around_before_count += 1; block.call } @record.save assert_equal 1, around_before_count end def test_should_persist_new_state @record.save assert_equal 'idling', @record.state end def test_should_run_after_callbacks ran_callback = false @machine.after_transition { ran_callback = true } @record.save assert ran_callback end def test_should_not_run_after_callbacks_with_failures_disabled_if_fails @model.before_create {|record| false} ran_callback = false @machine.after_transition { ran_callback = true } @record.save assert !ran_callback end def test_should_run_failure_callbacks_if_fails @model.before_create {|record| false} ran_callback = false @machine.after_failure { ran_callback = true } @record.save assert ran_callback end def test_should_run_before_transitions_within_transaction @machine.before_transition { self.class.create; raise Sequel::Error::Rollback } begin @record.save rescue Sequel::Error::Rollback end assert_equal 0, @model.count end def test_should_not_run_around_callbacks_with_failures_disabled_if_fails @model.before_create {|record| false} ran_callback = [false] @machine.around_transition {|block| block.call; ran_callback[0] = true } @record.save assert !ran_callback[0] end def test_should_run_around_callbacks_after_yield ran_callback = [false] @machine.around_transition {|block| block.call; ran_callback[0] = true } @record.save assert ran_callback[0] end def test_should_run_after_transitions_within_transaction @machine.after_transition { self.class.create; raise Sequel::Error::Rollback } @record.save assert_equal 0, @model.count end def test_should_run_around_transition_within_transaction @machine.around_transition {|block| block.call; self.class.create; raise Sequel::Error::Rollback } @record.save assert_equal 0, @model.count end def test_should_allow_additional_transitions_to_new_state_in_after_transitions @machine.event :park do transition :idling => :parked end @machine.after_transition(:on => :ignite) { park } @record.save assert_equal 'parked', @record.state @record.reload assert_equal 'parked', @record.state end def test_should_allow_additional_transitions_to_previous_state_in_after_transitions @machine.event :shift_up do transition :idling => :first_gear end @machine.after_transition(:on => :ignite) { shift_up } @record.save assert_equal 'first_gear', @record.state @record.reload assert_equal 'first_gear', @record.state end def test_should_return_nil_on_manual_rollback @machine.before_transition { raise Sequel::Error::Rollback } assert_equal nil, @record.save end end if defined?(Sequel::VERSION) && Gem::Version.new(Sequel::VERSION) >= Gem::Version.new('3.4.0') class MachineWithEventAttributesOnAutosaveTest < BaseTestCase def setup @vehicle_model = new_model(:vehicle) DB.alter_table(@vehicle_model.table_identifier) do add_column :owner_id, :integer end @vehicle_model.class_eval { get_db_schema(true) } SequelTest.const_set('Vehicle', @vehicle_model) @owner_model = new_model(:owner) do plugin :nested_attributes end SequelTest.const_set('Owner', @owner_model) machine = StateMachine::Machine.new(@vehicle_model) machine.event :ignite do transition :parked => :idling end @owner = @owner_model.create @vehicle = @vehicle_model.create(:state => 'parked', :owner_id => @owner.id) end def test_should_persist_many_association @owner_model.one_to_many :vehicles, :class_name => 'SequelTest::Vehicle' @owner_model.nested_attributes :vehicles @owner.vehicles_attributes = [{:id => @vehicle.id, :state_event => 'ignite'}] @owner.save @vehicle.reload assert_equal 'idling', @vehicle.state end if Gem::Version.new(Sequel::VERSION) >= Gem::Version.new('3.10.0') def test_should_persist_one_association @owner_model.one_to_one :vehicle, :class_name => 'SequelTest::Vehicle' @owner_model.nested_attributes :vehicle @owner.vehicle_attributes = {:id => @vehicle.id, :state_event => 'ignite'} @owner.save @vehicle.reload assert_equal 'idling', @vehicle.state end end def teardown SequelTest.class_eval do remove_const('Vehicle') remove_const('Owner') end super end end end class MachineWithEventAttributesOnCustomActionTest < BaseTestCase def setup @superclass = new_model do def persist save end end @model = Class.new(@superclass) @machine = StateMachine::Machine.new(@model, :action => :persist) @machine.event :ignite do transition :parked => :idling end @record = @model.new @record.state = 'parked' @record.state_event = 'ignite' end def test_should_not_transition_on_valid? @record.valid? assert_equal 'parked', @record.state end def test_should_not_transition_on_save @record.save assert_equal 'parked', @record.state end def test_should_transition_on_custom_action @record.persist assert_equal 'idling', @record.state end end class MachineWithScopesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model) @machine.state :parked, :first_gear @machine.state :idling, :value => lambda {'idling'} end def test_should_create_singular_with_scope assert @model.respond_to?(:with_state) end def test_should_only_include_records_with_state_in_singular_with_scope parked = @model.create :state => 'parked' @model.create :state => 'idling' assert_equal [parked], @model.with_state(:parked).all end def test_should_create_plural_with_scope assert @model.respond_to?(:with_states) end def test_should_only_include_records_with_states_in_plural_with_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked, idling], @model.with_states(:parked, :idling).all end def test_should_allow_lookup_by_string_name parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked, idling], @model.with_states('parked', 'idling').all end def test_should_create_singular_without_scope assert @model.respond_to?(:without_state) end def test_should_only_include_records_without_state_in_singular_without_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [parked], @model.without_state(:idling).all end def test_should_create_plural_without_scope assert @model.respond_to?(:without_states) end def test_should_only_include_records_without_states_in_plural_without_scope parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' first_gear = @model.create :state => 'first_gear' assert_equal [parked, idling], @model.without_states(:first_gear).all end def test_should_allow_chaining_scopes_and_filters parked = @model.create :state => 'parked' idling = @model.create :state => 'idling' assert_equal [idling], @model.without_state(:parked).with_state(:idling).all end def test_should_run_on_tables_with_double_underscores @model = new_model(:foo__bar) @machine = StateMachine::Machine.new(@model) @machine.state :parked, :first_gear @machine.state :idling, :value => lambda {'idling'} parked = @model.create :state => 'parked' @model.create :state => 'idling' assert_equal [parked], @model.with_state(:parked).all end end class MachineWithScopesAndOwnerSubclassTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :state) @subclass = Class.new(@model) @subclass_machine = @subclass.state_machine(:state) {} @subclass_machine.state :parked, :idling, :first_gear end def test_should_only_include_records_with_subclass_states_in_with_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' assert_equal [parked, idling], @subclass.with_states(:parked, :idling).all end def test_should_only_include_records_without_subclass_states_in_without_scope parked = @subclass.create :state => 'parked' idling = @subclass.create :state => 'idling' first_gear = @subclass.create :state => 'first_gear' assert_equal [parked, idling], @subclass.without_states(:first_gear).all end end class MachineWithComplexPluralizationScopesTest < BaseTestCase def setup @model = new_model @machine = StateMachine::Machine.new(@model, :status) end def test_should_create_singular_with_scope assert @model.respond_to?(:with_status) end def test_should_create_plural_with_scope assert @model.respond_to?(:with_statuses) end end class MachineWithScopesAndJoinsTest < BaseTestCase def setup @company = new_model(:company) SequelTest.const_set('Company', @company) @vehicle = new_model(:vehicle) do many_to_one :company, :class => SequelTest::Company end DB.alter_table(@vehicle.table_identifier) do add_column :company_id, :integer end @vehicle.class_eval { get_db_schema(true) } SequelTest.const_set('Vehicle', @vehicle) @company_machine = StateMachine::Machine.new(@company, :initial => :active) @vehicle_machine = StateMachine::Machine.new(@vehicle, :initial => :parked) @vehicle_machine.state :idling @ford = @company.create @mustang = @vehicle.create(:company => @ford) end def test_should_find_records_in_with_scope assert_equal [@mustang], @vehicle.with_states(:parked).join(@company.table_identifier.value, :id => :company_id).filter(:"#{@company.table_identifier.value}__state" => 'active').select(@vehicle.table_identifier.value.to_sym.*).all end def test_should_find_records_in_without_scope assert_equal [@mustang], @vehicle.without_states(:idling).join(@company.table_identifier.value, :id => :company_id).filter(:"#{@company.table_identifier.value}__state" => 'active').select(@vehicle.table_identifier.value.to_sym.*).all end def teardown SequelTest.class_eval do remove_const('Vehicle') remove_const('Company') end end end end state-machine-1.2.0/test/unit/transition_collection_test.rb0000644000175000017500000017221512305405267023555 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class TransitionCollectionTest < Test::Unit::TestCase def test_should_raise_exception_if_invalid_option_specified exception = assert_raise(ArgumentError) {StateMachine::TransitionCollection.new([], :invalid => true)} assert_equal 'Invalid key(s): invalid', exception.message end def test_should_raise_exception_if_multiple_transitions_for_same_attribute_specified @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new exception = assert_raise(ArgumentError) do StateMachine::TransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling), StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) end assert_equal 'Cannot perform multiple transitions in parallel for the same state machine attribute', exception.message end end class TransitionCollectionByDefaultTest < Test::Unit::TestCase def setup @transitions = StateMachine::TransitionCollection.new end def test_should_not_skip_actions assert !@transitions.skip_actions end def test_should_not_skip_after assert !@transitions.skip_after end def test_should_use_transaction assert @transitions.use_transaction end def test_should_be_empty assert @transitions.empty? end end class TransitionCollectionEmptyWithoutBlockTest < Test::Unit::TestCase def setup @transitions = StateMachine::TransitionCollection.new @result = @transitions.perform end def test_should_succeed assert_equal true, @result end end class TransitionCollectionEmptyWithBlockTest < Test::Unit::TestCase def setup @transitions = StateMachine::TransitionCollection.new end def test_should_raise_exception_if_perform_raises_exception assert_raise(ArgumentError) { @transitions.perform { raise ArgumentError } } end def test_should_use_block_result_if_non_boolean assert_equal 1, @transitions.perform { 1 } end def test_should_use_block_result_if_false assert_equal false, @transitions.perform { false } end def test_should_use_block_reslut_if_nil assert_equal nil, @transitions.perform { nil } end end class TransitionCollectionInvalidTest < Test::Unit::TestCase def setup @transitions = StateMachine::TransitionCollection.new([false]) end def test_should_be_empty assert @transitions.empty? end def test_should_not_succeed assert_equal false, @transitions.perform end def test_should_not_run_perform_block ran_block = false @transitions.perform { ran_block = true } assert !ran_block end end class TransitionCollectionPartialInvalidTest < Test::Unit::TestCase def setup @klass = Class.new do attr_accessor :ran_transaction end @callbacks = [] @machine = StateMachine::Machine.new(@klass, :initial => :parked) @machine.state :idling @machine.event :ignite @machine.before_transition {@callbacks << :before} @machine.after_transition {@callbacks << :after} @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} class << @machine def within_transaction(object) object.ran_transaction = true end end @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling), false ]) end def test_should_not_store_invalid_values assert_equal 1, @transitions.length end def test_should_not_succeed assert_equal false, @transitions.perform end def test_should_not_start_transaction assert !@object.ran_transaction end def test_should_not_run_perform_block ran_block = false @transitions.perform { ran_block = true } assert !ran_block end def test_should_not_run_before_callbacks assert !@callbacks.include?(:before) end def test_should_not_persist_states assert_equal 'parked', @object.state end def test_should_not_run_after_callbacks assert !@callbacks.include?(:after) end def test_should_not_run_around_callbacks_before_yield assert !@callbacks.include?(:around_before) end def test_should_not_run_around_callbacks_after_yield assert !@callbacks.include?(:around_after) end end class TransitionCollectionValidTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :persisted def initialize @persisted = nil super @persisted = [] end def state=(value) @persisted << 'state' if @persisted @state = value end def status=(value) @persisted << 'status' if @persisted @status = value end end @state = StateMachine::Machine.new(@klass, :initial => :parked) @state.state :idling @state.event :ignite @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear) @status.state :second_gear @status.event :shift_up @object = @klass.new @result = StateMachine::TransitionCollection.new([ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling), @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear) ]).perform end def test_should_succeed assert_equal true, @result end def test_should_persist_each_state assert_equal 'idling', @object.state assert_equal 'second_gear', @object.status end def test_should_persist_in_order assert_equal ['state', 'status'], @object.persisted end def test_should_store_results_in_transitions assert_nil @state_transition.result assert_nil @status_transition.result end end class TransitionCollectionWithoutTransactionsTest < Test::Unit::TestCase def setup @klass = Class.new do attr_accessor :ran_transaction end @machine = StateMachine::Machine.new(@klass, :initial => :parked) @machine.state :idling @machine.event :ignite class << @machine def within_transaction(object) object.ran_transaction = true end end @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ], :transaction => false) @transitions.perform end def test_should_not_run_within_transaction assert !@object.ran_transaction end end class TransitionCollectionWithTransactionsTest < Test::Unit::TestCase def setup @klass = Class.new do attr_accessor :running_transaction, :cancelled_transaction end @machine = StateMachine::Machine.new(@klass, :initial => :parked) @machine.state :idling @machine.event :ignite class << @machine def within_transaction(object) object.running_transaction = true object.cancelled_transaction = yield == false object.running_transaction = false end end @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ], :transaction => true) end def test_should_run_before_callbacks_within_transaction @machine.before_transition {|object| @in_transaction = object.running_transaction} @transitions.perform assert @in_transaction end def test_should_run_action_within_transaction @transitions.perform { @in_transaction = @object.running_transaction } assert @in_transaction end def test_should_run_after_callbacks_within_transaction @machine.after_transition {|object| @in_transaction = object.running_transaction} @transitions.perform assert @in_transaction end def test_should_cancel_the_transaction_on_before_halt @machine.before_transition {throw :halt} @transitions.perform assert @object.cancelled_transaction end def test_should_cancel_the_transaction_on_action_failure @transitions.perform { false } assert @object.cancelled_transaction end def test_should_not_cancel_the_transaction_on_after_halt @machine.after_transition {throw :halt} @transitions.perform assert !@object.cancelled_transaction end end class TransitionCollectionWithEmptyActionsTest < Test::Unit::TestCase def setup @klass = Class.new @state = StateMachine::Machine.new(@klass, :initial => :parked) @state.state :idling @state.event :ignite @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear) @status.state :second_gear @status.event :shift_up @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling), @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear) ]) @object.state = 'idling' @object.status = 'second_gear' @result = @transitions.perform end def test_should_succeed assert_equal true, @result end def test_should_persist_states assert_equal 'idling', @object.state assert_equal 'second_gear', @object.status end def test_should_store_results_in_transitions assert_nil @state_transition.result assert_nil @status_transition.result end end class TransitionCollectionWithSkippedActionsTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :actions def save_state (@actions ||= []) << :save_state :save_state end def save_status (@actions ||= []) << :save_status :save_status end end @callbacks = [] @state = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save_state) @state.state :idling @state.event :ignite @state.before_transition {@callbacks << :state_before} @state.after_transition {@callbacks << :state_after} @state.around_transition {|block| @callbacks << :state_around_before; block.call; @callbacks << :state_around_after} @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save_status) @status.state :second_gear @status.event :shift_up @status.before_transition {@callbacks << :status_before} @status.after_transition {@callbacks << :status_after} @status.around_transition {|block| @callbacks << :status_around_before; block.call; @callbacks << :status_around_after} @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling), @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear) ], :actions => false) @result = @transitions.perform end def test_should_skip_actions assert_equal true, @transitions.skip_actions end def test_should_succeed assert_equal true, @result end def test_should_persist_states assert_equal 'idling', @object.state assert_equal 'second_gear', @object.status end def test_should_not_run_actions assert_nil @object.actions end def test_should_store_results_in_transitions assert_nil @state_transition.result assert_nil @status_transition.result end def test_should_run_all_callbacks assert_equal [:state_before, :state_around_before, :status_before, :status_around_before, :status_around_after, :status_after, :state_around_after, :state_after], @callbacks end end class TransitionCollectionWithSkippedActionsAndBlockTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save_state) @machine.state :idling @machine.event :ignite @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ @state_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ], :actions => false) @result = @transitions.perform { @ran_block = true; 1 } end def test_should_succeed assert_equal 1, @result end def test_should_persist_states assert_equal 'idling', @object.state end def test_should_run_block assert @ran_block end def test_should_store_results_in_transitions assert_equal 1, @state_transition.result end end class TransitionCollectionWithDuplicateActionsTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :actions def save (@actions ||= []) << :save :save end end @state = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @state.state :idling @state.event :ignite @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save) @status.state :second_gear @status.event :shift_up @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling), @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear) ]) @result = @transitions.perform end def test_should_succeed assert_equal :save, @result end def test_should_persist_states assert_equal 'idling', @object.state assert_equal 'second_gear', @object.status end def test_should_run_action_once assert_equal [:save], @object.actions end def test_should_store_results_in_transitions assert_equal :save, @state_transition.result assert_equal :save, @status_transition.result end end class TransitionCollectionWithDifferentActionsTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :actions def save_state (@actions ||= []) << :save_state :save_state end def save_status (@actions ||= []) << :save_status :save_status end end @state = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save_state) @state.state :idling @state.event :ignite @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save_status) @status.state :second_gear @status.event :shift_up @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling), @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear) ]) end def test_should_succeed assert_equal true, @transitions.perform end def test_should_persist_states @transitions.perform assert_equal 'idling', @object.state assert_equal 'second_gear', @object.status end def test_should_run_actions_in_order @transitions.perform assert_equal [:save_state, :save_status], @object.actions end def test_should_store_results_in_transitions @transitions.perform assert_equal :save_state, @state_transition.result assert_equal :save_status, @status_transition.result end def test_should_not_halt_if_action_fails_for_first_transition @klass.class_eval do remove_method :save_state def save_state (@actions ||= []) << :save_state false end end assert_equal false, @transitions.perform assert_equal [:save_state, :save_status], @object.actions end def test_should_halt_if_action_fails_for_second_transition @klass.class_eval do remove_method :save_status def save_status (@actions ||= []) << :save_status false end end assert_equal false, @transitions.perform assert_equal [:save_state, :save_status], @object.actions end def test_should_rollback_if_action_errors_for_first_transition @klass.class_eval do remove_method :save_state def save_state raise ArgumentError end end begin; @transitions.perform; rescue; end assert_equal 'parked', @object.state assert_equal 'first_gear', @object.status end def test_should_rollback_if_action_errors_for_second_transition @klass.class_eval do remove_method :save_status def save_status raise ArgumentError end end begin; @transitions.perform; rescue; end assert_equal 'parked', @object.state assert_equal 'first_gear', @object.status end def test_should_not_run_after_callbacks_if_action_fails_for_first_transition @klass.class_eval do remove_method :save_state def save_state false end end @callbacks = [] @state.after_transition { @callbacks << :state_after } @state.around_transition {|block| block.call; @callbacks << :state_around } @status.after_transition { @callbacks << :status_after } @status.around_transition {|block| block.call; @callbacks << :status_around } @transitions.perform assert_equal [], @callbacks end def test_should_not_run_after_callbacks_if_action_fails_for_second_transition @klass.class_eval do remove_method :save_status def save_status false end end @callbacks = [] @state.after_transition { @callbacks << :state_after } @state.around_transition {|block| block.call; @callbacks << :state_around } @status.after_transition { @callbacks << :status_after } @status.around_transition {|block| block.call; @callbacks << :status_around } @transitions.perform assert_equal [], @callbacks end def test_should_run_after_failure_callbacks_if_action_fails_for_first_transition @klass.class_eval do remove_method :save_state def save_state false end end @callbacks = [] @state.after_failure { @callbacks << :state_after } @status.after_failure { @callbacks << :status_after } @transitions.perform assert_equal [:status_after, :state_after], @callbacks end def test_should_run_after_failure_callbacks_if_action_fails_for_second_transition @klass.class_eval do remove_method :save_status def save_status false end end @callbacks = [] @state.after_failure { @callbacks << :state_after } @status.after_failure { @callbacks << :status_after } @transitions.perform assert_equal [:status_after, :state_after], @callbacks end end class TransitionCollectionWithMixedActionsTest < Test::Unit::TestCase def setup @klass = Class.new do def save true end end @state = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @state.state :idling @state.event :ignite @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear) @status.state :second_gear @status.event :shift_up @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling), @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear) ]) @result = @transitions.perform end def test_should_succeed assert_equal true, @result end def test_should_persist_states assert_equal 'idling', @object.state assert_equal 'second_gear', @object.status end def test_should_store_results_in_transitions assert_equal true, @state_transition.result assert_nil @status_transition.result end end class TransitionCollectionWithBlockTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :actions def save (@actions ||= []) << :save end end @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save) @state.state :idling @state.event :ignite @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save) @status.state :second_gear @status.event :shift_up @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling), @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear) ]) @result = @transitions.perform { 1 } end def test_should_succeed assert_equal 1, @result end def test_should_persist_states assert_equal 'idling', @object.state assert_equal 'second_gear', @object.status end def test_should_not_run_machine_actions assert_nil @object.actions end def test_should_use_result_as_transition_result assert_equal 1, @state_transition.result assert_equal 1, @status_transition.result end end class TransitionCollectionWithActionFailedTest < Test::Unit::TestCase def setup @klass = Class.new do def save false end end @before_count = 0 @around_before_count = 0 @after_count = 0 @around_after_count = 0 @failure_count = 0 @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @machine.state :idling @machine.event :ignite @machine.before_transition {@before_count += 1} @machine.after_transition {@after_count += 1} @machine.around_transition {|block| @around_before_count += 1; block.call; @around_after_count += 1} @machine.after_failure {@failure_count += 1} @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) @result = @transitions.perform end def test_should_not_succeed assert_equal false, @result end def test_should_not_persist_state assert_equal 'parked', @object.state end def test_should_run_before_callbacks assert_equal 1, @before_count end def test_should_run_around_callbacks_before_yield assert_equal 1, @around_before_count end def test_should_not_run_after_callbacks assert_equal 0, @after_count end def test_should_not_run_around_callbacks assert_equal 0, @around_after_count end def test_should_run_failure_callbacks assert_equal 1, @failure_count end end class TransitionCollectionWithActionErrorTest < Test::Unit::TestCase def setup @klass = Class.new do def save raise ArgumentError end end @before_count = 0 @around_before_count = 0 @after_count = 0 @around_after_count = 0 @failure_count = 0 @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @machine.state :idling @machine.event :ignite @machine.before_transition {@before_count += 1} @machine.after_transition {@after_count += 1} @machine.around_transition {|block| @around_before_count += 1; block.call; @around_after_count += 1} @machine.after_failure {@failure_count += 1} @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) @raised = true begin @transitions.perform @raised = false rescue ArgumentError end end def test_should_not_catch_exception assert @raised end def test_should_not_persist_state assert_equal 'parked', @object.state end def test_should_run_before_callbacks assert_equal 1, @before_count end def test_should_run_around_callbacks_before_yield assert_equal 1, @around_before_count end def test_should_not_run_after_callbacks assert_equal 0, @after_count end def test_should_not_run_around_callbacks_after_yield assert_equal 0, @around_after_count end def test_should_not_run_failure_callbacks assert_equal 0, @failure_count end end class TransitionCollectionWithCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :saved def save @saved = true end end @before_callbacks = [] @after_callbacks = [] @state = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @state.state :idling @state.event :ignite @state.before_transition {@before_callbacks << :state_before} @state.after_transition {@after_callbacks << :state_after} @state.around_transition {|block| @before_callbacks << :state_around; block.call; @after_callbacks << :state_around} @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save) @status.state :second_gear @status.event :shift_up @status.before_transition {@before_callbacks << :status_before} @status.after_transition {@after_callbacks << :status_after} @status.around_transition {|block| @before_callbacks << :status_around; block.call; @after_callbacks << :status_around} @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling), StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear) ]) end def test_should_run_before_callbacks_in_order @transitions.perform assert_equal [:state_before, :state_around, :status_before, :status_around], @before_callbacks end def test_should_halt_if_before_callback_halted_for_first_transition @state.before_transition {throw :halt} assert_equal false, @transitions.perform assert_equal [:state_before, :state_around], @before_callbacks end def test_should_halt_if_before_callback_halted_for_second_transition @status.before_transition {throw :halt} assert_equal false, @transitions.perform assert_equal [:state_before, :state_around, :status_before, :status_around], @before_callbacks end def test_should_halt_if_around_callback_halted_before_yield_for_first_transition @state.around_transition {throw :halt} assert_equal false, @transitions.perform assert_equal [:state_before, :state_around], @before_callbacks end def test_should_halt_if_around_callback_halted_before_yield_for_second_transition @status.around_transition {throw :halt} assert_equal false, @transitions.perform assert_equal [:state_before, :state_around, :status_before, :status_around], @before_callbacks end def test_should_run_after_callbacks_in_reverse_order @transitions.perform assert_equal [:status_around, :status_after, :state_around, :state_after], @after_callbacks end def test_should_not_halt_if_after_callback_halted_for_first_transition @state.after_transition {throw :halt} assert_equal true, @transitions.perform assert_equal [:status_around, :status_after, :state_around, :state_after], @after_callbacks end def test_should_not_halt_if_around_callback_halted_for_second_transition @status.around_transition {|block| block.call; throw :halt} assert_equal true, @transitions.perform assert_equal [:state_around, :state_after], @after_callbacks end def test_should_run_before_callbacks_before_persisting_the_state @state.before_transition {|object| @before_state = object.state} @state.around_transition {|object, transition, block| @around_state = object.state; block.call} @transitions.perform assert_equal 'parked', @before_state assert_equal 'parked', @around_state end def test_should_persist_state_before_running_action @klass.class_eval do attr_reader :saved_on_persist def state=(value) @state = value @saved_on_persist = saved end end @transitions.perform assert !@object.saved_on_persist end def test_should_persist_state_before_running_action_block @klass.class_eval do attr_writer :saved attr_reader :saved_on_persist def state=(value) @state = value @saved_on_persist = saved end end @transitions.perform { @object.saved = true } assert !@object.saved_on_persist end def test_should_run_after_callbacks_after_running_the_action @state.after_transition {|object| @after_saved = object.saved} @state.around_transition {|object, transition, block| block.call; @around_saved = object.saved} @transitions.perform assert @after_saved assert @around_saved end end class TransitionCollectionWithBeforeCallbackHaltTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :saved def save @saved = true end end @before_count = 0 @after_count = 0 @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @machine.state :idling @machine.event :ignite @machine.before_transition {@before_count += 1; throw :halt} @machine.before_transition {@before_count += 1} @machine.after_transition {@after_count += 1} @machine.around_transition {|block| @before_count += 1; block.call; @after_count += 1} @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) @result = @transitions.perform end def test_should_not_succeed assert_equal false, @result end def test_should_not_persist_state assert_equal 'parked', @object.state end def test_should_not_run_action assert !@object.saved end def test_should_not_run_further_before_callbacks assert_equal 1, @before_count end def test_should_not_run_after_callbacks assert_equal 0, @after_count end end class TransitionCollectionWithAfterCallbackHaltTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :saved def save @saved = true end end @before_count = 0 @after_count = 0 @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @machine.state :idling @machine.event :ignite @machine.before_transition {@before_count += 1} @machine.after_transition {@after_count += 1; throw :halt} @machine.after_transition {@after_count += 1} @machine.around_transition {|block| @before_count += 1; block.call; @after_count += 1} @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) @result = @transitions.perform end def test_should_succeed assert_equal true, @result end def test_should_persist_state assert_equal 'idling', @object.state end def test_should_run_before_callbacks assert_equal 2, @before_count end def test_should_not_run_further_after_callbacks assert_equal 2, @after_count end end class TransitionCollectionWithSkippedAfterCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @callbacks = [] @machine = StateMachine::Machine.new(@klass, :initial => :parked) @machine.state :idling @machine.event :ignite @machine.after_transition {@callbacks << :after} @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ], :after => false) @result = @transitions.perform end def test_should_succeed assert_equal true, @result end def test_should_not_run_after_callbacks assert !@callbacks.include?(:after) end def test_should_run_after_callbacks_on_subsequent_perform StateMachine::TransitionCollection.new([@transition]).perform assert @callbacks.include?(:after) end end if StateMachine::Transition.pause_supported? class TransitionCollectionWithSkippedAfterCallbacksAndAroundCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @callbacks = [] @machine = StateMachine::Machine.new(@klass, :initial => :parked) @machine.state :idling @machine.event :ignite @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ], :after => false) @result = @transitions.perform end def test_should_succeed assert_equal true, @result end def test_should_not_run_around_callbacks_after_yield assert !@callbacks.include?(:around_after) end def test_should_run_around_callbacks_after_yield_on_subsequent_perform StateMachine::TransitionCollection.new([@transition]).perform assert @callbacks.include?(:around_after) end def test_should_not_rerun_around_callbacks_before_yield_on_subsequent_perform @callbacks = [] StateMachine::TransitionCollection.new([@transition]).perform assert !@callbacks.include?(:around_before) end end else class TransitionCollectionWithSkippedAfterCallbacksAndAroundCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @callbacks = [] @machine = StateMachine::Machine.new(@klass, :initial => :parked) @machine.state :idling @machine.event :ignite @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after} @object = @klass.new @transitions = StateMachine::TransitionCollection.new([ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ], :after => false) end def test_should_raise_exception assert_raise(ArgumentError) { @transitions.perform } end end end class TransitionCollectionWithActionHookBaseTest < Test::Unit::TestCase def setup @superclass = Class.new do def save true end end @klass = Class.new(@superclass) do attr_reader :saved, :state_on_save, :state_event_on_save, :state_event_transition_on_save def save @saved = true @state_on_save = state @state_event_on_save = state_event @state_event_transition_on_save = state_event_transition super end end @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @machine.state :idling @machine.event :ignite @object = @klass.new @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def default_test end end class TransitionCollectionWithActionHookAndSkippedActionTest < TransitionCollectionWithActionHookBaseTest def setup super @result = StateMachine::TransitionCollection.new([@transition], :actions => false).perform end def test_should_succeed assert_equal true, @result end def test_should_not_run_action assert !@object.saved end end class TransitionCollectionWithActionHookAndSkippedAfterCallbacksTest < TransitionCollectionWithActionHookBaseTest def setup super @result = StateMachine::TransitionCollection.new([@transition], :after => false).perform end def test_should_succeed assert_equal true, @result end def test_should_run_action assert @object.saved end def test_should_have_already_persisted_when_running_action assert_equal 'idling', @object.state_on_save end def test_should_not_have_event_during_action assert_nil @object.state_event_on_save end def test_should_not_write_event assert_nil @object.state_event end def test_should_not_have_event_transition_during_save assert_nil @object.state_event_transition_on_save end def test_should_not_write_event_attribute assert_nil @object.send(:state_event_transition) end end class TransitionCollectionWithActionHookAndBlockTest < TransitionCollectionWithActionHookBaseTest def setup super @result = StateMachine::TransitionCollection.new([@transition]).perform { true } end def test_should_succeed assert_equal true, @result end def test_should_not_run_action assert !@object.saved end end class TransitionCollectionWithActionHookInvalidTest < TransitionCollectionWithActionHookBaseTest def setup super @result = StateMachine::TransitionCollection.new([@transition, nil]).perform end def test_should_not_succeed assert_equal false, @result end def test_should_not_run_action assert !@object.saved end end class TransitionCollectionWithActionHookWithNilActionTest < TransitionCollectionWithActionHookBaseTest def setup super @machine = StateMachine::Machine.new(@klass, :status, :initial => :first_gear) @machine.state :second_gear @machine.event :shift_up @result = StateMachine::TransitionCollection.new([@transition, StateMachine::Transition.new(@object, @machine, :shift_up, :first_gear, :second_gear)]).perform end def test_should_succeed assert_equal true, @result end def test_should_run_action assert @object.saved end def test_should_have_already_persisted_when_running_action assert_equal 'idling', @object.state_on_save end def test_should_not_have_event_during_action assert_nil @object.state_event_on_save end def test_should_not_write_event assert_nil @object.state_event end def test_should_not_have_event_transition_during_save assert_nil @object.state_event_transition_on_save end def test_should_not_write_event_attribute assert_nil @object.send(:state_event_transition) end end class TransitionCollectionWithActionHookWithDifferentActionsTest < TransitionCollectionWithActionHookBaseTest def setup super @klass.class_eval do def save_status true end end @machine = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save_status) @machine.state :second_gear @machine.event :shift_up @result = StateMachine::TransitionCollection.new([@transition, StateMachine::Transition.new(@object, @machine, :shift_up, :first_gear, :second_gear)]).perform end def test_should_succeed assert_equal true, @result end def test_should_run_action assert @object.saved end def test_should_have_already_persisted_when_running_action assert_equal 'idling', @object.state_on_save end def test_should_not_have_event_during_action assert_nil @object.state_event_on_save end def test_should_not_write_event assert_nil @object.state_event end def test_should_not_have_event_transition_during_save assert_nil @object.state_event_transition_on_save end def test_should_not_write_event_attribute assert_nil @object.send(:state_event_transition) end end class TransitionCollectionWithActionHookTest < TransitionCollectionWithActionHookBaseTest def setup super @result = StateMachine::TransitionCollection.new([@transition]).perform end def test_should_succeed assert_equal true, @result end def test_should_run_action assert @object.saved end def test_should_not_have_already_persisted_when_running_action assert_equal 'parked', @object.state_on_save end def test_should_persist assert_equal 'idling', @object.state end def test_should_not_have_event_during_action assert_nil @object.state_event_on_save end def test_should_not_write_event assert_nil @object.state_event end def test_should_have_event_transition_during_action assert_equal @transition, @object.state_event_transition_on_save end def test_should_not_write_event_transition assert_nil @object.send(:state_event_transition) end def test_should_mark_event_transition_as_transient assert @transition.transient? end end class TransitionCollectionWithActionHookMultipleTest < TransitionCollectionWithActionHookBaseTest def setup super @status_machine = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save) @status_machine.state :second_gear @status_machine.event :shift_up @klass.class_eval do attr_reader :status_on_save, :status_event_on_save, :status_event_transition_on_save remove_method :save def save @saved = true @state_on_save = state @state_event_on_save = state_event @state_event_transition_on_save = state_event_transition @status_on_save = status @status_event_on_save = status_event @status_event_transition_on_save = status_event_transition super 1 end end @object = @klass.new @state_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) @status_transition = StateMachine::Transition.new(@object, @status_machine, :shift_up, :first_gear, :second_gear) @result = StateMachine::TransitionCollection.new([@state_transition, @status_transition]).perform end def test_should_succeed assert_equal 1, @result end def test_should_run_action assert @object.saved end def test_should_not_have_already_persisted_when_running_action assert_equal 'parked', @object.state_on_save assert_equal 'first_gear', @object.status_on_save end def test_should_persist assert_equal 'idling', @object.state assert_equal 'second_gear', @object.status end def test_should_not_have_events_during_action assert_nil @object.state_event_on_save assert_nil @object.status_event_on_save end def test_should_not_write_events assert_nil @object.state_event assert_nil @object.status_event end def test_should_have_event_transitions_during_action assert_equal @state_transition, @object.state_event_transition_on_save assert_equal @status_transition, @object.status_event_transition_on_save end def test_should_not_write_event_transitions assert_nil @object.send(:state_event_transition) assert_nil @object.send(:status_event_transition) end def test_should_mark_event_transitions_as_transient assert @state_transition.transient? assert @status_transition.transient? end end class TransitionCollectionWithActionHookErrorTest < TransitionCollectionWithActionHookBaseTest def setup super @superclass.class_eval do remove_method :save def save raise ArgumentError end end begin; StateMachine::TransitionCollection.new([@transition]).perform; rescue; end end def test_should_not_write_event assert_nil @object.state_event end def test_should_not_write_event_transition assert_nil @object.send(:state_event_transition) end end class AttributeTransitionCollectionByDefaultTest < Test::Unit::TestCase def setup @transitions = StateMachine::AttributeTransitionCollection.new end def test_should_skip_actions assert @transitions.skip_actions end def test_should_not_skip_after assert !@transitions.skip_after end def test_should_not_use_transaction assert !@transitions.use_transaction end def test_should_be_empty assert @transitions.empty? end end class AttributeTransitionCollectionWithEventsTest < Test::Unit::TestCase def setup @klass = Class.new @state = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @state.state :idling @state.event :ignite @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save) @status.state :second_gear @status.event :shift_up @object = @klass.new @object.state_event = 'ignite' @object.status_event = 'shift_up' @transitions = StateMachine::AttributeTransitionCollection.new([ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling), @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear) ]) @result = @transitions.perform end def test_should_succeed assert_equal true, @result end def test_should_persist_states assert_equal 'idling', @object.state assert_equal 'second_gear', @object.status end def test_should_clear_events assert_nil @object.state_event assert_nil @object.status_event end def test_should_not_write_event_transitions assert_nil @object.send(:state_event_transition) assert_nil @object.send(:status_event_transition) end end class AttributeTransitionCollectionWithEventTransitionsTest < Test::Unit::TestCase def setup @klass = Class.new @state = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @state.state :idling @state.event :ignite @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save) @status.state :second_gear @status.event :shift_up @object = @klass.new @object.send(:state_event_transition=, @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)) @object.send(:status_event_transition=, @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)) @transitions = StateMachine::AttributeTransitionCollection.new([@state_transition, @status_transition]) @result = @transitions.perform end def test_should_succeed assert_equal true, @result end def test_should_persist_states assert_equal 'idling', @object.state assert_equal 'second_gear', @object.status end def test_should_not_write_events assert_nil @object.state_event assert_nil @object.status_event end def test_should_clear_event_transitions assert_nil @object.send(:state_event_transition) assert_nil @object.send(:status_event_transition) end end class AttributeTransitionCollectionWithActionFailedTest < Test::Unit::TestCase def setup @klass = Class.new @state = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @state.state :idling @state.event :ignite @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save) @status.state :second_gear @status.event :shift_up @object = @klass.new @object.state_event = 'ignite' @object.status_event = 'shift_up' @transitions = StateMachine::AttributeTransitionCollection.new([ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling), @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear) ]) @result = @transitions.perform { false } end def test_should_not_succeed assert_equal false, @result end def test_should_not_persist_states assert_equal 'parked', @object.state assert_equal 'first_gear', @object.status end def test_should_not_clear_events assert_equal :ignite, @object.state_event assert_equal :shift_up, @object.status_event end def test_should_not_write_event_transitions assert_nil @object.send(:state_event_transition) assert_nil @object.send(:status_event_transition) end end class AttributeTransitionCollectionWithActionErrorTest < Test::Unit::TestCase def setup @klass = Class.new @state = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @state.state :idling @state.event :ignite @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save) @status.state :second_gear @status.event :shift_up @object = @klass.new @object.state_event = 'ignite' @object.status_event = 'shift_up' @transitions = StateMachine::AttributeTransitionCollection.new([ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling), @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear) ]) begin; @transitions.perform { raise ArgumentError }; rescue; end end def test_should_not_persist_states assert_equal 'parked', @object.state assert_equal 'first_gear', @object.status end def test_should_not_clear_events assert_equal :ignite, @object.state_event assert_equal :shift_up, @object.status_event end def test_should_not_write_event_transitions assert_nil @object.send(:state_event_transition) assert_nil @object.send(:status_event_transition) end end class AttributeTransitionCollectionWithCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @state = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @state.state :idling @state.event :ignite @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save) @status.state :second_gear @status.event :shift_up @object = @klass.new @transitions = StateMachine::AttributeTransitionCollection.new([ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling), @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear) ]) end def test_should_not_have_events_during_before_callbacks @state.before_transition {|object, transition| @before_state_event = object.state_event } @state.around_transition {|object, transition, block| @around_state_event = object.state_event; block.call } @transitions.perform assert_nil @before_state_event assert_nil @around_state_event end def test_should_not_have_events_during_action @transitions.perform { @state_event = @object.state_event } assert_nil @state_event end def test_should_not_have_events_during_after_callbacks @state.after_transition {|object, transition| @after_state_event = object.state_event } @state.around_transition {|object, transition, block| block.call; @around_state_event = object.state_event } @transitions.perform assert_nil @after_state_event assert_nil @around_state_event end def test_should_not_have_event_transitions_during_before_callbacks @state.before_transition {|object, transition| @state_event_transition = object.send(:state_event_transition) } @transitions.perform assert_nil @state_event_transition end def test_should_not_have_event_transitions_during_action @transitions.perform { @state_event_transition = @object.send(:state_event_transition) } assert_nil @state_event_transition end def test_should_not_have_event_transitions_during_after_callbacks @state.after_transition {|object, transition| @after_state_event_transition = object.send(:state_event_transition) } @state.around_transition {|object, transition, block| block.call; @around_state_event_transition = object.send(:state_event_transition) } @transitions.perform assert_nil @after_state_event_transition assert_nil @around_state_event_transition end end class AttributeTransitionCollectionWithBeforeCallbackHaltTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @machine.state :idling @machine.event :ignite @machine.before_transition {throw :halt} @object = @klass.new @object.state_event = 'ignite' @transitions = StateMachine::AttributeTransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) @result = @transitions.perform end def test_should_not_succeed assert_equal false, @result end def test_should_not_clear_event assert_equal :ignite, @object.state_event end def test_should_not_write_event_transition assert_nil @object.send(:state_event_transition) end end class AttributeTransitionCollectionWithBeforeCallbackErrorTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @machine.state :idling @machine.event :ignite @machine.before_transition {raise ArgumentError} @object = @klass.new @object.state_event = 'ignite' @transitions = StateMachine::AttributeTransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) begin; @transitions.perform; rescue; end end def test_should_not_clear_event assert_equal :ignite, @object.state_event end def test_should_not_write_event_transition assert_nil @object.send(:state_event_transition) end end class AttributeTransitionCollectionWithAroundCallbackBeforeYieldHaltTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @machine.state :idling @machine.event :ignite @machine.around_transition {throw :halt} @object = @klass.new @object.state_event = 'ignite' @transitions = StateMachine::AttributeTransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) @result = @transitions.perform end def test_should_not_succeed assert_equal false, @result end def test_should_not_clear_event assert_equal :ignite, @object.state_event end def test_should_not_write_event_transition assert_nil @object.send(:state_event_transition) end end class AttributeTransitionCollectionWithAroundAfterYieldCallbackErrorTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @machine.state :idling @machine.event :ignite @machine.before_transition {raise ArgumentError} @object = @klass.new @object.state_event = 'ignite' @transitions = StateMachine::AttributeTransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) begin; @transitions.perform; rescue; end end def test_should_not_clear_event assert_equal :ignite, @object.state_event end def test_should_not_write_event_transition assert_nil @object.send(:state_event_transition) end end class AttributeTransitionCollectionWithSkippedAfterCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @state = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @state.state :idling @state.event :ignite @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save) @status.state :second_gear @status.event :shift_up @object = @klass.new @object.state_event = 'ignite' @object.status_event = 'shift_up' @transitions = StateMachine::AttributeTransitionCollection.new([ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling), @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear) ], :after => false) end def test_should_clear_events @transitions.perform assert_nil @object.state_event assert_nil @object.status_event end def test_should_write_event_transitions_if_success @transitions.perform { true } assert_equal @state_transition, @object.send(:state_event_transition) assert_equal @status_transition, @object.send(:status_event_transition) end def test_should_not_write_event_transitions_if_failed @transitions.perform { false } assert_nil @object.send(:state_event_transition) assert_nil @object.send(:status_event_transition) end end class AttributeTransitionCollectionWithAfterCallbackHaltTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @machine.state :idling @machine.event :ignite @machine.after_transition {throw :halt} @object = @klass.new @object.state_event = 'ignite' @transitions = StateMachine::AttributeTransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) @result = @transitions.perform end def test_should_succeed assert_equal true, @result end def test_should_clear_event assert_nil @object.state_event end def test_should_not_write_event_transition assert_nil @object.send(:state_event_transition) end end class AttributeTransitionCollectionWithAfterCallbackErrorTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @machine.state :idling @machine.event :ignite @machine.after_transition {raise ArgumentError} @object = @klass.new @object.state_event = 'ignite' @transitions = StateMachine::AttributeTransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) begin; @transitions.perform; rescue; end end def test_should_clear_event assert_nil @object.state_event end def test_should_not_write_event_transition assert_nil @object.send(:state_event_transition) end end class AttributeTransitionCollectionWithAroundCallbackAfterYieldHaltTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @machine.state :idling @machine.event :ignite @machine.around_transition {|block| block.call; throw :halt} @object = @klass.new @object.state_event = 'ignite' @transitions = StateMachine::AttributeTransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) @result = @transitions.perform end def test_should_succeed assert_equal true, @result end def test_should_clear_event assert_nil @object.state_event end def test_should_not_write_event_transition assert_nil @object.send(:state_event_transition) end end class AttributeTransitionCollectionWithAroundCallbackAfterYieldErrorTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @machine.state :idling @machine.event :ignite @machine.around_transition {|block| block.call; raise ArgumentError} @object = @klass.new @object.state_event = 'ignite' @transitions = StateMachine::AttributeTransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) begin; @transitions.perform; rescue; end end def test_should_clear_event assert_nil @object.state_event end def test_should_not_write_event_transition assert_nil @object.send(:state_event_transition) end end class AttributeTransitionCollectionMarshallingTest < Test::Unit::TestCase def setup @klass = Class.new self.class.const_set('Example', @klass) @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @machine.state :idling @machine.event :ignite @object = @klass.new @object.state_event = 'ignite' end def test_should_marshal_during_before_callbacks @machine.before_transition {|object, transition| Marshal.dump(object)} assert_nothing_raised do transitions(:after => false).perform { true } transitions.perform { true } end end def test_should_marshal_during_action assert_nothing_raised do transitions(:after => false).perform do Marshal.dump(@object) true end transitions.perform do Marshal.dump(@object) true end end end def test_should_marshal_during_after_callbacks @machine.after_transition {|object, transition| Marshal.dump(object)} assert_nothing_raised do transitions(:after => false).perform { true } transitions.perform { true } end end if StateMachine::Transition.pause_supported? def test_should_marshal_during_around_callbacks_before_yield @machine.around_transition {|object, transition, block| Marshal.dump(object); block.call} assert_nothing_raised do transitions(:after => false).perform { true } transitions.perform { true } end end def test_should_marshal_during_around_callbacks_after_yield @machine.around_transition {|object, transition, block| block.call; Marshal.dump(object)} assert_nothing_raised do transitions(:after => false).perform { true } transitions.perform { true } end end end def teardown self.class.send(:remove_const, 'Example') end private def transitions(options = {}) StateMachine::AttributeTransitionCollection.new([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ], options) end end state-machine-1.2.0/test/unit/machine_test.rb0000644000175000017500000030173012305405267020550 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class MachineByDefaultTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @object = @klass.new end def test_should_have_an_owner_class assert_equal @klass, @machine.owner_class end def test_should_have_a_name assert_equal :state, @machine.name end def test_should_have_an_attribute assert_equal :state, @machine.attribute end def test_should_prefix_custom_attributes_with_attribute assert_equal :state_event, @machine.attribute(:event) end def test_should_have_an_initial_state assert_not_nil @machine.initial_state(@object) end def test_should_have_a_nil_initial_state assert_nil @machine.initial_state(@object).value end def test_should_not_have_any_events assert !@machine.events.any? end def test_should_not_have_any_before_callbacks assert @machine.callbacks[:before].empty? end def test_should_not_have_any_after_callbacks assert @machine.callbacks[:after].empty? end def test_should_not_have_any_failure_callbacks assert @machine.callbacks[:failure].empty? end def test_should_not_have_an_action assert_nil @machine.action end def test_should_use_tranactions assert_equal true, @machine.use_transactions end def test_should_not_have_a_namespace assert_nil @machine.namespace end def test_should_have_a_nil_state assert_equal [nil], @machine.states.keys end def test_should_set_initial_on_nil_state assert @machine.state(nil).initial end def test_should_generate_default_messages assert_equal 'is invalid', @machine.generate_message(:invalid) assert_equal 'cannot transition when parked', @machine.generate_message(:invalid_event, [[:state, :parked]]) assert_equal 'cannot transition via "park"', @machine.generate_message(:invalid_transition, [[:event, :park]]) end def test_should_not_be_extended_by_the_base_integration assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::Base) end def test_should_not_be_extended_by_the_active_model_integration assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::ActiveModel) end def test_should_not_be_extended_by_the_active_record_integration assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::ActiveRecord) end def test_should_not_be_extended_by_the_datamapper_integration assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::DataMapper) end def test_should_not_be_extended_by_the_mongo_mapper_integration assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::MongoMapper) end def test_should_not_be_extended_by_the_sequel_integration assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::Sequel) end def test_should_define_a_reader_attribute_for_the_attribute assert @object.respond_to?(:state) end def test_should_define_a_writer_attribute_for_the_attribute assert @object.respond_to?(:state=) end def test_should_define_a_predicate_for_the_attribute assert @object.respond_to?(:state?) end def test_should_define_a_name_reader_for_the_attribute assert @object.respond_to?(:state_name) end def test_should_define_an_event_reader_for_the_attribute assert @object.respond_to?(:state_events) end def test_should_define_a_transition_reader_for_the_attribute assert @object.respond_to?(:state_transitions) end def test_should_define_a_path_reader_for_the_attribute assert @object.respond_to?(:state_paths) end def test_should_define_an_event_runner_for_the_attribute assert @object.respond_to?(:fire_state_event) end def test_should_not_define_an_event_attribute_reader assert !@object.respond_to?(:state_event) end def test_should_not_define_an_event_attribute_writer assert !@object.respond_to?(:state_event=) end def test_should_not_define_an_event_transition_attribute_reader assert !@object.respond_to?(:state_event_transition) end def test_should_not_define_an_event_transition_attribute_writer assert !@object.respond_to?(:state_event_transition=) end def test_should_define_a_human_attribute_name_reader_for_the_attribute assert @klass.respond_to?(:human_state_name) end def test_should_define_a_human_event_name_reader_for_the_attribute assert @klass.respond_to?(:human_state_event_name) end def test_should_not_define_singular_with_scope assert !@klass.respond_to?(:with_state) end def test_should_not_define_singular_without_scope assert !@klass.respond_to?(:without_state) end def test_should_not_define_plural_with_scope assert !@klass.respond_to?(:with_states) end def test_should_not_define_plural_without_scope assert !@klass.respond_to?(:without_states) end def test_should_extend_owner_class_with_class_methods assert((class << @klass; ancestors; end).include?(StateMachine::ClassMethods)) end def test_should_include_instance_methods_in_owner_class assert @klass.included_modules.include?(StateMachine::InstanceMethods) end def test_should_define_state_machines_reader expected = {:state => @machine} assert_equal expected, @klass.state_machines end end class MachineWithCustomNameTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :status) @object = @klass.new end def test_should_use_custom_name assert_equal :status, @machine.name end def test_should_use_custom_name_for_attribute assert_equal :status, @machine.attribute end def test_should_prefix_custom_attributes_with_custom_name assert_equal :status_event, @machine.attribute(:event) end def test_should_define_a_reader_attribute_for_the_attribute assert @object.respond_to?(:status) end def test_should_define_a_writer_attribute_for_the_attribute assert @object.respond_to?(:status=) end def test_should_define_a_predicate_for_the_attribute assert @object.respond_to?(:status?) end def test_should_define_a_name_reader_for_the_attribute assert @object.respond_to?(:status_name) end def test_should_define_an_event_reader_for_the_attribute assert @object.respond_to?(:status_events) end def test_should_define_a_transition_reader_for_the_attribute assert @object.respond_to?(:status_transitions) end def test_should_define_an_event_runner_for_the_attribute assert @object.respond_to?(:fire_status_event) end def test_should_define_a_human_attribute_name_reader_for_the_attribute assert @klass.respond_to?(:human_status_name) end def test_should_define_a_human_event_name_reader_for_the_attribute assert @klass.respond_to?(:human_status_event_name) end end class MachineWithoutInitializationTest < Test::Unit::TestCase def setup @klass = Class.new do def initialize(attributes = {}) attributes.each {|attr, value| send("#{attr}=", value)} super() end end @machine = StateMachine::Machine.new(@klass, :initial => :parked, :initialize => false) end def test_should_not_have_an_initial_state object = @klass.new assert_nil object.state end def test_should_still_allow_manual_initialization @klass.send(:include, Module.new do def initialize(attributes = {}) super() initialize_state_machines end end) object = @klass.new assert_equal 'parked', object.state end end class MachineWithStaticInitialStateTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) end def test_should_not_have_dynamic_initial_state assert !@machine.dynamic_initial_state? end def test_should_have_an_initial_state object = @klass.new assert_equal 'parked', @machine.initial_state(object).value end def test_should_write_to_attribute_when_initializing_state object = @klass.allocate @machine.initialize_state(object) assert_equal 'parked', object.state end def test_should_set_initial_on_state_object assert @machine.state(:parked).initial end def test_should_set_initial_state_on_created_object assert_equal 'parked', @klass.new.state end def test_not_set_initial_state_even_if_not_empty @klass.class_eval do def initialize(attributes = {}) self.state = 'idling' super() end end object = @klass.new assert_equal 'idling', object.state end def test_should_set_initial_state_prior_to_initialization base = Class.new do attr_accessor :state_on_init def initialize self.state_on_init = state end end klass = Class.new(base) StateMachine::Machine.new(klass, :initial => :parked) assert_equal 'parked', klass.new.state_on_init end def test_should_be_included_in_known_states assert_equal [:parked], @machine.states.keys end end class MachineWithDynamicInitialStateTest < Test::Unit::TestCase def setup @klass = Class.new do attr_accessor :initial_state end @machine = StateMachine::Machine.new(@klass, :initial => lambda {|object| object.initial_state || :default}) @machine.state :parked, :idling, :default @object = @klass.new end def test_should_have_dynamic_initial_state assert @machine.dynamic_initial_state? end def test_should_use_the_record_for_determining_the_initial_state @object.initial_state = :parked assert_equal :parked, @machine.initial_state(@object).name @object.initial_state = :idling assert_equal :idling, @machine.initial_state(@object).name end def test_should_write_to_attribute_when_initializing_state object = @klass.allocate object.initial_state = :parked @machine.initialize_state(object) assert_equal 'parked', object.state end def test_should_set_initial_state_on_created_object assert_equal 'default', @object.state end def test_should_not_set_initial_state_even_if_not_empty @klass.class_eval do def initialize(attributes = {}) self.state = 'parked' super() end end object = @klass.new assert_equal 'parked', object.state end def test_should_set_initial_state_after_initialization base = Class.new do attr_accessor :state_on_init def initialize self.state_on_init = state end end klass = Class.new(base) machine = StateMachine::Machine.new(klass, :initial => lambda {|object| :parked}) machine.state :parked assert_nil klass.new.state_on_init end def test_should_not_be_included_in_known_states assert_equal [:parked, :idling, :default], @machine.states.map {|state| state.name} end end class MachineStateInitializationTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :initialize => false) @object = @klass.new @object.state = nil end def test_should_set_states_if_nil @machine.initialize_state(@object) assert_equal 'parked', @object.state end def test_should_set_states_if_empty @object.state = '' @machine.initialize_state(@object) assert_equal 'parked', @object.state end def test_should_not_set_states_if_not_empty @object.state = 'idling' @machine.initialize_state(@object) assert_equal 'idling', @object.state end def test_should_set_states_if_not_empty_and_forced @object.state = 'idling' @machine.initialize_state(@object, :force => true) assert_equal 'parked', @object.state end def test_should_not_set_state_if_nil_and_nil_is_valid_state @machine.state :initial, :value => nil @machine.initialize_state(@object) assert_nil @object.state end def test_should_write_to_hash_if_specified @machine.initialize_state(@object, :to => hash = {}) assert_equal({'state' => 'parked'}, hash) end def test_should_not_write_to_object_if_writing_to_hash @machine.initialize_state(@object, :to => {}) assert_nil @object.state end end class MachineWithCustomActionTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new, :action => :save) end def test_should_use_the_custom_action assert_equal :save, @machine.action end end class MachineWithNilActionTest < Test::Unit::TestCase def setup integration = Module.new do include StateMachine::Integrations::Base @defaults = {:action => :save} end StateMachine::Integrations.const_set('Custom', integration) @machine = StateMachine::Machine.new(Class.new, :action => nil, :integration => :custom) end def test_should_have_a_nil_action assert_nil @machine.action end def teardown StateMachine::Integrations.send(:remove_const, 'Custom') end end class MachineWithoutIntegrationTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @object = @klass.new end def test_transaction_should_yield @yielded = false @machine.within_transaction(@object) do @yielded = true end assert @yielded end def test_invalidation_should_do_nothing assert_nil @machine.invalidate(@object, :state, :invalid_transition, [[:event, 'park']]) end def test_reset_should_do_nothing assert_nil @machine.reset(@object) end def test_errors_for_should_be_empty assert_equal '', @machine.errors_for(@object) end end class MachineWithCustomIntegrationTest < Test::Unit::TestCase def setup integration = Module.new do include StateMachine::Integrations::Base def self.matching_ancestors ['MachineWithCustomIntegrationTest::Vehicle'] end end StateMachine::Integrations.const_set('Custom', integration) superclass = Class.new self.class.const_set('Vehicle', superclass) @klass = Class.new(superclass) end def test_should_be_extended_by_the_integration_if_explicit machine = StateMachine::Machine.new(@klass, :integration => :custom) assert((class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)) end def test_should_not_be_extended_by_the_integration_if_implicit_but_not_available StateMachine::Integrations::Custom.class_eval do class << self; remove_method :matching_ancestors; end def self.matching_ancestors [] end end machine = StateMachine::Machine.new(@klass) assert(!(class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)) end def test_should_not_be_extended_by_the_integration_if_implicit_but_not_matched StateMachine::Integrations::Custom.class_eval do class << self; remove_method :matching_ancestors; end def self.matching_ancestors [] end end machine = StateMachine::Machine.new(@klass) assert(!(class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)) end def test_should_be_extended_by_the_integration_if_implicit_and_available_and_matches machine = StateMachine::Machine.new(@klass) assert((class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)) end def test_should_not_be_extended_by_the_integration_if_nil machine = StateMachine::Machine.new(@klass, :integration => nil) assert(!(class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)) end def test_should_not_be_extended_by_the_integration_if_false machine = StateMachine::Machine.new(@klass, :integration => false) assert(!(class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)) end def teardown self.class.send(:remove_const, 'Vehicle') StateMachine::Integrations.send(:remove_const, 'Custom') end end class MachineWithIntegrationTest < Test::Unit::TestCase def setup StateMachine::Integrations.const_set('Custom', Module.new do include StateMachine::Integrations::Base @defaults = {:action => :save, :use_transactions => false} attr_reader :initialized, :with_scopes, :without_scopes, :ran_transaction def after_initialize @initialized = true end def create_with_scope(name) (@with_scopes ||= []) << name lambda {} end def create_without_scope(name) (@without_scopes ||= []) << name lambda {} end def transaction(object) @ran_transaction = true yield end end) @machine = StateMachine::Machine.new(Class.new, :integration => :custom) end def test_should_call_after_initialize_hook assert @machine.initialized end def test_should_use_the_default_action assert_equal :save, @machine.action end def test_should_use_the_custom_action_if_specified machine = StateMachine::Machine.new(Class.new, :integration => :custom, :action => :save!) assert_equal :save!, machine.action end def test_should_use_the_default_use_transactions assert_equal false, @machine.use_transactions end def test_should_use_the_custom_use_transactions_if_specified machine = StateMachine::Machine.new(Class.new, :integration => :custom, :use_transactions => true) assert_equal true, machine.use_transactions end def test_should_define_a_singular_and_plural_with_scope assert_equal %w(with_state with_states), @machine.with_scopes end def test_should_define_a_singular_and_plural_without_scope assert_equal %w(without_state without_states), @machine.without_scopes end def teardown StateMachine::Integrations.send(:remove_const, 'Custom') end end class MachineWithActionUndefinedTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :action => :save) @object = @klass.new end def test_should_define_an_event_attribute_reader assert @object.respond_to?(:state_event) end def test_should_define_an_event_attribute_writer assert @object.respond_to?(:state_event=) end def test_should_define_an_event_transition_attribute_reader assert @object.respond_to?(:state_event_transition, true) end def test_should_define_an_event_transition_attribute_writer assert @object.respond_to?(:state_event_transition=, true) end def test_should_not_define_action assert !@object.respond_to?(:save) end def test_should_not_mark_action_hook_as_defined assert !@machine.action_hook? end end class MachineWithActionDefinedInClassTest < Test::Unit::TestCase def setup @klass = Class.new do def save end end @machine = StateMachine::Machine.new(@klass, :action => :save) @object = @klass.new end def test_should_define_an_event_attribute_reader assert @object.respond_to?(:state_event) end def test_should_define_an_event_attribute_writer assert @object.respond_to?(:state_event=) end def test_should_define_an_event_transition_attribute_reader assert @object.respond_to?(:state_event_transition, true) end def test_should_define_an_event_transition_attribute_writer assert @object.respond_to?(:state_event_transition=, true) end def test_should_not_define_action assert !@klass.ancestors.any? {|ancestor| ancestor != @klass && ancestor.method_defined?(:save)} end def test_should_not_mark_action_hook_as_defined assert !@machine.action_hook? end end class MachineWithActionDefinedInIncludedModuleTest < Test::Unit::TestCase def setup @mod = mod = Module.new do def save end end @klass = Class.new do include mod end @machine = StateMachine::Machine.new(@klass, :action => :save) @object = @klass.new end def test_should_define_an_event_attribute_reader assert @object.respond_to?(:state_event) end def test_should_define_an_event_attribute_writer assert @object.respond_to?(:state_event=) end def test_should_define_an_event_transition_attribute_reader assert @object.respond_to?(:state_event_transition, true) end def test_should_define_an_event_transition_attribute_writer assert @object.respond_to?(:state_event_transition=, true) end def test_should_define_action assert @klass.ancestors.any? {|ancestor| ![@klass, @mod].include?(ancestor) && ancestor.method_defined?(:save)} end def test_should_keep_action_public assert @klass.public_method_defined?(:save) end def test_should_mark_action_hook_as_defined assert @machine.action_hook? end end class MachineWithActionDefinedInSuperclassTest < Test::Unit::TestCase def setup @superclass = Class.new do def save end end @klass = Class.new(@superclass) @machine = StateMachine::Machine.new(@klass, :action => :save) @object = @klass.new end def test_should_define_an_event_attribute_reader assert @object.respond_to?(:state_event) end def test_should_define_an_event_attribute_writer assert @object.respond_to?(:state_event=) end def test_should_define_an_event_transition_attribute_reader assert @object.respond_to?(:state_event_transition, true) end def test_should_define_an_event_transition_attribute_writer assert @object.respond_to?(:state_event_transition=, true) end def test_should_define_action assert @klass.ancestors.any? {|ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.method_defined?(:save)} end def test_should_keep_action_public assert @klass.public_method_defined?(:save) end def test_should_mark_action_hook_as_defined assert @machine.action_hook? end end class MachineWithPrivateActionTest < Test::Unit::TestCase def setup @superclass = Class.new do private def save end end @klass = Class.new(@superclass) @machine = StateMachine::Machine.new(@klass, :action => :save) @object = @klass.new end def test_should_define_an_event_attribute_reader assert @object.respond_to?(:state_event) end def test_should_define_an_event_attribute_writer assert @object.respond_to?(:state_event=) end def test_should_define_an_event_transition_attribute_reader assert @object.respond_to?(:state_event_transition, true) end def test_should_define_an_event_transition_attribute_writer assert @object.respond_to?(:state_event_transition=, true) end def test_should_define_action assert @klass.ancestors.any? {|ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.private_method_defined?(:save)} end def test_should_keep_action_private assert @klass.private_method_defined?(:save) end def test_should_mark_action_hook_as_defined assert @machine.action_hook? end end class MachineWithActionAlreadyOverriddenTest < Test::Unit::TestCase def setup @superclass = Class.new do def save end end @klass = Class.new(@superclass) StateMachine::Machine.new(@klass, :action => :save) @machine = StateMachine::Machine.new(@klass, :status, :action => :save) @object = @klass.new end def test_should_not_redefine_action assert_equal 1, @klass.ancestors.select {|ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.method_defined?(:save)}.length end def test_should_mark_action_hook_as_defined assert @machine.action_hook? end end class MachineWithCustomPluralTest < Test::Unit::TestCase def setup @integration = Module.new do include StateMachine::Integrations::Base class << self; attr_accessor :with_scopes, :without_scopes; end @with_scopes = [] @without_scopes = [] def create_with_scope(name) StateMachine::Integrations::Custom.with_scopes << name lambda {} end def create_without_scope(name) StateMachine::Integrations::Custom.without_scopes << name lambda {} end end StateMachine::Integrations.const_set('Custom', @integration) end def test_should_define_a_singular_and_plural_with_scope StateMachine::Machine.new(Class.new, :integration => :custom, :plural => 'staties') assert_equal %w(with_state with_staties), @integration.with_scopes end def test_should_define_a_singular_and_plural_without_scope StateMachine::Machine.new(Class.new, :integration => :custom, :plural => 'staties') assert_equal %w(without_state without_staties), @integration.without_scopes end def test_should_define_single_with_scope_if_singular_same_as_plural StateMachine::Machine.new(Class.new, :integration => :custom, :plural => 'state') assert_equal %w(with_state), @integration.with_scopes end def test_should_define_single_without_scope_if_singular_same_as_plural StateMachine::Machine.new(Class.new, :integration => :custom, :plural => 'state') assert_equal %w(without_state), @integration.without_scopes end def teardown StateMachine::Integrations.send(:remove_const, 'Custom') end end class MachineWithCustomInvalidationTest < Test::Unit::TestCase def setup @integration = Module.new do include StateMachine::Integrations::Base def invalidate(object, attribute, message, values = []) object.error = generate_message(message, values) end end StateMachine::Integrations.const_set('Custom', @integration) @klass = Class.new do attr_accessor :error end @machine = StateMachine::Machine.new(@klass, :integration => :custom, :messages => {:invalid_transition => 'cannot %s'}) @machine.state :parked @object = @klass.new @object.state = 'parked' end def test_generate_custom_message assert_equal 'cannot park', @machine.generate_message(:invalid_transition, [[:event, :park]]) end def test_use_custom_message @machine.invalidate(@object, :state, :invalid_transition, [[:event, 'park']]) assert_equal 'cannot park', @object.error end def teardown StateMachine::Integrations.send(:remove_const, 'Custom') end end class MachineTest < Test::Unit::TestCase def test_should_raise_exception_if_invalid_option_specified assert_raise(ArgumentError) {StateMachine::Machine.new(Class.new, :invalid => true)} end def test_should_not_raise_exception_if_custom_messages_specified assert_nothing_raised {StateMachine::Machine.new(Class.new, :messages => {:invalid_transition => 'custom'})} end def test_should_evaluate_a_block_during_initialization called = true StateMachine::Machine.new(Class.new) do called = respond_to?(:event) end assert called end def test_should_provide_matcher_helpers_during_initialization matchers = [] StateMachine::Machine.new(Class.new) do matchers = [all, any, same] end assert_equal [StateMachine::AllMatcher.instance, StateMachine::AllMatcher.instance, StateMachine::LoopbackMatcher.instance], matchers end end class MachineAfterBeingCopiedTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new, :state, :initial => :parked) @machine.event(:ignite) {} @machine.before_transition(lambda {}) @machine.after_transition(lambda {}) @machine.around_transition(lambda {}) @machine.after_failure(lambda {}) @copied_machine = @machine.clone end def test_should_not_have_the_same_collection_of_states assert_not_same @copied_machine.states, @machine.states end def test_should_copy_each_state assert_not_same @copied_machine.states[:parked], @machine.states[:parked] end def test_should_update_machine_for_each_state assert_equal @copied_machine, @copied_machine.states[:parked].machine end def test_should_not_update_machine_for_original_state assert_equal @machine, @machine.states[:parked].machine end def test_should_not_have_the_same_collection_of_events assert_not_same @copied_machine.events, @machine.events end def test_should_copy_each_event assert_not_same @copied_machine.events[:ignite], @machine.events[:ignite] end def test_should_update_machine_for_each_event assert_equal @copied_machine, @copied_machine.events[:ignite].machine end def test_should_not_update_machine_for_original_event assert_equal @machine, @machine.events[:ignite].machine end def test_should_not_have_the_same_callbacks assert_not_same @copied_machine.callbacks, @machine.callbacks end def test_should_not_have_the_same_before_callbacks assert_not_same @copied_machine.callbacks[:before], @machine.callbacks[:before] end def test_should_not_have_the_same_after_callbacks assert_not_same @copied_machine.callbacks[:after], @machine.callbacks[:after] end def test_should_not_have_the_same_failure_callbacks assert_not_same @copied_machine.callbacks[:failure], @machine.callbacks[:failure] end end class MachineAfterChangingOwnerClassTest < Test::Unit::TestCase def setup @original_class = Class.new @machine = StateMachine::Machine.new(@original_class) @new_class = Class.new(@original_class) @new_machine = @machine.clone @new_machine.owner_class = @new_class @object = @new_class.new end def test_should_update_owner_class assert_equal @new_class, @new_machine.owner_class end def test_should_not_change_original_owner_class assert_equal @original_class, @machine.owner_class end def test_should_change_the_associated_machine_in_the_new_class assert_equal @new_machine, @new_class.state_machines[:state] end def test_should_not_change_the_associated_machine_in_the_original_class assert_equal @machine, @original_class.state_machines[:state] end end class MachineAfterChangingInitialState < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @machine.initial_state = :idling @object = @klass.new end def test_should_change_the_initial_state assert_equal :idling, @machine.initial_state(@object).name end def test_should_include_in_known_states assert_equal [:parked, :idling], @machine.states.map {|state| state.name} end def test_should_reset_original_initial_state assert !@machine.state(:parked).initial end def test_should_set_new_state_to_initial assert @machine.state(:idling).initial end end class MachineWithHelpersTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @object = @klass.new end def test_should_throw_exception_with_invalid_scope assert_raise(RUBY_VERSION < '1.9' ? IndexError : KeyError) { @machine.define_helper(:invalid, :park) {} } end end class MachineWithInstanceHelpersTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @object = @klass.new end def test_should_not_redefine_existing_public_methods @klass.class_eval do def park true end end @machine.define_helper(:instance, :park) {} assert_equal true, @object.park end def test_should_not_redefine_existing_protected_methods @klass.class_eval do protected def park true end end @machine.define_helper(:instance, :park) {} assert_equal true, @object.send(:park) end def test_should_not_redefine_existing_private_methods @klass.class_eval do private def park true end end @machine.define_helper(:instance, :park) {} assert_equal true, @object.send(:park) end def test_should_warn_if_defined_in_superclass require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new superclass = Class.new do def park end end klass = Class.new(superclass) machine = StateMachine::Machine.new(klass) machine.define_helper(:instance, :park) {} assert_equal "Instance method \"park\" is already defined in #{superclass.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string ensure $stderr = @original_stderr end def test_should_warn_if_defined_in_multiple_superclasses require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new superclass1 = Class.new do def park end end superclass2 = Class.new(superclass1) do def park end end klass = Class.new(superclass2) machine = StateMachine::Machine.new(klass) machine.define_helper(:instance, :park) {} assert_equal "Instance method \"park\" is already defined in #{superclass1.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string ensure $stderr = @original_stderr end def test_should_warn_if_defined_in_module_prior_to_helper_module require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new mod = Module.new do def park end end klass = Class.new do include mod end machine = StateMachine::Machine.new(klass) machine.define_helper(:instance, :park) {} assert_equal "Instance method \"park\" is already defined in #{mod.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string ensure $stderr = @original_stderr end def test_should_not_warn_if_defined_in_module_after_helper_module require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new klass = Class.new machine = StateMachine::Machine.new(klass) mod = Module.new do def park end end klass.class_eval do include mod end machine.define_helper(:instance, :park) {} assert_equal '', $stderr.string ensure $stderr = @original_stderr end def test_should_define_if_ignoring_method_conflicts_and_defined_in_superclass require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new StateMachine::Machine.ignore_method_conflicts = true superclass = Class.new do def park end end klass = Class.new(superclass) machine = StateMachine::Machine.new(klass) machine.define_helper(:instance, :park) {true} assert_equal '', $stderr.string assert_equal true, klass.new.park ensure StateMachine::Machine.ignore_method_conflicts = false $stderr = @original_stderr end def test_should_define_nonexistent_methods @machine.define_helper(:instance, :park) {false} assert_equal false, @object.park end def test_should_warn_if_defined_multiple_times require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @machine.define_helper(:instance, :park) {} @machine.define_helper(:instance, :park) {} assert_equal "Instance method \"park\" is already defined in #{@klass} :state instance helpers, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string ensure $stderr = @original_stderr end def test_should_pass_context_as_arguments helper_args = nil @machine.define_helper(:instance, :park) {|*args| helper_args = args} @object.park assert_equal 2, helper_args.length assert_equal [@machine, @object], helper_args end def test_should_pass_method_arguments_through helper_args = nil @machine.define_helper(:instance, :park) {|*args| helper_args = args} @object.park(1, 2, 3) assert_equal 5, helper_args.length assert_equal [@machine, @object, 1, 2, 3], helper_args end def test_should_allow_string_evaluation @machine.define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def park false end end_eval assert_equal false, @object.park end end class MachineWithClassHelpersTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) end def test_should_not_redefine_existing_public_methods class << @klass def states [] end end @machine.define_helper(:class, :states) {} assert_equal [], @klass.states end def test_should_not_redefine_existing_protected_methods class << @klass protected def states [] end end @machine.define_helper(:class, :states) {} assert_equal [], @klass.send(:states) end def test_should_not_redefine_existing_private_methods class << @klass private def states [] end end @machine.define_helper(:class, :states) {} assert_equal [], @klass.send(:states) end def test_should_warn_if_defined_in_superclass require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new superclass = Class.new do def self.park end end klass = Class.new(superclass) machine = StateMachine::Machine.new(klass) machine.define_helper(:class, :park) {} assert_equal "Class method \"park\" is already defined in #{superclass.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string ensure $stderr = @original_stderr end def test_should_warn_if_defined_in_multiple_superclasses require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new superclass1 = Class.new do def self.park end end superclass2 = Class.new(superclass1) do def self.park end end klass = Class.new(superclass2) machine = StateMachine::Machine.new(klass) machine.define_helper(:class, :park) {} assert_equal "Class method \"park\" is already defined in #{superclass1.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string ensure $stderr = @original_stderr end def test_should_warn_if_defined_in_module_prior_to_helper_module require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new mod = Module.new do def park end end klass = Class.new do extend mod end machine = StateMachine::Machine.new(klass) machine.define_helper(:class, :park) {} assert_equal "Class method \"park\" is already defined in #{mod.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string ensure $stderr = @original_stderr end def test_should_not_warn_if_defined_in_module_after_helper_module require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new klass = Class.new machine = StateMachine::Machine.new(klass) mod = Module.new do def park end end klass.class_eval do extend mod end machine.define_helper(:class, :park) {} assert_equal '', $stderr.string ensure $stderr = @original_stderr end def test_should_define_if_ignoring_method_conflicts_and_defined_in_superclass require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new StateMachine::Machine.ignore_method_conflicts = true superclass = Class.new do def self.park end end klass = Class.new(superclass) machine = StateMachine::Machine.new(klass) machine.define_helper(:class, :park) {true} assert_equal '', $stderr.string assert_equal true, klass.park ensure StateMachine::Machine.ignore_method_conflicts = false $stderr = @original_stderr end def test_should_define_nonexistent_methods @machine.define_helper(:class, :states) {[]} assert_equal [], @klass.states end def test_should_warn_if_defined_multiple_times require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @machine.define_helper(:class, :states) {} @machine.define_helper(:class, :states) {} assert_equal "Class method \"states\" is already defined in #{@klass} :state class helpers, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string ensure $stderr = @original_stderr end def test_should_pass_context_as_arguments helper_args = nil @machine.define_helper(:class, :states) {|*args| helper_args = args} @klass.states assert_equal 2, helper_args.length assert_equal [@machine, @klass], helper_args end def test_should_pass_method_arguments_through helper_args = nil @machine.define_helper(:class, :states) {|*args| helper_args = args} @klass.states(1, 2, 3) assert_equal 5, helper_args.length assert_equal [@machine, @klass, 1, 2, 3], helper_args end def test_should_allow_string_evaluation @machine.define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1 def states [] end end_eval assert_equal [], @klass.states end end class MachineWithConflictingHelpersBeforeDefinitionTest < Test::Unit::TestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @superclass = Class.new do def self.with_state :with_state end def self.with_states :with_states end def self.without_state :without_state end def self.without_states :without_states end def self.human_state_name :human_state_name end def self.human_state_event_name :human_state_event_name end attr_accessor :status def state 'parked' end def state=(value) self.status = value end def state? true end def state_name :parked end def human_state_name 'parked' end def state_events [:ignite] end def state_transitions [{:parked => :idling}] end def state_paths [[{:parked => :idling}]] end def fire_state_event true end end @klass = Class.new(@superclass) StateMachine::Integrations.const_set('Custom', Module.new do include StateMachine::Integrations::Base def create_with_scope(name) lambda {|klass, values| []} end def create_without_scope(name) lambda {|klass, values| []} end end) @machine = StateMachine::Machine.new(@klass, :integration => :custom) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new end def test_should_not_redefine_singular_with_scope assert_equal :with_state, @klass.with_state end def test_should_not_redefine_plural_with_scope assert_equal :with_states, @klass.with_states end def test_should_not_redefine_singular_without_scope assert_equal :without_state, @klass.without_state end def test_should_not_redefine_plural_without_scope assert_equal :without_states, @klass.without_states end def test_should_not_redefine_human_attribute_name_reader assert_equal :human_state_name, @klass.human_state_name end def test_should_not_redefine_human_event_name_reader assert_equal :human_state_event_name, @klass.human_state_event_name end def test_should_not_redefine_attribute_reader assert_equal 'parked', @object.state end def test_should_not_redefine_attribute_writer @object.state = 'parked' assert_equal 'parked', @object.status end def test_should_not_define_attribute_predicate assert @object.state? end def test_should_not_redefine_attribute_name_reader assert_equal :parked, @object.state_name end def test_should_not_redefine_attribute_human_name_reader assert_equal 'parked', @object.human_state_name end def test_should_not_redefine_attribute_events_reader assert_equal [:ignite], @object.state_events end def test_should_not_redefine_attribute_transitions_reader assert_equal [{:parked => :idling}], @object.state_transitions end def test_should_not_redefine_attribute_paths_reader assert_equal [[{:parked => :idling}]], @object.state_paths end def test_should_not_redefine_event_runner assert_equal true, @object.fire_state_event end def test_should_output_warning expected = [ 'Instance method "state_events"', 'Instance method "state_transitions"', 'Instance method "fire_state_event"', 'Instance method "state_paths"', 'Class method "human_state_name"', 'Class method "human_state_event_name"', 'Instance method "state_name"', 'Instance method "human_state_name"', 'Class method "with_state"', 'Class method "with_states"', 'Class method "without_state"', 'Class method "without_states"' ].map {|method| "#{method} is already defined in #{@superclass.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n"}.join assert_equal expected, $stderr.string end def teardown $stderr = @original_stderr StateMachine::Integrations.send(:remove_const, 'Custom') end end class MachineWithConflictingHelpersAfterDefinitionTest < Test::Unit::TestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @klass = Class.new do def self.with_state :with_state end def self.with_states :with_states end def self.without_state :without_state end def self.without_states :without_states end def self.human_state_name :human_state_name end def self.human_state_event_name :human_state_event_name end attr_accessor :status def state 'parked' end def state=(value) self.status = value end def state? true end def state_name :parked end def human_state_name 'parked' end def state_events [:ignite] end def state_transitions [{:parked => :idling}] end def state_paths [[{:parked => :idling}]] end def fire_state_event true end end StateMachine::Integrations.const_set('Custom', Module.new do include StateMachine::Integrations::Base def create_with_scope(name) lambda {|klass, values| []} end def create_without_scope(name) lambda {|klass, values| []} end end) @machine = StateMachine::Machine.new(@klass, :integration => :custom) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new end def test_should_not_redefine_singular_with_scope assert_equal :with_state, @klass.with_state end def test_should_not_redefine_plural_with_scope assert_equal :with_states, @klass.with_states end def test_should_not_redefine_singular_without_scope assert_equal :without_state, @klass.without_state end def test_should_not_redefine_plural_without_scope assert_equal :without_states, @klass.without_states end def test_should_not_redefine_human_attribute_name_reader assert_equal :human_state_name, @klass.human_state_name end def test_should_not_redefine_human_event_name_reader assert_equal :human_state_event_name, @klass.human_state_event_name end def test_should_not_redefine_attribute_reader assert_equal 'parked', @object.state end def test_should_not_redefine_attribute_writer @object.state = 'parked' assert_equal 'parked', @object.status end def test_should_not_define_attribute_predicate assert @object.state? end def test_should_not_redefine_attribute_name_reader assert_equal :parked, @object.state_name end def test_should_not_redefine_attribute_human_name_reader assert_equal 'parked', @object.human_state_name end def test_should_not_redefine_attribute_events_reader assert_equal [:ignite], @object.state_events end def test_should_not_redefine_attribute_transitions_reader assert_equal [{:parked => :idling}], @object.state_transitions end def test_should_not_redefine_attribute_paths_reader assert_equal [[{:parked => :idling}]], @object.state_paths end def test_should_not_redefine_event_runner assert_equal true, @object.fire_state_event end def test_should_allow_super_chaining @klass.class_eval do def self.with_state(*states) super end def self.with_states(*states) super end def self.without_state(*states) super end def self.without_states(*states) super end def self.human_state_name(state) super end def self.human_state_event_name(event) super end attr_accessor :status def state super end def state=(value) super end def state?(state) super end def state_name super end def human_state_name super end def state_events super end def state_transitions super end def state_paths super end def fire_state_event(event) super end end assert_equal [], @klass.with_state assert_equal [], @klass.with_states assert_equal [], @klass.without_state assert_equal [], @klass.without_states assert_equal 'parked', @klass.human_state_name(:parked) assert_equal 'ignite', @klass.human_state_event_name(:ignite) assert_equal nil, @object.state @object.state = 'idling' assert_equal 'idling', @object.state assert_equal nil, @object.status assert_equal false, @object.state?(:parked) assert_equal :idling, @object.state_name assert_equal 'idling', @object.human_state_name assert_equal [], @object.state_events assert_equal [], @object.state_transitions assert_equal [], @object.state_paths assert_equal false, @object.fire_state_event(:ignite) end def test_should_not_output_warning assert_equal '', $stderr.string end def teardown $stderr = @original_stderr StateMachine::Integrations.send(:remove_const, 'Custom') end end class MachineWithSuperclassConflictingHelpersAfterDefinitionTest < Test::Unit::TestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @superclass = Class.new @klass = Class.new(@superclass) @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @superclass.class_eval do def state? true end end @object = @klass.new end def test_should_call_superclass_attribute_predicate_without_arguments assert @object.state? end def test_should_define_attribute_predicate_with_arguments assert !@object.state?(:parked) end def teardown $stderr = @original_stderr end end class MachineWithoutInitializeTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @object = @klass.new end def test_should_initialize_state assert_equal 'parked', @object.state end end class MachineWithInitializeWithoutSuperTest < Test::Unit::TestCase def setup @klass = Class.new do def initialize end end @machine = StateMachine::Machine.new(@klass, :initial => :parked) @object = @klass.new end def test_should_not_initialize_state assert_nil @object.state end end class MachineWithInitializeAndSuperTest < Test::Unit::TestCase def setup @klass = Class.new do def initialize super() end end @machine = StateMachine::Machine.new(@klass, :initial => :parked) @object = @klass.new end def test_should_initialize_state assert_equal 'parked', @object.state end end class MachineWithInitializeArgumentsAndBlockTest < Test::Unit::TestCase def setup @superclass = Class.new do attr_reader :args attr_reader :block_given def initialize(*args) @args = args @block_given = block_given? end end @klass = Class.new(@superclass) @machine = StateMachine::Machine.new(@klass, :initial => :parked) @object = @klass.new(1, 2, 3) {} end def test_should_initialize_state assert_equal 'parked', @object.state end def test_should_preserve_arguments assert_equal [1, 2, 3], @object.args end def test_should_preserve_block assert @object.block_given end end class MachineWithCustomInitializeTest < Test::Unit::TestCase def setup @klass = Class.new do def initialize(state = nil, options = {}) @state = state initialize_state_machines(options) end end @machine = StateMachine::Machine.new(@klass, :initial => :parked) @object = @klass.new end def test_should_initialize_state assert_equal 'parked', @object.state end def test_should_allow_custom_options @machine.state :idling @object = @klass.new('idling', :static => :force) assert_equal 'parked', @object.state end end class MachinePersistenceTest < Test::Unit::TestCase def setup @klass = Class.new do attr_accessor :state_event end @machine = StateMachine::Machine.new(@klass, :initial => :parked) @object = @klass.new end def test_should_allow_reading_state assert_equal 'parked', @machine.read(@object, :state) end def test_should_allow_reading_custom_attributes assert_nil @machine.read(@object, :event) @object.state_event = 'ignite' assert_equal 'ignite', @machine.read(@object, :event) end def test_should_allow_reading_custom_instance_variables @klass.class_eval do attr_writer :state_value end @object.state_value = 1 assert_raise(NoMethodError) { @machine.read(@object, :value) } assert_equal 1, @machine.read(@object, :value, true) end def test_should_allow_writing_state @machine.write(@object, :state, 'idling') assert_equal 'idling', @object.state end def test_should_allow_writing_custom_attributes @machine.write(@object, :event, 'ignite') assert_equal 'ignite', @object.state_event end def test_should_allow_writing_custom_instance_variables @klass.class_eval do attr_reader :state_value end assert_raise(NoMethodError) { @machine.write(@object, :value, 1) } assert_equal 1, @machine.write(@object, :value, 1, true) assert_equal 1, @object.state_value end end class MachineWithStatesTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @parked, @idling = @machine.state :parked, :idling @object = @klass.new end def test_should_have_states assert_equal [nil, :parked, :idling], @machine.states.map {|state| state.name} end def test_should_allow_state_lookup_by_name assert_equal @parked, @machine.states[:parked] end def test_should_allow_state_lookup_by_value assert_equal @parked, @machine.states['parked', :value] end def test_should_allow_human_state_name_lookup assert_equal 'parked', @klass.human_state_name(:parked) end def test_should_raise_exception_on_invalid_human_state_name_lookup exception = assert_raise(IndexError) {@klass.human_state_name(:invalid)} assert_equal ':invalid is an invalid name', exception.message end def test_should_use_stringified_name_for_value assert_equal 'parked', @parked.value end def test_should_not_use_custom_matcher assert_nil @parked.matcher end def test_should_raise_exception_if_invalid_option_specified exception = assert_raise(ArgumentError) {@machine.state(:first_gear, :invalid => true)} assert_equal 'Invalid key(s): invalid', exception.message end def test_should_raise_exception_if_conflicting_type_used_for_name exception = assert_raise(ArgumentError) { @machine.state 'first_gear' } assert_equal '"first_gear" state defined as String, :parked defined as Symbol; all states must be consistent', exception.message end def test_should_not_raise_exception_if_conflicting_type_is_nil_for_name assert_nothing_raised { @machine.state nil } end end class MachineWithStatesWithCustomValuesTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @state = @machine.state :parked, :value => 1 @object = @klass.new @object.state = 1 end def test_should_use_custom_value assert_equal 1, @state.value end def test_should_allow_lookup_by_custom_value assert_equal @state, @machine.states[1, :value] end end class MachineWithStatesWithCustomHumanNamesTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @state = @machine.state :parked, :human_name => 'stopped' end def test_should_use_custom_human_name assert_equal 'stopped', @state.human_name end def test_should_allow_human_state_name_lookup assert_equal 'stopped', @klass.human_state_name(:parked) end end class MachineWithStatesWithRuntimeDependenciesTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked end def test_should_not_evaluate_value_during_definition assert_nothing_raised { @machine.state :parked, :value => lambda {raise ArgumentError} } end def test_should_not_evaluate_if_not_initial_state @machine.state :parked, :value => lambda {raise ArgumentError} assert_nothing_raised { @klass.new } end end class MachineWithStateWithMatchersTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @state = @machine.state :parked, :if => lambda {|value| !value.nil?} @object = @klass.new @object.state = 1 end def test_should_use_custom_matcher assert_not_nil @state.matcher assert @state.matches?(1) assert !@state.matches?(nil) end end class MachineWithCachedStateTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @state = @machine.state :parked, :value => lambda {Object.new}, :cache => true @object = @klass.new end def test_should_use_evaluated_value assert_instance_of Object, @object.state end def test_use_same_value_across_multiple_objects assert_equal @object.state, @klass.new.state end end class MachineWithStatesWithBehaviorsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @parked, @idling = @machine.state :parked, :idling do def speed 0 end end end def test_should_define_behaviors_for_each_state assert_not_nil @parked.methods[:speed] assert_not_nil @idling.methods[:speed] end def test_should_define_different_behaviors_for_each_state assert_not_equal @parked.methods[:speed], @idling.methods[:speed] end end class MachineWithExistingStateTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @state = @machine.state :parked @same_state = @machine.state :parked, :value => 1 end def test_should_not_create_a_new_state assert_same @state, @same_state end def test_should_update_attributes assert_equal 1, @state.value end def test_should_no_longer_be_able_to_look_up_state_by_original_value assert_nil @machine.states['parked', :value] end def test_should_be_able_to_look_up_state_by_new_value assert_equal @state, @machine.states[1, :value] end end class MachineWithStateMatchersTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) end def test_should_empty_array_for_all_matcher assert_equal [], @machine.state(StateMachine::AllMatcher.instance) end def test_should_return_referenced_states_for_blacklist_matcher assert_instance_of StateMachine::State, @machine.state(StateMachine::BlacklistMatcher.new([:parked])) end def test_should_not_allow_configurations exception = assert_raise(ArgumentError) { @machine.state(StateMachine::BlacklistMatcher.new([:parked]), :human_name => 'Parked') } assert_equal 'Cannot configure states when using matchers (using {:human_name=>"Parked"})', exception.message end def test_should_track_referenced_states @machine.state(StateMachine::BlacklistMatcher.new([:parked])) assert_equal [nil, :parked], @machine.states.map {|state| state.name} end def test_should_eval_context_for_matching_states contexts_run = [] @machine.event(StateMachine::BlacklistMatcher.new([:parked])) { contexts_run << self.name } @machine.event :parked assert_equal [], contexts_run @machine.event :idling assert_equal [:idling], contexts_run @machine.event :first_gear, :second_gear assert_equal [:idling, :first_gear, :second_gear], contexts_run end end class MachineWithOtherStates < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @parked, @idling = @machine.other_states(:parked, :idling) end def test_should_include_other_states_in_known_states assert_equal [@parked, @idling], @machine.states.to_a end def test_should_use_default_value assert_equal 'idling', @idling.value end def test_should_not_create_matcher assert_nil @idling.matcher end end class MachineWithEventsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) end def test_should_return_the_created_event assert_instance_of StateMachine::Event, @machine.event(:ignite) end def test_should_create_event_with_given_name event = @machine.event(:ignite) {} assert_equal :ignite, event.name end def test_should_evaluate_block_within_event_context responded = false @machine.event :ignite do responded = respond_to?(:transition) end assert responded end def test_should_be_aliased_as_on event = @machine.on(:ignite) {} assert_equal :ignite, event.name end def test_should_have_events event = @machine.event(:ignite) assert_equal [event], @machine.events.to_a end def test_should_allow_human_state_name_lookup @machine.event(:ignite) assert_equal 'ignite', @klass.human_state_event_name(:ignite) end def test_should_raise_exception_on_invalid_human_state_event_name_lookup exception = assert_raise(IndexError) {@klass.human_state_event_name(:invalid)} assert_equal ':invalid is an invalid name', exception.message end def test_should_raise_exception_if_conflicting_type_used_for_name @machine.event :park exception = assert_raise(ArgumentError) { @machine.event 'ignite' } assert_equal '"ignite" event defined as String, :park defined as Symbol; all events must be consistent', exception.message end end class MachineWithExistingEventTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @event = @machine.event(:ignite) @same_event = @machine.event(:ignite) end def test_should_not_create_new_event assert_same @event, @same_event end def test_should_allow_accessing_event_without_block assert_equal @event, @machine.event(:ignite) end end class MachineWithEventsWithCustomHumanNamesTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @event = @machine.event(:ignite, :human_name => 'start') end def test_should_use_custom_human_name assert_equal 'start', @event.human_name end def test_should_allow_human_state_name_lookup assert_equal 'start', @klass.human_state_event_name(:ignite) end end class MachineWithEventMatchersTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) end def test_should_empty_array_for_all_matcher assert_equal [], @machine.event(StateMachine::AllMatcher.instance) end def test_should_return_referenced_events_for_blacklist_matcher assert_instance_of StateMachine::Event, @machine.event(StateMachine::BlacklistMatcher.new([:park])) end def test_should_not_allow_configurations exception = assert_raise(ArgumentError) { @machine.event(StateMachine::BlacklistMatcher.new([:park]), :human_name => 'Park') } assert_equal 'Cannot configure events when using matchers (using {:human_name=>"Park"})', exception.message end def test_should_track_referenced_events @machine.event(StateMachine::BlacklistMatcher.new([:park])) assert_equal [:park], @machine.events.map {|event| event.name} end def test_should_eval_context_for_matching_events contexts_run = [] @machine.event(StateMachine::BlacklistMatcher.new([:park])) { contexts_run << self.name } @machine.event :park assert_equal [], contexts_run @machine.event :ignite assert_equal [:ignite], contexts_run @machine.event :shift_up, :shift_down assert_equal [:ignite, :shift_up, :shift_down], contexts_run end end class MachineWithEventsWithTransitionsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @event = @machine.event(:ignite) do transition :parked => :idling transition :stalled => :idling end end def test_should_have_events assert_equal [@event], @machine.events.to_a end def test_should_track_states_defined_in_event_transitions assert_equal [:parked, :idling, :stalled], @machine.states.map {|state| state.name} end def test_should_not_duplicate_states_defined_in_multiple_event_transitions @machine.event :park do transition :idling => :parked end assert_equal [:parked, :idling, :stalled], @machine.states.map {|state| state.name} end def test_should_track_state_from_new_events @machine.event :shift_up do transition :idling => :first_gear end assert_equal [:parked, :idling, :stalled, :first_gear], @machine.states.map {|state| state.name} end end class MachineWithMultipleEventsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @park, @shift_down = @machine.event(:park, :shift_down) do transition :first_gear => :parked end end def test_should_have_events assert_equal [@park, @shift_down], @machine.events.to_a end def test_should_define_transitions_for_each_event [@park, @shift_down].each {|event| assert_equal 1, event.branches.size} end def test_should_transition_the_same_for_each_event object = @klass.new object.state = 'first_gear' object.park assert_equal 'parked', object.state object = @klass.new object.state = 'first_gear' object.shift_down assert_equal 'parked', object.state end end class MachineWithTransitionsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) end def test_should_require_on_event exception = assert_raise(ArgumentError) { @machine.transition(:parked => :idling) } assert_equal 'Must specify :on event', exception.message end def test_should_not_allow_except_on_option exception = assert_raise(ArgumentError) {@machine.transition(:except_on => :ignite, :on => :ignite)} assert_equal 'Invalid key(s): except_on', exception.message end def test_should_allow_transitioning_without_a_to_state assert_nothing_raised {@machine.transition(:from => :parked, :on => :ignite)} end def test_should_allow_transitioning_without_a_from_state assert_nothing_raised {@machine.transition(:to => :idling, :on => :ignite)} end def test_should_allow_except_from_option assert_nothing_raised {@machine.transition(:except_from => :idling, :on => :ignite)} end def test_should_allow_except_to_option assert_nothing_raised {@machine.transition(:except_to => :parked, :on => :ignite)} end def test_should_allow_implicit_options branch = @machine.transition(:first_gear => :second_gear, :on => :shift_up) assert_instance_of StateMachine::Branch, branch state_requirements = branch.state_requirements assert_equal 1, state_requirements.length assert_instance_of StateMachine::WhitelistMatcher, state_requirements[0][:from] assert_equal [:first_gear], state_requirements[0][:from].values assert_instance_of StateMachine::WhitelistMatcher, state_requirements[0][:to] assert_equal [:second_gear], state_requirements[0][:to].values assert_instance_of StateMachine::WhitelistMatcher, branch.event_requirement assert_equal [:shift_up], branch.event_requirement.values end def test_should_allow_multiple_implicit_options branch = @machine.transition(:first_gear => :second_gear, :second_gear => :third_gear, :on => :shift_up) state_requirements = branch.state_requirements assert_equal 2, state_requirements.length end def test_should_allow_verbose_options branch = @machine.transition(:from => :parked, :to => :idling, :on => :ignite) assert_instance_of StateMachine::Branch, branch end def test_should_include_all_transition_states_in_machine_states @machine.transition(:parked => :idling, :on => :ignite) assert_equal [:parked, :idling], @machine.states.map {|state| state.name} end def test_should_include_all_transition_events_in_machine_events @machine.transition(:parked => :idling, :on => :ignite) assert_equal [:ignite], @machine.events.map {|event| event.name} end def test_should_allow_multiple_events branches = @machine.transition(:parked => :ignite, :on => [:ignite, :shift_up]) assert_equal 2, branches.length assert_equal [:ignite, :shift_up], @machine.events.map {|event| event.name} end def test_should_not_modify_options options = {:parked => :idling, :on => :ignite} @machine.transition(options) assert_equal options, {:parked => :idling, :on => :ignite} end end class MachineWithTransitionCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new do attr_accessor :callbacks end @machine = StateMachine::Machine.new(@klass, :initial => :parked) @event = @machine.event :ignite do transition :parked => :idling end @object = @klass.new @object.callbacks = [] end def test_should_not_raise_exception_if_implicit_option_specified assert_nothing_raised {@machine.before_transition :invalid => :valid, :do => lambda {}} end def test_should_raise_exception_if_method_not_specified exception = assert_raise(ArgumentError) {@machine.before_transition :to => :idling} assert_equal 'Method(s) for callback must be specified', exception.message end def test_should_invoke_callbacks_during_transition @machine.before_transition lambda {|object| object.callbacks << 'before'} @machine.after_transition lambda {|object| object.callbacks << 'after'} @machine.around_transition lambda {|object, transition, block| object.callbacks << 'before_around'; block.call; object.callbacks << 'after_around'} @event.fire(@object) assert_equal %w(before before_around after_around after), @object.callbacks end def test_should_allow_multiple_callbacks @machine.before_transition lambda {|object| object.callbacks << 'before1'}, lambda {|object| object.callbacks << 'before2'} @machine.after_transition lambda {|object| object.callbacks << 'after1'}, lambda {|object| object.callbacks << 'after2'} @machine.around_transition( lambda {|object, transition, block| object.callbacks << 'before_around1'; block.call; object.callbacks << 'after_around1'}, lambda {|object, transition, block| object.callbacks << 'before_around2'; block.call; object.callbacks << 'after_around2'} ) @event.fire(@object) assert_equal %w(before1 before2 before_around1 before_around2 after_around2 after_around1 after1 after2), @object.callbacks end def test_should_allow_multiple_callbacks_with_requirements @machine.before_transition lambda {|object| object.callbacks << 'before_parked1'}, lambda {|object| object.callbacks << 'before_parked2'}, :from => :parked @machine.before_transition lambda {|object| object.callbacks << 'before_idling1'}, lambda {|object| object.callbacks << 'before_idling2'}, :from => :idling @machine.after_transition lambda {|object| object.callbacks << 'after_parked1'}, lambda {|object| object.callbacks << 'after_parked2'}, :from => :parked @machine.after_transition lambda {|object| object.callbacks << 'after_idling1'}, lambda {|object| object.callbacks << 'after_idling2'}, :from => :idling @machine.around_transition( lambda {|object, transition, block| object.callbacks << 'before_around_parked1'; block.call; object.callbacks << 'after_around_parked1'}, lambda {|object, transition, block| object.callbacks << 'before_around_parked2'; block.call; object.callbacks << 'after_around_parked2'}, :from => :parked ) @machine.around_transition( lambda {|object, transition, block| object.callbacks << 'before_around_idling1'; block.call; object.callbacks << 'after_around_idling1'}, lambda {|object, transition, block| object.callbacks << 'before_around_idling2'; block.call; object.callbacks << 'after_around_idling2'}, :from => :idling ) @event.fire(@object) assert_equal %w(before_parked1 before_parked2 before_around_parked1 before_around_parked2 after_around_parked2 after_around_parked1 after_parked1 after_parked2), @object.callbacks end def test_should_support_from_requirement @machine.before_transition :from => :parked, :do => lambda {|object| object.callbacks << :parked} @machine.before_transition :from => :idling, :do => lambda {|object| object.callbacks << :idling} @event.fire(@object) assert_equal [:parked], @object.callbacks end def test_should_support_except_from_requirement @machine.before_transition :except_from => :parked, :do => lambda {|object| object.callbacks << :parked} @machine.before_transition :except_from => :idling, :do => lambda {|object| object.callbacks << :idling} @event.fire(@object) assert_equal [:idling], @object.callbacks end def test_should_support_to_requirement @machine.before_transition :to => :parked, :do => lambda {|object| object.callbacks << :parked} @machine.before_transition :to => :idling, :do => lambda {|object| object.callbacks << :idling} @event.fire(@object) assert_equal [:idling], @object.callbacks end def test_should_support_except_to_requirement @machine.before_transition :except_to => :parked, :do => lambda {|object| object.callbacks << :parked} @machine.before_transition :except_to => :idling, :do => lambda {|object| object.callbacks << :idling} @event.fire(@object) assert_equal [:parked], @object.callbacks end def test_should_support_on_requirement @machine.before_transition :on => :park, :do => lambda {|object| object.callbacks << :park} @machine.before_transition :on => :ignite, :do => lambda {|object| object.callbacks << :ignite} @event.fire(@object) assert_equal [:ignite], @object.callbacks end def test_should_support_except_on_requirement @machine.before_transition :except_on => :park, :do => lambda {|object| object.callbacks << :park} @machine.before_transition :except_on => :ignite, :do => lambda {|object| object.callbacks << :ignite} @event.fire(@object) assert_equal [:park], @object.callbacks end def test_should_support_implicit_requirement @machine.before_transition :parked => :idling, :do => lambda {|object| object.callbacks << :parked} @machine.before_transition :idling => :parked, :do => lambda {|object| object.callbacks << :idling} @event.fire(@object) assert_equal [:parked], @object.callbacks end def test_should_track_states_defined_in_transition_callbacks @machine.before_transition :parked => :idling, :do => lambda {} @machine.after_transition :first_gear => :second_gear, :do => lambda {} @machine.around_transition :third_gear => :fourth_gear, :do => lambda {} assert_equal [:parked, :idling, :first_gear, :second_gear, :third_gear, :fourth_gear], @machine.states.map {|state| state.name} end def test_should_not_duplicate_states_defined_in_multiple_event_transitions @machine.before_transition :parked => :idling, :do => lambda {} @machine.after_transition :first_gear => :second_gear, :do => lambda {} @machine.after_transition :parked => :idling, :do => lambda {} @machine.around_transition :parked => :idling, :do => lambda {} assert_equal [:parked, :idling, :first_gear, :second_gear], @machine.states.map {|state| state.name} end def test_should_define_predicates_for_each_state [:parked?, :idling?].each {|predicate| assert @object.respond_to?(predicate)} end end class MachineWithFailureCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new do attr_accessor :callbacks end @machine = StateMachine::Machine.new(@klass, :initial => :parked) @event = @machine.event :ignite @object = @klass.new @object.callbacks = [] end def test_should_raise_exception_if_implicit_option_specified exception = assert_raise(ArgumentError) {@machine.after_failure :invalid => :valid, :do => lambda {}} assert_equal 'Invalid key(s): invalid', exception.message end def test_should_raise_exception_if_method_not_specified exception = assert_raise(ArgumentError) {@machine.after_failure :on => :ignite} assert_equal 'Method(s) for callback must be specified', exception.message end def test_should_invoke_callbacks_during_failed_transition @machine.after_failure lambda {|object| object.callbacks << 'failure'} @event.fire(@object) assert_equal %w(failure), @object.callbacks end def test_should_allow_multiple_callbacks @machine.after_failure lambda {|object| object.callbacks << 'failure1'}, lambda {|object| object.callbacks << 'failure2'} @event.fire(@object) assert_equal %w(failure1 failure2), @object.callbacks end def test_should_allow_multiple_callbacks_with_requirements @machine.after_failure lambda {|object| object.callbacks << 'failure_ignite1'}, lambda {|object| object.callbacks << 'failure_ignite2'}, :on => :ignite @machine.after_failure lambda {|object| object.callbacks << 'failure_park1'}, lambda {|object| object.callbacks << 'failure_park2'}, :on => :park @event.fire(@object) assert_equal %w(failure_ignite1 failure_ignite2), @object.callbacks end end class MachineWithPathsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.event :ignite do transition :parked => :idling end @machine.event :shift_up do transition :first_gear => :second_gear end @object = @klass.new @object.state = 'parked' end def test_should_have_paths assert_equal [[StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)]], @machine.paths_for(@object) end def test_should_allow_requirement_configuration assert_equal [[StateMachine::Transition.new(@object, @machine, :shift_up, :first_gear, :second_gear)]], @machine.paths_for(@object, :from => :first_gear) end end class MachineWithOwnerSubclassTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @subclass = Class.new(@klass) end def test_should_have_a_different_collection_of_state_machines assert_not_same @klass.state_machines, @subclass.state_machines end def test_should_have_the_same_attribute_associated_state_machines assert_equal @klass.state_machines, @subclass.state_machines end end class MachineWithExistingMachinesOnOwnerClassTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @second_machine = StateMachine::Machine.new(@klass, :status, :initial => :idling) @object = @klass.new end def test_should_track_each_state_machine expected = {:state => @machine, :status => @second_machine} assert_equal expected, @klass.state_machines end def test_should_initialize_state_for_both_machines assert_equal 'parked', @object.state assert_equal 'idling', @object.status end end class MachineWithExistingMachinesWithSameAttributesOnOwnerClassTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @second_machine = StateMachine::Machine.new(@klass, :public_state, :initial => :idling, :attribute => :state) @object = @klass.new end def test_should_track_each_state_machine expected = {:state => @machine, :public_state => @second_machine} assert_equal expected, @klass.state_machines end def test_should_write_to_state_only_once @klass.class_eval do attr_reader :write_count def state=(value) @write_count ||= 0 @write_count += 1 end end object = @klass.new assert_equal 1, object.write_count end def test_should_initialize_based_on_first_machine assert_equal 'parked', @object.state end def test_should_not_allow_second_machine_to_initialize_state @object.state = nil @second_machine.initialize_state(@object) assert_nil @object.state end def test_should_allow_transitions_on_both_machines @machine.event :ignite do transition :parked => :idling end @second_machine.event :park do transition :idling => :parked end @object.ignite assert_equal 'idling', @object.state @object.park assert_equal 'parked', @object.state end def test_should_copy_new_states_to_sibling_machines @first_gear = @machine.state :first_gear assert_equal @first_gear, @second_machine.state(:first_gear) @second_gear = @second_machine.state :second_gear assert_equal @second_gear, @machine.state(:second_gear) end def test_should_copy_all_existing_states_to_new_machines third_machine = StateMachine::Machine.new(@klass, :protected_state, :attribute => :state) assert_equal @machine.state(:parked), third_machine.state(:parked) assert_equal @machine.state(:idling), third_machine.state(:idling) end end class MachineWithExistingMachinesWithSameAttributesOnOwnerSubclassTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @second_machine = StateMachine::Machine.new(@klass, :public_state, :initial => :idling, :attribute => :state) @subclass = Class.new(@klass) @object = @subclass.new end def test_should_not_copy_sibling_machines_to_subclass_after_initialization @subclass.state_machine(:state) {} assert_equal @klass.state_machine(:public_state), @subclass.state_machine(:public_state) end def test_should_copy_sibling_machines_to_subclass_after_new_state subclass_machine = @subclass.state_machine(:state) {} subclass_machine.state :first_gear assert_not_equal @klass.state_machine(:public_state), @subclass.state_machine(:public_state) end def test_should_copy_new_states_to_sibling_machines subclass_machine = @subclass.state_machine(:state) {} @first_gear = subclass_machine.state :first_gear second_subclass_machine = @subclass.state_machine(:public_state) assert_equal @first_gear, second_subclass_machine.state(:first_gear) end end class MachineWithNamespaceTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm', :initial => :active) do event :enable do transition :off => :active end event :disable do transition :active => :off end end @object = @klass.new end def test_should_namespace_state_predicates [:alarm_active?, :alarm_off?].each do |name| assert @object.respond_to?(name) end end def test_should_namespace_event_checks [:can_enable_alarm?, :can_disable_alarm?].each do |name| assert @object.respond_to?(name) end end def test_should_namespace_event_transition_readers [:enable_alarm_transition, :disable_alarm_transition].each do |name| assert @object.respond_to?(name) end end def test_should_namespace_events [:enable_alarm, :disable_alarm].each do |name| assert @object.respond_to?(name) end end def test_should_namespace_bang_events [:enable_alarm!, :disable_alarm!].each do |name| assert @object.respond_to?(name) end end end class MachineWithCustomAttributeTest < Test::Unit::TestCase def setup StateMachine::Integrations.const_set('Custom', Module.new do include StateMachine::Integrations::Base @defaults = {:action => :save, :use_transactions => false} def create_with_scope(name) lambda {} end def create_without_scope(name) lambda {} end end) @klass = Class.new @machine = StateMachine::Machine.new(@klass, :state, :attribute => :state_id, :initial => :active, :integration => :custom) do event :ignite do transition :parked => :idling end end @object = @klass.new end def test_should_define_a_reader_attribute_for_the_attribute assert @object.respond_to?(:state_id) end def test_should_define_a_writer_attribute_for_the_attribute assert @object.respond_to?(:state_id=) end def test_should_define_a_predicate_for_the_attribute assert @object.respond_to?(:state?) end def test_should_define_a_name_reader_for_the_attribute assert @object.respond_to?(:state_name) end def test_should_define_a_human_name_reader_for_the_attribute assert @object.respond_to?(:state_name) end def test_should_define_an_event_reader_for_the_attribute assert @object.respond_to?(:state_events) end def test_should_define_a_transition_reader_for_the_attribute assert @object.respond_to?(:state_transitions) end def test_should_define_a_path_reader_for_the_attribute assert @object.respond_to?(:state_paths) end def test_should_define_an_event_runner_for_the_attribute assert @object.respond_to?(:fire_state_event) end def test_should_define_a_human_attribute_name_reader assert @klass.respond_to?(:human_state_name) end def test_should_define_a_human_event_name_reader assert @klass.respond_to?(:human_state_event_name) end def test_should_define_singular_with_scope assert @klass.respond_to?(:with_state) end def test_should_define_singular_without_scope assert @klass.respond_to?(:without_state) end def test_should_define_plural_with_scope assert @klass.respond_to?(:with_states) end def test_should_define_plural_without_scope assert @klass.respond_to?(:without_states) end def test_should_define_state_machines_reader expected = {:state => @machine} assert_equal expected, @klass.state_machines end def teardown StateMachine::Integrations.send(:remove_const, 'Custom') end end class MachineFinderWithoutExistingMachineTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.find_or_create(@klass) end def test_should_accept_a_block called = false StateMachine::Machine.find_or_create(Class.new) do called = respond_to?(:event) end assert called end def test_should_create_a_new_machine assert_not_nil @machine end def test_should_use_default_state assert_equal :state, @machine.attribute end end class MachineFinderWithExistingOnSameClassTest < Test::Unit::TestCase def setup @klass = Class.new @existing_machine = StateMachine::Machine.new(@klass) @machine = StateMachine::Machine.find_or_create(@klass) end def test_should_accept_a_block called = false StateMachine::Machine.find_or_create(@klass) do called = respond_to?(:event) end assert called end def test_should_not_create_a_new_machine assert_same @machine, @existing_machine end end class MachineFinderWithExistingMachineOnSuperclassTest < Test::Unit::TestCase def setup integration = Module.new do include StateMachine::Integrations::Base def self.matches?(klass) false end end StateMachine::Integrations.const_set('Custom', integration) @base_class = Class.new @base_machine = StateMachine::Machine.new(@base_class, :status, :action => :save, :integration => :custom) @base_machine.event(:ignite) {} @base_machine.before_transition(lambda {}) @base_machine.after_transition(lambda {}) @base_machine.around_transition(lambda {}) @klass = Class.new(@base_class) @machine = StateMachine::Machine.find_or_create(@klass, :status) {} end def test_should_accept_a_block called = false StateMachine::Machine.find_or_create(Class.new(@base_class)) do called = respond_to?(:event) end assert called end def test_should_not_create_a_new_machine_if_no_block_or_options machine = StateMachine::Machine.find_or_create(Class.new(@base_class), :status) assert_same machine, @base_machine end def test_should_create_a_new_machine_if_given_options machine = StateMachine::Machine.find_or_create(@klass, :status, :initial => :parked) assert_not_nil machine assert_not_same machine, @base_machine end def test_should_create_a_new_machine_if_given_block assert_not_nil @machine assert_not_same @machine, @base_machine end def test_should_copy_the_base_attribute assert_equal :status, @machine.attribute end def test_should_copy_the_base_configuration assert_equal :save, @machine.action end def test_should_copy_events # Can't assert equal arrays since their machines change assert_equal 1, @machine.events.length end def test_should_copy_before_callbacks assert_equal @base_machine.callbacks[:before], @machine.callbacks[:before] end def test_should_copy_after_transitions assert_equal @base_machine.callbacks[:after], @machine.callbacks[:after] end def test_should_use_the_same_integration assert((class << @machine; ancestors; end).include?(StateMachine::Integrations::Custom)) end def teardown StateMachine::Integrations.send(:remove_const, 'Custom') end end class MachineFinderCustomOptionsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.find_or_create(@klass, :status, :initial => :parked) @object = @klass.new end def test_should_use_custom_attribute assert_equal :status, @machine.attribute end def test_should_set_custom_initial_state assert_equal :parked, @machine.initial_state(@object).name end end begin # Load library require 'graphviz' class MachineDrawingTest < Test::Unit::TestCase def setup @klass = Class.new do def self.name; @name ||= "Vehicle_#{rand(1000000)}"; end end @machine = StateMachine::Machine.new(@klass, :initial => :parked) @machine.event :ignite do transition :parked => :idling end end def test_should_raise_exception_if_invalid_option_specified assert_raise(ArgumentError) {@machine.draw(:invalid => true)} end def test_should_save_file_with_class_name_by_default @machine.draw assert File.exists?("./#{@klass.name}_state.png") end def test_should_allow_base_name_to_be_customized name = "machine_#{rand(1000000)}" @machine.draw(:name => name) @path = "./#{name}.png" assert File.exists?(@path) end def test_should_allow_format_to_be_customized @machine.draw(:format => 'jpg') @path = "./#{@klass.name}_state.jpg" assert File.exists?(@path) end def test_should_allow_path_to_be_customized @machine.draw(:path => "#{File.dirname(__FILE__)}/") @path = "#{File.dirname(__FILE__)}/#{@klass.name}_state.png" assert File.exists?(@path) end def test_should_allow_orientation_to_be_landscape graph = @machine.draw(:orientation => 'landscape') assert_equal 'LR', graph['rankdir'].to_s.gsub('"', '') end def test_should_allow_orientation_to_be_portrait graph = @machine.draw(:orientation => 'portrait') assert_equal 'TB', graph['rankdir'].to_s.gsub('"', '') end if Constants::RGV_VERSION != '0.9.0' def test_should_allow_human_names_to_be_displayed @machine.event :ignite, :human_name => 'Ignite' @machine.state :parked, :human_name => 'Parked' @machine.state :idling, :human_name => 'Idling' graph = @machine.draw(:human_names => true) parked_node = graph.get_node('parked') assert_equal 'Parked', parked_node['label'].to_s.gsub('"', '') idling_node = graph.get_node('idling') assert_equal 'Idling', idling_node['label'].to_s.gsub('"', '') end end def teardown FileUtils.rm Dir[@path || "./#{@klass.name}_state.png"] end end class MachineDrawingWithIntegerStatesTest < Test::Unit::TestCase def setup @klass = Class.new do def self.name; @name ||= "Vehicle_#{rand(1000000)}"; end end @machine = StateMachine::Machine.new(@klass, :state_id, :initial => :parked) @machine.event :ignite do transition :parked => :idling end @machine.state :parked, :value => 1 @machine.state :idling, :value => 2 @graph = @machine.draw end def test_should_draw_all_states assert_equal 3, @graph.node_count end def test_should_draw_all_events assert_equal 2, @graph.edge_count end def test_should_draw_machine assert File.exist?("./#{@klass.name}_state_id.png") end def teardown FileUtils.rm Dir["./#{@klass.name}_state_id.png"] end end class MachineDrawingWithNilStatesTest < Test::Unit::TestCase def setup @klass = Class.new do def self.name; @name ||= "Vehicle_#{rand(1000000)}"; end end @machine = StateMachine::Machine.new(@klass, :initial => :parked) @machine.event :ignite do transition :parked => :idling end @machine.state :parked, :value => nil @graph = @machine.draw end def test_should_draw_all_states assert_equal 3, @graph.node_count end def test_should_draw_all_events assert_equal 2, @graph.edge_count end def test_should_draw_machine assert File.exist?("./#{@klass.name}_state.png") end def teardown FileUtils.rm Dir["./#{@klass.name}_state.png"] end end class MachineDrawingWithDynamicStatesTest < Test::Unit::TestCase def setup @klass = Class.new do def self.name; @name ||= "Vehicle_#{rand(1000000)}"; end end @machine = StateMachine::Machine.new(@klass, :initial => :parked) @machine.event :activate do transition :parked => :idling end @machine.state :idling, :value => lambda {Time.now} @graph = @machine.draw end def test_should_draw_all_states assert_equal 3, @graph.node_count end def test_should_draw_all_events assert_equal 2, @graph.edge_count end def test_should_draw_machine assert File.exist?("./#{@klass.name}_state.png") end def teardown FileUtils.rm Dir["./#{@klass.name}_state.png"] end end class MachineClassDrawingTest < Test::Unit::TestCase def setup @klass = Class.new do def self.name; @name ||= "Vehicle_#{rand(1000000)}"; end end @machine = StateMachine::Machine.new(@klass) @machine.event :ignite do transition :parked => :idling end end def test_should_raise_exception_if_no_class_names_specified exception = assert_raise(ArgumentError) {StateMachine::Machine.draw(nil)} assert_equal 'At least one class must be specified', exception.message end def test_should_load_files StateMachine::Machine.draw('Switch', :file => File.expand_path("#{File.dirname(__FILE__)}/../files/switch.rb")) assert defined?(::Switch) end def test_should_allow_path_and_format_to_be_customized StateMachine::Machine.draw('Switch', :file => File.expand_path("#{File.dirname(__FILE__)}/../files/switch.rb"), :path => "#{File.dirname(__FILE__)}/", :format => 'jpg') assert File.exist?("#{File.dirname(__FILE__)}/#{Switch.name}_state.jpg") end def teardown FileUtils.rm Dir["{.,#{File.dirname(__FILE__)}}/#{Switch.name}_state.{jpg,png}"] end end rescue LoadError $stderr.puts 'Skipping GraphViz StateMachine::Machine tests. `gem install ruby-graphviz` >= v0.9.17 and try again.' end unless ENV['TRAVIS'] state-machine-1.2.0/test/unit/machine_collection_test.rb0000644000175000017500000004314312305405267022764 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class MachineCollectionByDefaultTest < Test::Unit::TestCase def setup @machines = StateMachine::MachineCollection.new end def test_should_not_have_any_machines assert @machines.empty? end end class MachineCollectionStateInitializationTest < Test::Unit::TestCase def setup @machines = StateMachine::MachineCollection.new @klass = Class.new @machines[:state] = StateMachine::Machine.new(@klass, :state, :initial => :parked) @machines[:alarm_state] = StateMachine::Machine.new(@klass, :alarm_state, :initial => lambda {|object| :active}) @machines[:alarm_state].state :active, :value => lambda {'active'} # Prevent the auto-initialization hook from firing @klass.class_eval do def initialize end end @object = @klass.new @object.state = nil @object.alarm_state = nil end def test_should_raise_exception_if_invalid_option_specified assert_raise(ArgumentError) {@machines.initialize_states(@object, :invalid => true)} end def test_should_only_initialize_static_states_prior_to_block @machines.initialize_states(@object) do @state_in_block = @object.state @alarm_state_in_block = @object.alarm_state end assert_equal 'parked', @state_in_block assert_nil @alarm_state_in_block end def test_should_only_initialize_dynamic_states_after_block @machines.initialize_states(@object) do @alarm_state_in_block = @object.alarm_state end assert_nil @alarm_state_in_block assert_equal 'active', @object.alarm_state end def test_should_initialize_all_states_without_block @machines.initialize_states(@object) assert_equal 'parked', @object.state assert_equal 'active', @object.alarm_state end def test_should_skip_static_states_if_disabled @machines.initialize_states(@object, :static => false) assert_nil @object.state assert_equal 'active', @object.alarm_state end def test_should_not_initialize_existing_static_states_by_default @object.state = 'idling' @machines.initialize_states(@object) assert_equal 'idling', @object.state end def test_should_initialize_existing_static_states_if_forced @object.state = 'idling' @machines.initialize_states(@object, :static => :force) assert_equal 'parked', @object.state end def test_should_not_initialize_existing_static_states_if_not_forced @object.state = 'idling' @machines.initialize_states(@object, :static => true) assert_equal 'idling', @object.state end def test_should_skip_dynamic_states_if_disabled @machines.initialize_states(@object, :dynamic => false) assert_equal 'parked', @object.state assert_nil @object.alarm_state end def test_should_not_initialize_existing_dynamic_states_by_default @object.alarm_state = 'inactive' @machines.initialize_states(@object) assert_equal 'inactive', @object.alarm_state end def test_should_initialize_existing_dynamic_states_if_forced @object.alarm_state = 'inactive' @machines.initialize_states(@object, :dynamic => :force) assert_equal 'active', @object.alarm_state end def test_should_not_initialize_existing_dynamic_states_if_not_forced @object.alarm_state = 'inactive' @machines.initialize_states(@object, :dynamic => true) assert_equal 'inactive', @object.alarm_state end end class MachineCollectionFireTest < Test::Unit::TestCase def setup @machines = StateMachine::MachineCollection.new @klass = Class.new do attr_reader :saved def save @saved = true end end # First machine @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save) @state.event :ignite do transition :parked => :idling end @state.event :park do transition :idling => :parked end # Second machine @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :action => :save, :namespace => 'alarm') @alarm_state.event :enable do transition :off => :active end @alarm_state.event :disable do transition :active => :off end @object = @klass.new end def test_should_raise_exception_if_invalid_event_specified exception = assert_raise(StateMachine::InvalidEvent) { @machines.fire_events(@object, :invalid) } assert_equal :invalid, exception.event exception = assert_raise(StateMachine::InvalidEvent) { @machines.fire_events(@object, :ignite, :invalid) } assert_equal :invalid, exception.event end def test_should_fail_if_any_event_cannot_transition assert !@machines.fire_events(@object, :park, :disable_alarm) assert_equal 'parked', @object.state assert_equal 'active', @object.alarm_state assert !@object.saved assert !@machines.fire_events(@object, :ignite, :enable_alarm) assert_equal 'parked', @object.state assert_equal 'active', @object.alarm_state assert !@object.saved end def test_should_run_failure_callbacks_if_any_event_cannot_transition @state_failure_run = @alarm_state_failure_run = false @machines[:state].after_failure {@state_failure_run = true} @machines[:alarm_state].after_failure {@alarm_state_failure_run = true} assert !@machines.fire_events(@object, :park, :disable_alarm) assert @state_failure_run assert !@alarm_state_failure_run end def test_should_be_successful_if_all_events_transition assert @machines.fire_events(@object, :ignite, :disable_alarm) assert_equal 'idling', @object.state assert_equal 'off', @object.alarm_state assert @object.saved end def test_should_not_save_if_skipping_action assert @machines.fire_events(@object, :ignite, :disable_alarm, false) assert_equal 'idling', @object.state assert_equal 'off', @object.alarm_state assert !@object.saved end end class MachineCollectionFireWithTransactionsTest < Test::Unit::TestCase def setup @machines = StateMachine::MachineCollection.new @klass = Class.new do attr_accessor :allow_save def save @allow_save end end StateMachine::Integrations.const_set('Custom', Module.new do include StateMachine::Integrations::Base attr_reader :rolled_back def transaction(object) @rolled_back = yield end end) # First machine @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save, :integration => :custom) @state.event :ignite do transition :parked => :idling end # Second machine @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :action => :save, :namespace => 'alarm', :integration => :custom) @alarm_state.event :disable do transition :active => :off end @object = @klass.new end def test_should_not_rollback_if_successful @object.allow_save = true assert @machines.fire_events(@object, :ignite, :disable_alarm) assert_equal true, @state.rolled_back assert_nil @alarm_state.rolled_back assert_equal 'idling', @object.state assert_equal 'off', @object.alarm_state end def test_should_rollback_if_not_successful @object.allow_save = false assert !@machines.fire_events(@object, :ignite, :disable_alarm) assert_equal false, @state.rolled_back assert_nil @alarm_state.rolled_back assert_equal 'parked', @object.state assert_equal 'active', @object.alarm_state end def test_should_run_failure_callbacks_if_not_successful @object.allow_save = false @state_failure_run = @alarm_state_failure_run = false @machines[:state].after_failure {@state_failure_run = true} @machines[:alarm_state].after_failure {@alarm_state_failure_run = true} assert !@machines.fire_events(@object, :ignite, :disable_alarm) assert @state_failure_run assert @alarm_state_failure_run end def teardown StateMachine::Integrations.send(:remove_const, 'Custom') end end class MachineCollectionFireWithValidationsTest < Test::Unit::TestCase def setup StateMachine::Integrations.const_set('Custom', Module.new do include StateMachine::Integrations::Base def invalidate(object, attribute, message, values = []) (object.errors ||= []) << generate_message(message, values) end def reset(object) object.errors = [] end end) @klass = Class.new do attr_accessor :errors def initialize @errors = [] super end end @machines = StateMachine::MachineCollection.new @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :integration => :custom) @state.event :ignite do transition :parked => :idling end @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :namespace => 'alarm', :integration => :custom) @alarm_state.event :disable do transition :active => :off end @object = @klass.new end def test_should_not_invalidate_if_transitions_exist assert @machines.fire_events(@object, :ignite, :disable_alarm) assert_equal [], @object.errors end def test_should_invalidate_if_no_transitions_exist @object.state = 'idling' @object.alarm_state = 'off' assert !@machines.fire_events(@object, :ignite, :disable_alarm) assert_equal ['cannot transition via "ignite"', 'cannot transition via "disable"'], @object.errors end def test_should_run_failure_callbacks_if_no_transitions_exist @object.state = 'idling' @object.alarm_state = 'off' @state_failure_run = @alarm_state_failure_run = false @machines[:state].after_failure {@state_failure_run = true} @machines[:alarm_state].after_failure {@alarm_state_failure_run = true} assert !@machines.fire_events(@object, :ignite, :disable_alarm) assert @state_failure_run assert @alarm_state_failure_run end def teardown StateMachine::Integrations.send(:remove_const, 'Custom') end end class MachineCollectionTransitionsWithoutEventsTest < Test::Unit::TestCase def setup @klass = Class.new @machines = StateMachine::MachineCollection.new @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save) @machine.event :ignite do transition :parked => :idling end @object = @klass.new @object.state_event = nil @transitions = @machines.transitions(@object, :save) end def test_should_be_empty assert @transitions.empty? end def test_should_perform assert_equal true, @transitions.perform end end class MachineCollectionTransitionsWithBlankEventsTest < Test::Unit::TestCase def setup @klass = Class.new @machines = StateMachine::MachineCollection.new @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save) @machine.event :ignite do transition :parked => :idling end @object = @klass.new @object.state_event = '' @transitions = @machines.transitions(@object, :save) end def test_should_be_empty assert @transitions.empty? end def test_should_perform assert_equal true, @transitions.perform end end class MachineCollectionTransitionsWithInvalidEventsTest < Test::Unit::TestCase def setup @klass = Class.new @machines = StateMachine::MachineCollection.new @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save) @machine.event :ignite do transition :parked => :idling end @object = @klass.new @object.state_event = 'invalid' @transitions = @machines.transitions(@object, :save) end def test_should_be_empty assert @transitions.empty? end def test_should_not_perform assert_equal false, @transitions.perform end end class MachineCollectionTransitionsWithoutTransitionTest < Test::Unit::TestCase def setup @klass = Class.new @machines = StateMachine::MachineCollection.new @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save) @machine.event :ignite do transition :parked => :idling end @object = @klass.new @object.state = 'idling' @object.state_event = 'ignite' @transitions = @machines.transitions(@object, :save) end def test_should_be_empty assert @transitions.empty? end def test_should_not_perform assert_equal false, @transitions.perform end end class MachineCollectionTransitionsWithTransitionTest < Test::Unit::TestCase def setup @klass = Class.new @machines = StateMachine::MachineCollection.new @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save) @machine.event :ignite do transition :parked => :idling end @object = @klass.new @object.state_event = 'ignite' @transitions = @machines.transitions(@object, :save) end def test_should_not_be_empty assert_equal 1, @transitions.length end def test_should_perform assert_equal true, @transitions.perform end end class MachineCollectionTransitionsWithSameActionsTest < Test::Unit::TestCase def setup @klass = Class.new @machines = StateMachine::MachineCollection.new @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save) @machine.event :ignite do transition :parked => :idling end @machines[:status] = @machine = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save) @machine.event :shift_up do transition :first_gear => :second_gear end @object = @klass.new @object.state_event = 'ignite' @object.status_event = 'shift_up' @transitions = @machines.transitions(@object, :save) end def test_should_not_be_empty assert_equal 2, @transitions.length end def test_should_perform assert_equal true, @transitions.perform end end class MachineCollectionTransitionsWithDifferentActionsTest < Test::Unit::TestCase def setup @klass = Class.new @machines = StateMachine::MachineCollection.new @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save) @state.event :ignite do transition :parked => :idling end @machines[:status] = @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :persist) @status.event :shift_up do transition :first_gear => :second_gear end @object = @klass.new @object.state_event = 'ignite' @object.status_event = 'shift_up' @transitions = @machines.transitions(@object, :save) end def test_should_only_select_matching_actions assert_equal 1, @transitions.length end end class MachineCollectionTransitionsWithExisitingTransitionsTest < Test::Unit::TestCase def setup @klass = Class.new @machines = StateMachine::MachineCollection.new @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save) @machine.event :ignite do transition :parked => :idling end @object = @klass.new @object.send(:state_event_transition=, StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)) @transitions = @machines.transitions(@object, :save) end def test_should_not_be_empty assert_equal 1, @transitions.length end def test_should_perform assert_equal true, @transitions.perform end end class MachineCollectionTransitionsWithCustomOptionsTest < Test::Unit::TestCase def setup @klass = Class.new @machines = StateMachine::MachineCollection.new @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save) @machine.event :ignite do transition :parked => :idling end @object = @klass.new @transitions = @machines.transitions(@object, :save, :after => false) end def test_should_use_custom_options assert @transitions.skip_after end end class MachineCollectionFireAttributesWithValidationsTest < Test::Unit::TestCase def setup @klass = Class.new do attr_accessor :errors def initialize @errors = [] super end end @machines = StateMachine::MachineCollection.new @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save) @machine.event :ignite do transition :parked => :idling end class << @machine def invalidate(object, attribute, message, values = []) (object.errors ||= []) << generate_message(message, values) end def reset(object) object.errors = [] end end @object = @klass.new end def test_should_invalidate_if_event_is_invalid @object.state_event = 'invalid' @machines.transitions(@object, :save) assert !@object.errors.empty? end def test_should_invalidate_if_no_transition_exists @object.state = 'idling' @object.state_event = 'ignite' @machines.transitions(@object, :save) assert !@object.errors.empty? end def test_should_not_invalidate_if_transition_exists @object.state_event = 'ignite' @machines.transitions(@object, :save) assert @object.errors.empty? end end state-machine-1.2.0/test/unit/branch_test.rb0000644000175000017500000007141112305405267020401 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class BranchTest < Test::Unit::TestCase def setup @branch = StateMachine::Branch.new(:from => :parked, :to => :idling) end def test_should_not_raise_exception_if_implicit_option_specified assert_nothing_raised { StateMachine::Branch.new(:invalid => :valid) } end def test_should_not_have_an_if_condition assert_nil @branch.if_condition end def test_should_not_have_an_unless_condition assert_nil @branch.unless_condition end def test_should_have_a_state_requirement assert_equal 1, @branch.state_requirements.length end def test_should_raise_an_exception_if_invalid_match_option_specified exception = assert_raise(ArgumentError) { @branch.match(Object.new, :invalid => true) } assert_equal 'Invalid key(s): invalid', exception.message end end class BranchWithNoRequirementsTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new end def test_should_use_all_matcher_for_event_requirement assert_equal StateMachine::AllMatcher.instance, @branch.event_requirement end def test_should_use_all_matcher_for_from_state_requirement assert_equal StateMachine::AllMatcher.instance, @branch.state_requirements.first[:from] end def test_should_use_all_matcher_for_to_state_requirement assert_equal StateMachine::AllMatcher.instance, @branch.state_requirements.first[:to] end def test_should_match_empty_query assert @branch.matches?(@object, {}) end def test_should_match_non_empty_query assert @branch.matches?(@object, :to => :idling, :from => :parked, :on => :ignite) end def test_should_include_all_requirements_in_match match = @branch.match(@object, {}) assert_equal @branch.state_requirements.first[:from], match[:from] assert_equal @branch.state_requirements.first[:to], match[:to] assert_equal @branch.event_requirement, match[:on] end end class BranchWithFromRequirementTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:from => :parked) end def test_should_use_a_whitelist_matcher assert_instance_of StateMachine::WhitelistMatcher, @branch.state_requirements.first[:from] end def test_should_match_if_not_specified assert @branch.matches?(@object, :to => :idling) end def test_should_match_if_included assert @branch.matches?(@object, :from => :parked) end def test_should_not_match_if_not_included assert !@branch.matches?(@object, :from => :idling) end def test_should_not_match_if_nil assert !@branch.matches?(@object, :from => nil) end def test_should_ignore_to assert @branch.matches?(@object, :from => :parked, :to => :idling) end def test_should_ignore_on assert @branch.matches?(@object, :from => :parked, :on => :ignite) end def test_should_be_included_in_known_states assert_equal [:parked], @branch.known_states end def test_should_include_requirement_in_match match = @branch.match(@object, :from => :parked) assert_equal @branch.state_requirements.first[:from], match[:from] end end class BranchWithMultipleFromRequirementsTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:from => [:idling, :parked]) end def test_should_match_if_included assert @branch.matches?(@object, :from => :idling) end def test_should_not_match_if_not_included assert !@branch.matches?(@object, :from => :first_gear) end def test_should_be_included_in_known_states assert_equal [:idling, :parked], @branch.known_states end end class BranchWithFromMatcherRequirementTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:from => StateMachine::BlacklistMatcher.new([:idling, :parked])) end def test_should_match_if_included assert @branch.matches?(@object, :from => :first_gear) end def test_should_not_match_if_not_included assert !@branch.matches?(@object, :from => :idling) end def test_include_values_in_known_states assert_equal [:idling, :parked], @branch.known_states end end class BranchWithToRequirementTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:to => :idling) end def test_should_use_a_whitelist_matcher assert_instance_of StateMachine::WhitelistMatcher, @branch.state_requirements.first[:to] end def test_should_match_if_not_specified assert @branch.matches?(@object, :from => :parked) end def test_should_match_if_included assert @branch.matches?(@object, :to => :idling) end def test_should_not_match_if_not_included assert !@branch.matches?(@object, :to => :parked) end def test_should_not_match_if_nil assert !@branch.matches?(@object, :to => nil) end def test_should_ignore_from assert @branch.matches?(@object, :to => :idling, :from => :parked) end def test_should_ignore_on assert @branch.matches?(@object, :to => :idling, :on => :ignite) end def test_should_be_included_in_known_states assert_equal [:idling], @branch.known_states end def test_should_include_requirement_in_match match = @branch.match(@object, :to => :idling) assert_equal @branch.state_requirements.first[:to], match[:to] end end class BranchWithMultipleToRequirementsTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:to => [:idling, :parked]) end def test_should_match_if_included assert @branch.matches?(@object, :to => :idling) end def test_should_not_match_if_not_included assert !@branch.matches?(@object, :to => :first_gear) end def test_should_be_included_in_known_states assert_equal [:idling, :parked], @branch.known_states end end class BranchWithToMatcherRequirementTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:to => StateMachine::BlacklistMatcher.new([:idling, :parked])) end def test_should_match_if_included assert @branch.matches?(@object, :to => :first_gear) end def test_should_not_match_if_not_included assert !@branch.matches?(@object, :to => :idling) end def test_include_values_in_known_states assert_equal [:idling, :parked], @branch.known_states end end class BranchWithOnRequirementTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:on => :ignite) end def test_should_use_a_whitelist_matcher assert_instance_of StateMachine::WhitelistMatcher, @branch.event_requirement end def test_should_match_if_not_specified assert @branch.matches?(@object, :from => :parked) end def test_should_match_if_included assert @branch.matches?(@object, :on => :ignite) end def test_should_not_match_if_not_included assert !@branch.matches?(@object, :on => :park) end def test_should_not_match_if_nil assert !@branch.matches?(@object, :on => nil) end def test_should_ignore_to assert @branch.matches?(@object, :on => :ignite, :to => :parked) end def test_should_ignore_from assert @branch.matches?(@object, :on => :ignite, :from => :parked) end def test_should_not_be_included_in_known_states assert_equal [], @branch.known_states end def test_should_include_requirement_in_match match = @branch.match(@object, :on => :ignite) assert_equal @branch.event_requirement, match[:on] end end class BranchWithMultipleOnRequirementsTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:on => [:ignite, :park]) end def test_should_match_if_included assert @branch.matches?(@object, :on => :ignite) end def test_should_not_match_if_not_included assert !@branch.matches?(@object, :on => :shift_up) end end class BranchWithOnMatcherRequirementTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:on => StateMachine::BlacklistMatcher.new([:ignite, :park])) end def test_should_match_if_included assert @branch.matches?(@object, :on => :shift_up) end def test_should_not_match_if_not_included assert !@branch.matches?(@object, :on => :ignite) end end class BranchWithExceptFromRequirementTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:except_from => :parked) end def test_should_use_a_blacklist_matcher assert_instance_of StateMachine::BlacklistMatcher, @branch.state_requirements.first[:from] end def test_should_match_if_not_included assert @branch.matches?(@object, :from => :idling) end def test_should_not_match_if_included assert !@branch.matches?(@object, :from => :parked) end def test_should_match_if_nil assert @branch.matches?(@object, :from => nil) end def test_should_ignore_to assert @branch.matches?(@object, :from => :idling, :to => :parked) end def test_should_ignore_on assert @branch.matches?(@object, :from => :idling, :on => :ignite) end def test_should_be_included_in_known_states assert_equal [:parked], @branch.known_states end end class BranchWithMultipleExceptFromRequirementsTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:except_from => [:idling, :parked]) end def test_should_match_if_not_included assert @branch.matches?(@object, :from => :first_gear) end def test_should_not_match_if_included assert !@branch.matches?(@object, :from => :idling) end def test_should_be_included_in_known_states assert_equal [:idling, :parked], @branch.known_states end end class BranchWithExceptFromMatcherRequirementTest < Test::Unit::TestCase def test_should_raise_an_exception exception = assert_raise(ArgumentError) { StateMachine::Branch.new(:except_from => StateMachine::AllMatcher.instance) } assert_equal ':except_from option cannot use matchers; use :from instead', exception.message end end class BranchWithExceptToRequirementTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:except_to => :idling) end def test_should_use_a_blacklist_matcher assert_instance_of StateMachine::BlacklistMatcher, @branch.state_requirements.first[:to] end def test_should_match_if_not_included assert @branch.matches?(@object, :to => :parked) end def test_should_not_match_if_included assert !@branch.matches?(@object, :to => :idling) end def test_should_match_if_nil assert @branch.matches?(@object, :to => nil) end def test_should_ignore_from assert @branch.matches?(@object, :to => :parked, :from => :idling) end def test_should_ignore_on assert @branch.matches?(@object, :to => :parked, :on => :ignite) end def test_should_be_included_in_known_states assert_equal [:idling], @branch.known_states end end class BranchWithMultipleExceptToRequirementsTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:except_to => [:idling, :parked]) end def test_should_match_if_not_included assert @branch.matches?(@object, :to => :first_gear) end def test_should_not_match_if_included assert !@branch.matches?(@object, :to => :idling) end def test_should_be_included_in_known_states assert_equal [:idling, :parked], @branch.known_states end end class BranchWithExceptToMatcherRequirementTest < Test::Unit::TestCase def test_should_raise_an_exception exception = assert_raise(ArgumentError) { StateMachine::Branch.new(:except_to => StateMachine::AllMatcher.instance) } assert_equal ':except_to option cannot use matchers; use :to instead', exception.message end end class BranchWithExceptOnRequirementTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:except_on => :ignite) end def test_should_use_a_blacklist_matcher assert_instance_of StateMachine::BlacklistMatcher, @branch.event_requirement end def test_should_match_if_not_included assert @branch.matches?(@object, :on => :park) end def test_should_not_match_if_included assert !@branch.matches?(@object, :on => :ignite) end def test_should_match_if_nil assert @branch.matches?(@object, :on => nil) end def test_should_ignore_to assert @branch.matches?(@object, :on => :park, :to => :idling) end def test_should_ignore_from assert @branch.matches?(@object, :on => :park, :from => :parked) end def test_should_not_be_included_in_known_states assert_equal [], @branch.known_states end end class BranchWithExceptOnMatcherRequirementTest < Test::Unit::TestCase def test_should_raise_an_exception exception = assert_raise(ArgumentError) { StateMachine::Branch.new(:except_on => StateMachine::AllMatcher.instance) } assert_equal ':except_on option cannot use matchers; use :on instead', exception.message end end class BranchWithMultipleExceptOnRequirementsTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:except_on => [:ignite, :park]) end def test_should_match_if_not_included assert @branch.matches?(@object, :on => :shift_up) end def test_should_not_match_if_included assert !@branch.matches?(@object, :on => :ignite) end end class BranchWithConflictingFromRequirementsTest < Test::Unit::TestCase def test_should_raise_an_exception exception = assert_raise(ArgumentError) { StateMachine::Branch.new(:from => :parked, :except_from => :parked) } assert_equal 'Conflicting keys: from, except_from', exception.message end end class BranchWithConflictingToRequirementsTest < Test::Unit::TestCase def test_should_raise_an_exception exception = assert_raise(ArgumentError) { StateMachine::Branch.new(:to => :idling, :except_to => :idling) } assert_equal 'Conflicting keys: to, except_to', exception.message end end class BranchWithConflictingOnRequirementsTest < Test::Unit::TestCase def test_should_raise_an_exception exception = assert_raise(ArgumentError) { StateMachine::Branch.new(:on => :ignite, :except_on => :ignite) } assert_equal 'Conflicting keys: on, except_on', exception.message end end class BranchWithDifferentRequirementsTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:from => :parked, :to => :idling, :on => :ignite) end def test_should_match_empty_query assert @branch.matches?(@object) end def test_should_match_if_all_requirements_match assert @branch.matches?(@object, :from => :parked, :to => :idling, :on => :ignite) end def test_should_not_match_if_from_not_included assert !@branch.matches?(@object, :from => :idling) end def test_should_not_match_if_to_not_included assert !@branch.matches?(@object, :to => :parked) end def test_should_not_match_if_on_not_included assert !@branch.matches?(@object, :on => :park) end def test_should_be_nil_if_unmatched assert_nil @branch.match(@object, :from => :parked, :to => :idling, :on => :park) end def test_should_include_all_known_states assert_equal [:parked, :idling], @branch.known_states end def test_should_not_duplicate_known_statse branch = StateMachine::Branch.new(:except_from => :idling, :to => :idling, :on => :ignite) assert_equal [:idling], branch.known_states end end class BranchWithNilRequirementsTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:from => nil, :to => nil) end def test_should_match_empty_query assert @branch.matches?(@object) end def test_should_match_if_all_requirements_match assert @branch.matches?(@object, :from => nil, :to => nil) end def test_should_not_match_if_from_not_included assert !@branch.matches?(@object, :from => :parked) end def test_should_not_match_if_to_not_included assert !@branch.matches?(@object, :to => :idling) end def test_should_include_all_known_states assert_equal [nil], @branch.known_states end end class BranchWithImplicitRequirementTest < Test::Unit::TestCase def setup @branch = StateMachine::Branch.new(:parked => :idling, :on => :ignite) end def test_should_create_an_event_requirement assert_instance_of StateMachine::WhitelistMatcher, @branch.event_requirement assert_equal [:ignite], @branch.event_requirement.values end def test_should_use_a_whitelist_from_matcher assert_instance_of StateMachine::WhitelistMatcher, @branch.state_requirements.first[:from] end def test_should_use_a_whitelist_to_matcher assert_instance_of StateMachine::WhitelistMatcher, @branch.state_requirements.first[:to] end end class BranchWithMultipleImplicitRequirementsTest < Test::Unit::TestCase def setup @object = Object.new @branch = StateMachine::Branch.new(:parked => :idling, :idling => :first_gear, :on => :ignite) end def test_should_create_multiple_state_requirements assert_equal 2, @branch.state_requirements.length end def test_should_not_match_event_as_state_requirement assert !@branch.matches?(@object, :from => :on, :to => :ignite) end def test_should_match_if_from_included_in_any assert @branch.matches?(@object, :from => :parked) assert @branch.matches?(@object, :from => :idling) end def test_should_not_match_if_from_not_included_in_any assert !@branch.matches?(@object, :from => :first_gear) end def test_should_match_if_to_included_in_any assert @branch.matches?(@object, :to => :idling) assert @branch.matches?(@object, :to => :first_gear) end def test_should_not_match_if_to_not_included_in_any assert !@branch.matches?(@object, :to => :parked) end def test_should_match_if_all_options_match assert @branch.matches?(@object, :from => :parked, :to => :idling, :on => :ignite) assert @branch.matches?(@object, :from => :idling, :to => :first_gear, :on => :ignite) end def test_should_not_match_if_any_options_do_not_match assert !@branch.matches?(@object, :from => :parked, :to => :idling, :on => :park) assert !@branch.matches?(@object, :from => :parked, :to => :first_gear, :on => :park) end def test_should_include_all_known_states assert_equal [:first_gear, :idling, :parked], @branch.known_states.sort_by {|state| state.to_s} end def test_should_not_duplicate_known_statse branch = StateMachine::Branch.new(:parked => :idling, :first_gear => :idling) assert_equal [:first_gear, :idling, :parked], branch.known_states.sort_by {|state| state.to_s} end end class BranchWithImplicitFromRequirementMatcherTest < Test::Unit::TestCase def setup @matcher = StateMachine::BlacklistMatcher.new(:parked) @branch = StateMachine::Branch.new(@matcher => :idling) end def test_should_not_convert_from_to_whitelist_matcher assert_equal @matcher, @branch.state_requirements.first[:from] end def test_should_convert_to_to_whitelist_matcher assert_instance_of StateMachine::WhitelistMatcher, @branch.state_requirements.first[:to] end end class BranchWithImplicitToRequirementMatcherTest < Test::Unit::TestCase def setup @matcher = StateMachine::BlacklistMatcher.new(:idling) @branch = StateMachine::Branch.new(:parked => @matcher) end def test_should_convert_from_to_whitelist_matcher assert_instance_of StateMachine::WhitelistMatcher, @branch.state_requirements.first[:from] end def test_should_not_convert_to_to_whitelist_matcher assert_equal @matcher, @branch.state_requirements.first[:to] end end class BranchWithImplicitAndExplicitRequirementsTest < Test::Unit::TestCase def setup @branch = StateMachine::Branch.new(:parked => :idling, :from => :parked) end def test_should_create_multiple_requirements assert_equal 2, @branch.state_requirements.length end def test_should_create_implicit_requirements_for_implicit_options assert(@branch.state_requirements.any? do |state_requirement| state_requirement[:from].values == [:parked] && state_requirement[:to].values == [:idling] end) end def test_should_create_implicit_requirements_for_explicit_options assert(@branch.state_requirements.any? do |state_requirement| state_requirement[:from].values == [:from] && state_requirement[:to].values == [:parked] end) end end class BranchWithIfConditionalTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_have_an_if_condition branch = StateMachine::Branch.new(:if => lambda {true}) assert_not_nil branch.if_condition end def test_should_match_if_true branch = StateMachine::Branch.new(:if => lambda {true}) assert branch.matches?(@object) end def test_should_not_match_if_false branch = StateMachine::Branch.new(:if => lambda {false}) assert !branch.matches?(@object) end def test_should_be_nil_if_unmatched branch = StateMachine::Branch.new(:if => lambda {false}) assert_nil branch.match(@object) end end class BranchWithMultipleIfConditionalsTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_match_if_all_are_true branch = StateMachine::Branch.new(:if => [lambda {true}, lambda {true}]) assert branch.match(@object) end def test_should_not_match_if_any_are_false branch = StateMachine::Branch.new(:if => [lambda {true}, lambda {false}]) assert !branch.match(@object) branch = StateMachine::Branch.new(:if => [lambda {false}, lambda {true}]) assert !branch.match(@object) end end class BranchWithUnlessConditionalTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_have_an_unless_condition branch = StateMachine::Branch.new(:unless => lambda {true}) assert_not_nil branch.unless_condition end def test_should_match_if_false branch = StateMachine::Branch.new(:unless => lambda {false}) assert branch.matches?(@object) end def test_should_not_match_if_true branch = StateMachine::Branch.new(:unless => lambda {true}) assert !branch.matches?(@object) end def test_should_be_nil_if_unmatched branch = StateMachine::Branch.new(:unless => lambda {true}) assert_nil branch.match(@object) end end class BranchWithMultipleUnlessConditionalsTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_match_if_all_are_false branch = StateMachine::Branch.new(:unless => [lambda {false}, lambda {false}]) assert branch.match(@object) end def test_should_not_match_if_any_are_true branch = StateMachine::Branch.new(:unless => [lambda {true}, lambda {false}]) assert !branch.match(@object) branch = StateMachine::Branch.new(:unless => [lambda {false}, lambda {true}]) assert !branch.match(@object) end end class BranchWithConflictingConditionalsTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_match_if_if_is_true_and_unless_is_false branch = StateMachine::Branch.new(:if => lambda {true}, :unless => lambda {false}) assert branch.match(@object) end def test_should_not_match_if_if_is_false_and_unless_is_true branch = StateMachine::Branch.new(:if => lambda {false}, :unless => lambda {true}) assert !branch.match(@object) end def test_should_not_match_if_if_is_false_and_unless_is_false branch = StateMachine::Branch.new(:if => lambda {false}, :unless => lambda {false}) assert !branch.match(@object) end def test_should_not_match_if_if_is_true_and_unless_is_true branch = StateMachine::Branch.new(:if => lambda {true}, :unless => lambda {true}) assert !branch.match(@object) end end class BranchWithoutGuardsTest < Test::Unit::TestCase def setup @object = Object.new end def test_should_match_if_if_is_false branch = StateMachine::Branch.new(:if => lambda {false}) assert branch.matches?(@object, :guard => false) end def test_should_match_if_if_is_true branch = StateMachine::Branch.new(:if => lambda {true}) assert branch.matches?(@object, :guard => false) end def test_should_match_if_unless_is_false branch = StateMachine::Branch.new(:unless => lambda {false}) assert branch.matches?(@object, :guard => false) end def test_should_match_if_unless_is_true branch = StateMachine::Branch.new(:unless => lambda {true}) assert branch.matches?(@object, :guard => false) end end begin # Load library require 'graphviz' class BranchDrawingTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) states = [:parked, :idling] @graph = StateMachine::Graph.new('test') states.each {|state| @graph.add_nodes(state.to_s)} @branch = StateMachine::Branch.new(:from => :idling, :to => :parked) @branch.draw(@graph, :park, states) @edge = @graph.get_edge_at_index(0) end def test_should_create_edges assert_equal 1, @graph.edge_count end def test_should_use_from_state_from_start_node assert_equal 'idling', @edge.node_one(false) end def test_should_use_to_state_for_end_node assert_equal 'parked', @edge.node_two(false) end def test_should_use_event_name_as_label assert_equal 'park', @edge['label'].to_s.gsub('"', '') end end class BranchDrawingWithFromRequirementTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) states = [:parked, :idling, :first_gear] @graph = StateMachine::Graph.new('test') states.each {|state| @graph.add_nodes(state.to_s)} @branch = StateMachine::Branch.new(:from => [:idling, :first_gear], :to => :parked) @branch.draw(@graph, :park, states) end def test_should_generate_edges_for_each_valid_from_state [:idling, :first_gear].each_with_index do |from_state, index| edge = @graph.get_edge_at_index(index) assert_equal from_state.to_s, edge.node_one(false) assert_equal 'parked', edge.node_two(false) end end end class BranchDrawingWithExceptFromRequirementTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) states = [:parked, :idling, :first_gear] @graph = StateMachine::Graph.new('test') states.each {|state| @graph.add_nodes(state.to_s)} @branch = StateMachine::Branch.new(:except_from => :parked, :to => :parked) @branch.draw(@graph, :park, states) end def test_should_generate_edges_for_each_valid_from_state %w(idling first_gear).each_with_index do |from_state, index| edge = @graph.get_edge_at_index(index) assert_equal from_state, edge.node_one(false) assert_equal 'parked', edge.node_two(false) end end end class BranchDrawingWithoutFromRequirementTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) states = [:parked, :idling, :first_gear] @graph = StateMachine::Graph.new('test') states.each {|state| @graph.add_nodes(state.to_s)} @branch = StateMachine::Branch.new(:to => :parked) @branch.draw(@graph, :park, states) end def test_should_generate_edges_for_each_valid_from_state %w(parked idling first_gear).each_with_index do |from_state, index| edge = @graph.get_edge_at_index(index) assert_equal from_state, edge.node_one(false) assert_equal 'parked', edge.node_two(false) end end end class BranchDrawingWithoutToRequirementTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) graph = StateMachine::Graph.new('test') graph.add_nodes('parked') @branch = StateMachine::Branch.new(:from => :parked) @branch.draw(graph, :park, [:parked]) @edge = graph.get_edge_at_index(0) end def test_should_create_loopback_edge assert_equal 'parked', @edge.node_one(false) assert_equal 'parked', @edge.node_two(false) end end class BranchDrawingWithNilStateTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) graph = StateMachine::Graph.new('test') graph.add_nodes('parked') @branch = StateMachine::Branch.new(:from => :idling, :to => nil) @branch.draw(graph, :park, [nil, :idling]) @edge = graph.get_edge_at_index(0) end def test_should_generate_edges_for_each_valid_from_state assert_equal 'idling', @edge.node_one(false) assert_equal 'nil', @edge.node_two(false) end end rescue LoadError $stderr.puts 'Skipping GraphViz StateMachine::Branch tests. `gem install ruby-graphviz` >= v0.9.17 and try again.' end unless ENV['TRAVIS'] state-machine-1.2.0/test/unit/invalid_transition_test.rb0000644000175000017500000000647612305405267023055 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class InvalidTransitionTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @state = @machine.state :parked @machine.event :ignite @object = @klass.new @object.state = 'parked' @invalid_transition = StateMachine::InvalidTransition.new(@object, @machine, :ignite) end def test_should_have_an_object assert_equal @object, @invalid_transition.object end def test_should_have_a_machine assert_equal @machine, @invalid_transition.machine end def test_should_have_an_event assert_equal :ignite, @invalid_transition.event end def test_should_have_a_qualified_event assert_equal :ignite, @invalid_transition.qualified_event end def test_should_have_a_from_value assert_equal 'parked', @invalid_transition.from end def test_should_have_a_from_name assert_equal :parked, @invalid_transition.from_name end def test_should_have_a_qualified_from_name assert_equal :parked, @invalid_transition.qualified_from_name end def test_should_generate_a_message assert_equal 'Cannot transition state via :ignite from :parked', @invalid_transition.message end end class InvalidTransitionWithNamespaceTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm') @state = @machine.state :active @machine.event :disable @object = @klass.new @object.state = 'active' @invalid_transition = StateMachine::InvalidTransition.new(@object, @machine, :disable) end def test_should_have_an_event assert_equal :disable, @invalid_transition.event end def test_should_have_a_qualified_event assert_equal :disable_alarm, @invalid_transition.qualified_event end def test_should_have_a_from_name assert_equal :active, @invalid_transition.from_name end def test_should_have_a_qualified_from_name assert_equal :alarm_active, @invalid_transition.qualified_from_name end end class InvalidTransitionWithIntegrationTest < Test::Unit::TestCase def setup StateMachine::Integrations.const_set('Custom', Module.new do include StateMachine::Integrations::Base def errors_for(object) object.errors end end) @klass = Class.new do attr_accessor :errors end @machine = StateMachine::Machine.new(@klass, :integration => :custom) @machine.state :parked @machine.event :ignite @object = @klass.new @object.state = 'parked' end def test_should_generate_a_message_without_reasons_if_empty @object.errors = '' invalid_transition = StateMachine::InvalidTransition.new(@object, @machine, :ignite) assert_equal 'Cannot transition state via :ignite from :parked', invalid_transition.message end def test_should_generate_a_message_with_error_reasons_if_errors_found @object.errors = 'Id is invalid, Name is invalid' invalid_transition = StateMachine::InvalidTransition.new(@object, @machine, :ignite) assert_equal 'Cannot transition state via :ignite from :parked (Reason(s): Id is invalid, Name is invalid)', invalid_transition.message end def teardown StateMachine::Integrations.send(:remove_const, 'Custom') end end state-machine-1.2.0/test/unit/state_machine_test.rb0000644000175000017500000000126312305405267021746 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class StateMachineByDefaultTest < Test::Unit::TestCase def setup @klass = Class.new @machine = @klass.state_machine end def test_should_use_state_attribute assert_equal :state, @machine.attribute end end class StateMachineTest < Test::Unit::TestCase def setup @klass = Class.new end def test_should_allow_state_machines_on_any_class assert @klass.respond_to?(:state_machine) end def test_should_evaluate_block_within_machine_context responded = false @klass.state_machine(:state) do responded = respond_to?(:event) end assert responded end end state-machine-1.2.0/test/unit/node_collection_test.rb0000644000175000017500000002474712305405267022316 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class Node < Struct.new(:name, :value, :machine) def context yield end end class NodeCollectionByDefaultTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(@machine) end def test_should_not_have_any_nodes assert_equal 0, @collection.length end def test_should_have_a_machine assert_equal @machine, @collection.machine end def test_should_index_by_name @collection << object = Node.new(:parked) assert_equal object, @collection[:parked] end end class NodeCollectionTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(@machine) end def test_should_raise_exception_if_invalid_option_specified exception = assert_raise(ArgumentError) { StateMachine::NodeCollection.new(@machine, :invalid => true) } assert_equal 'Invalid key(s): invalid', exception.message end def test_should_raise_exception_on_lookup_if_invalid_index_specified exception = assert_raise(ArgumentError) { @collection[:something, :invalid] } assert_equal 'Invalid index: :invalid', exception.message end def test_should_raise_exception_on_fetch_if_invalid_index_specified exception = assert_raise(ArgumentError) { @collection.fetch(:something, :invalid) } assert_equal 'Invalid index: :invalid', exception.message end end class NodeCollectionAfterBeingCopiedTest < Test::Unit::TestCase def setup machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(machine) @collection << @parked = Node.new(:parked) @contexts_run = contexts_run = [] @collection.context([:parked]) {contexts_run << :parked} @contexts_run.clear @copied_collection = @collection.dup @copied_collection << @idling = Node.new(:idling) @copied_collection.context([:first_gear]) {contexts_run << :first_gear} end def test_should_not_modify_the_original_list assert_equal 1, @collection.length assert_equal 2, @copied_collection.length end def test_should_not_modify_the_indices assert_nil @collection[:idling] assert_equal @idling, @copied_collection[:idling] end def test_should_copy_each_node assert_not_same @parked, @copied_collection[:parked] end def test_should_not_run_contexts assert_equal [], @contexts_run end def test_should_not_modify_contexts @collection << Node.new(:first_gear) assert_equal [], @contexts_run end def test_should_copy_contexts @copied_collection << Node.new(:parked) assert !@contexts_run.empty? end end class NodeCollectionWithoutIndicesTest < Test::Unit::TestCase def setup machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(machine, :index => {}) end def test_should_allow_adding_node @collection << Object.new assert_equal 1, @collection.length end def test_should_not_allow_keys_retrieval exception = assert_raise(ArgumentError) { @collection.keys } assert_equal 'No indices configured', exception.message end def test_should_not_allow_lookup @collection << Object.new exception = assert_raise(ArgumentError) { @collection[0] } assert_equal 'No indices configured', exception.message end def test_should_not_allow_fetching @collection << Object.new exception = assert_raise(ArgumentError) { @collection.fetch(0) } assert_equal 'No indices configured', exception.message end end class NodeCollectionWithIndicesTest < Test::Unit::TestCase def setup machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(machine, :index => [:name, :value]) @object = Node.new(:parked, 1) @collection << @object end def test_should_use_first_index_by_default_on_key_retrieval assert_equal [:parked], @collection.keys end def test_should_allow_customizing_index_for_key_retrieval assert_equal [1], @collection.keys(:value) end def test_should_use_first_index_by_default_on_lookup assert_equal @object, @collection[:parked] assert_nil @collection[1] end def test_should_allow_customizing_index_on_lookup assert_equal @object, @collection[1, :value] assert_nil @collection[:parked, :value] end def test_should_use_first_index_by_default_on_fetch assert_equal @object, @collection.fetch(:parked) exception = assert_raise(IndexError) { @collection.fetch(1) } assert_equal '1 is an invalid name', exception.message end def test_should_allow_customizing_index_on_fetch assert_equal @object, @collection.fetch(1, :value) exception = assert_raise(IndexError) { @collection.fetch(:parked, :value) } assert_equal ':parked is an invalid value', exception.message end end class NodeCollectionWithNodesTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(@machine) @parked = Node.new(:parked, nil, @machine) @idling = Node.new(:idling, nil, @machine) @collection << @parked @collection << @idling end def test_should_be_able_to_enumerate order = [] @collection.each {|object| order << object} assert_equal [@parked, @idling], order end def test_should_be_able_to_concatenate_multiple_nodes @first_gear = Node.new(:first_gear, nil, @machine) @second_gear = Node.new(:second_gear, nil, @machine) @collection.concat([@first_gear, @second_gear]) order = [] @collection.each {|object| order << object} assert_equal [@parked, @idling, @first_gear, @second_gear], order end def test_should_be_able_to_access_by_index assert_equal @parked, @collection.at(0) assert_equal @idling, @collection.at(1) end def test_should_deep_copy_machine_changes new_machine = StateMachine::Machine.new(Class.new) @collection.machine = new_machine assert_equal new_machine, @collection.machine assert_equal new_machine, @parked.machine assert_equal new_machine, @idling.machine end end class NodeCollectionAfterUpdateTest < Test::Unit::TestCase def setup machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(machine, :index => [:name, :value]) @parked = Node.new(:parked, 1) @idling = Node.new(:idling, 2) @collection << @parked << @idling @parked.name = :parking @parked.value = 0 @collection.update(@parked) end def test_should_not_change_the_index assert_equal @parked, @collection.at(0) end def test_should_not_duplicate_in_the_collection assert_equal 2, @collection.length end def test_should_add_each_indexed_key assert_equal @parked, @collection[:parking] assert_equal @parked, @collection[0, :value] end def test_should_remove_each_old_indexed_key assert_nil @collection[:parked] assert_nil @collection[1, :value] end end class NodeCollectionWithStringIndexTest < Test::Unit::TestCase def setup machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(machine, :index => [:name, :value]) @parked = Node.new(:parked, 1) @collection << @parked end def test_should_index_by_name assert_equal @parked, @collection[:parked] end def test_should_index_by_string_name assert_equal @parked, @collection['parked'] end end class NodeCollectionWithSymbolIndexTest < Test::Unit::TestCase def setup machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(machine, :index => [:name, :value]) @parked = Node.new('parked', 1) @collection << @parked end def test_should_index_by_name assert_equal @parked, @collection['parked'] end def test_should_index_by_symbol_name assert_equal @parked, @collection[:parked] end end class NodeCollectionWithNumericIndexTest < Test::Unit::TestCase def setup machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(machine, :index => [:name, :value]) @parked = Node.new(10, 1) @collection << @parked end def test_should_index_by_name assert_equal @parked, @collection[10] end def test_should_index_by_string_name assert_equal @parked, @collection['10'] end def test_should_index_by_symbol_name assert_equal @parked, @collection[:'10'] end end class NodeCollectionWithPredefinedContextsTest < Test::Unit::TestCase def setup machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(machine) @contexts_run = contexts_run = [] @collection.context([:parked]) { contexts_run << :parked } @collection.context([:parked]) { contexts_run << :second_parked } end def test_should_run_contexts_in_the_order_defined @collection << Node.new(:parked) assert_equal [:parked, :second_parked], @contexts_run end def test_should_not_run_contexts_if_not_matched @collection << Node.new(:idling) assert_equal [], @contexts_run end end class NodeCollectionWithPostdefinedContextsTest < Test::Unit::TestCase def setup machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(machine) @collection << Node.new(:parked) end def test_should_run_context_if_matched contexts_run = [] @collection.context([:parked]) { contexts_run << :parked } assert_equal [:parked], contexts_run end def test_should_not_run_contexts_if_not_matched contexts_run = [] @collection.context([:idling]) { contexts_run << :idling } assert_equal [], contexts_run end end class NodeCollectionWithMatcherContextsTest < Test::Unit::TestCase def setup machine = StateMachine::Machine.new(Class.new) @collection = StateMachine::NodeCollection.new(machine) @collection << Node.new(:parked) end def test_should_always_run_all_matcher_context contexts_run = [] @collection.context([StateMachine::AllMatcher.instance]) { contexts_run << :all } assert_equal [:all], contexts_run end def test_should_only_run_blacklist_matcher_if_not_matched contexts_run = [] @collection.context([StateMachine::BlacklistMatcher.new([:parked])]) { contexts_run << :blacklist } assert_equal [], contexts_run @collection << Node.new(:idling) assert_equal [:blacklist], contexts_run end end state-machine-1.2.0/test/unit/event_test.rb0000644000175000017500000010100012305405267020251 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class EventByDefaultTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @object = @klass.new end def test_should_have_a_machine assert_equal @machine, @event.machine end def test_should_have_a_name assert_equal :ignite, @event.name end def test_should_have_a_qualified_name assert_equal :ignite, @event.qualified_name end def test_should_have_a_human_name assert_equal 'ignite', @event.human_name end def test_should_not_have_any_branches assert @event.branches.empty? end def test_should_have_no_known_states assert @event.known_states.empty? end def test_should_not_be_able_to_fire assert !@event.can_fire?(@object) end def test_should_not_have_a_transition assert_nil @event.transition_for(@object) end def test_should_define_a_predicate assert @object.respond_to?(:can_ignite?) end def test_should_define_a_transition_accessor assert @object.respond_to?(:ignite_transition) end def test_should_define_an_action assert @object.respond_to?(:ignite) end def test_should_define_a_bang_action assert @object.respond_to?(:ignite!) end end class EventTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @event.transition :parked => :idling end def test_should_allow_changing_machine new_machine = StateMachine::Machine.new(Class.new) @event.machine = new_machine assert_equal new_machine, @event.machine end def test_should_allow_changing_human_name @event.human_name = 'Stop' assert_equal 'Stop', @event.human_name end def test_should_provide_matcher_helpers_during_initialization matchers = [] @event.instance_eval do matchers = [all, any, same] end assert_equal [StateMachine::AllMatcher.instance, StateMachine::AllMatcher.instance, StateMachine::LoopbackMatcher.instance], matchers end def test_should_use_pretty_inspect assert_match "# :idling]>", @event.inspect end end class EventWithHumanNameTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.events << @event = StateMachine::Event.new(@machine, :ignite, :human_name => 'start') end def test_should_use_custom_human_name assert_equal 'start', @event.human_name end end class EventWithDynamicHumanNameTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.events << @event = StateMachine::Event.new(@machine, :ignite, :human_name => lambda {|event, object| ['start', object]}) end def test_should_use_custom_human_name human_name, klass = @event.human_name assert_equal 'start', human_name assert_equal @klass, klass end def test_should_allow_custom_class_to_be_passed_through human_name, klass = @event.human_name(1) assert_equal 'start', human_name assert_equal 1, klass end def test_should_not_cache_value assert_not_same @event.human_name, @event.human_name end end class EventWithConflictingHelpersBeforeDefinitionTest < Test::Unit::TestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @superclass = Class.new do def can_ignite? 0 end def ignite_transition 0 end def ignite 0 end def ignite! 0 end end @klass = Class.new(@superclass) @machine = StateMachine::Machine.new(@klass) @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @object = @klass.new end def test_should_not_redefine_predicate assert_equal 0, @object.can_ignite? end def test_should_not_redefine_transition_accessor assert_equal 0, @object.ignite_transition end def test_should_not_redefine_action assert_equal 0, @object.ignite end def test_should_not_redefine_bang_action assert_equal 0, @object.ignite! end def test_should_output_warning expected = %w(can_ignite? ignite_transition ignite ignite!).map do |method| "Instance method \"#{method}\" is already defined in #{@superclass.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n" end.join assert_equal expected, $stderr.string end def teardown $stderr = @original_stderr end end class EventWithConflictingHelpersAfterDefinitionTest < Test::Unit::TestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @klass = Class.new do def can_ignite? 0 end def ignite_transition 0 end def ignite 0 end def ignite! 0 end end @machine = StateMachine::Machine.new(@klass) @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @object = @klass.new end def test_should_not_redefine_predicate assert_equal 0, @object.can_ignite? end def test_should_not_redefine_transition_accessor assert_equal 0, @object.ignite_transition end def test_should_not_redefine_action assert_equal 0, @object.ignite end def test_should_not_redefine_bang_action assert_equal 0, @object.ignite! end def test_should_allow_super_chaining @klass.class_eval do def can_ignite? super end def ignite_transition super end def ignite super end def ignite! super end end assert_equal false, @object.can_ignite? assert_equal nil, @object.ignite_transition assert_equal false, @object.ignite assert_raise(StateMachine::InvalidTransition) { @object.ignite! } end def test_should_not_output_warning assert_equal '', $stderr.string end def teardown $stderr = @original_stderr end end class EventWithConflictingMachineTest < Test::Unit::TestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @klass = Class.new @state_machine = StateMachine::Machine.new(@klass, :state) @state_machine.state :parked, :idling @state_machine.events << @state_event = StateMachine::Event.new(@state_machine, :ignite) end def test_should_not_overwrite_first_event @status_machine = StateMachine::Machine.new(@klass, :status) @status_machine.state :first_gear, :second_gear @status_machine.events << @status_event = StateMachine::Event.new(@status_machine, :ignite) @object = @klass.new @object.state = 'parked' @object.status = 'first_gear' @state_event.transition(:parked => :idling) @status_event.transition(:parked => :first_gear) @object.ignite assert_equal 'idling', @object.state assert_equal 'first_gear', @object.status end def test_should_output_warning @status_machine = StateMachine::Machine.new(@klass, :status) @status_machine.events << @status_event = StateMachine::Event.new(@status_machine, :ignite) assert_equal "Event :ignite for :status is already defined in :state\n", $stderr.string end def test_should_not_output_warning_if_using_different_namespace @status_machine = StateMachine::Machine.new(@klass, :status, :namespace => 'alarm') @status_machine.events << @status_event = StateMachine::Event.new(@status_machine, :ignite) assert_equal '', $stderr.string end def teardown $stderr = @original_stderr end end class EventWithNamespaceTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm') @machine.events << @event = StateMachine::Event.new(@machine, :enable) @object = @klass.new end def test_should_have_a_name assert_equal :enable, @event.name end def test_should_have_a_qualified_name assert_equal :enable_alarm, @event.qualified_name end def test_should_namespace_predicate assert @object.respond_to?(:can_enable_alarm?) end def test_should_namespace_transition_accessor assert @object.respond_to?(:enable_alarm_transition) end def test_should_namespace_action assert @object.respond_to?(:enable_alarm) end def test_should_namespace_bang_action assert @object.respond_to?(:enable_alarm!) end end class EventContextTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.events << @event = StateMachine::Event.new(@machine, :ignite, :human_name => 'start') end def test_should_evaluate_within_the_event scope = nil @event.context { scope = self } assert_equal @event, scope end end class EventTransitionsTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.events << @event = StateMachine::Event.new(@machine, :ignite) end def test_should_not_raise_exception_if_implicit_option_specified assert_nothing_raised {@event.transition(:invalid => :valid)} end def test_should_not_allow_on_option exception = assert_raise(ArgumentError) {@event.transition(:on => :ignite)} assert_equal 'Invalid key(s): on', exception.message end def test_should_automatically_set_on_option branch = @event.transition(:to => :idling) assert_instance_of StateMachine::WhitelistMatcher, branch.event_requirement assert_equal [:ignite], branch.event_requirement.values end def test_should_not_allow_except_on_option exception = assert_raise(ArgumentError) {@event.transition(:except_on => :ignite)} assert_equal 'Invalid key(s): except_on', exception.message end def test_should_allow_transitioning_without_a_to_state assert_nothing_raised {@event.transition(:from => :parked)} end def test_should_allow_transitioning_without_a_from_state assert_nothing_raised {@event.transition(:to => :idling)} end def test_should_allow_except_from_option assert_nothing_raised {@event.transition(:except_from => :idling)} end def test_should_allow_except_to_option assert_nothing_raised {@event.transition(:except_to => :idling)} end def test_should_allow_transitioning_from_a_single_state assert @event.transition(:parked => :idling) end def test_should_allow_transitioning_from_multiple_states assert @event.transition([:parked, :idling] => :idling) end def test_should_allow_transitions_to_multiple_states assert @event.transition(:parked => [:parked, :idling]) end def test_should_have_transitions branch = @event.transition(:to => :idling) assert_equal [branch], @event.branches end end class EventAfterBeingCopiedTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @copied_event = @event.dup end def test_should_not_have_the_same_collection_of_branches assert_not_same @event.branches, @copied_event.branches end def test_should_not_have_the_same_collection_of_known_states assert_not_same @event.known_states, @copied_event.known_states end end class EventWithoutTransitionsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @object = @klass.new end def test_should_not_be_able_to_fire assert !@event.can_fire?(@object) end def test_should_not_have_a_transition assert_nil @event.transition_for(@object) end def test_should_not_fire assert !@event.fire(@object) end def test_should_not_change_the_current_state @event.fire(@object) assert_nil @object.state end end class EventWithTransitionsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @event.transition(:parked => :idling) @event.transition(:first_gear => :idling) end def test_should_include_all_transition_states_in_known_states assert_equal [:parked, :idling, :first_gear], @event.known_states end def test_should_include_new_transition_states_after_calling_known_states @event.known_states @event.transition(:stalled => :idling) assert_equal [:parked, :idling, :first_gear, :stalled], @event.known_states end def test_should_clear_known_states_on_reset @event.reset assert_equal [], @event.known_states end def test_should_use_pretty_inspect assert_match "# :idling, :first_gear => :idling]>", @event.inspect end end class EventWithoutMatchingTransitionsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @event.transition(:parked => :idling) @object = @klass.new @object.state = 'idling' end def test_should_not_be_able_to_fire assert !@event.can_fire?(@object) end def test_should_be_able_to_fire_with_custom_from_state assert @event.can_fire?(@object, :from => :parked) end def test_should_not_have_a_transition assert_nil @event.transition_for(@object) end def test_should_have_a_transition_with_custom_from_state assert_not_nil @event.transition_for(@object, :from => :parked) end def test_should_not_fire assert !@event.fire(@object) end def test_should_not_change_the_current_state @event.fire(@object) assert_equal 'idling', @object.state end end class EventWithMatchingDisabledTransitionsTest < Test::Unit::TestCase def setup StateMachine::Integrations.const_set('Custom', Module.new do include StateMachine::Integrations::Base def invalidate(object, attribute, message, values = []) (object.errors ||= []) << generate_message(message, values) end def reset(object) object.errors = [] end end) @klass = Class.new do attr_accessor :errors end @machine = StateMachine::Machine.new(@klass, :integration => :custom) @machine.state :parked, :idling @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @event.transition(:parked => :idling, :if => lambda {false}) @object = @klass.new @object.state = 'parked' end def test_should_not_be_able_to_fire assert !@event.can_fire?(@object) end def test_should_be_able_to_fire_with_disabled_guards assert @event.can_fire?(@object, :guard => false) end def test_should_not_have_a_transition assert_nil @event.transition_for(@object) end def test_should_have_a_transition_with_disabled_guards assert_not_nil @event.transition_for(@object, :guard => false) end def test_should_not_fire assert !@event.fire(@object) end def test_should_not_change_the_current_state @event.fire(@object) assert_equal 'parked', @object.state end def test_should_invalidate_the_state @event.fire(@object) assert_equal ['cannot transition via "ignite"'], @object.errors end def test_should_invalidate_with_human_event_name @event.human_name = 'start' @event.fire(@object) assert_equal ['cannot transition via "start"'], @object.errors end def test_should_invalid_with_human_state_name_if_specified klass = Class.new do attr_accessor :errors end machine = StateMachine::Machine.new(klass, :integration => :custom, :messages => {:invalid_transition => 'cannot transition via "%s" from "%s"'}) parked, idling = machine.state :parked, :idling parked.human_name = 'stopped' machine.events << event = StateMachine::Event.new(machine, :ignite) event.transition(:parked => :idling, :if => lambda {false}) object = @klass.new object.state = 'parked' event.fire(object) assert_equal ['cannot transition via "ignite" from "stopped"'], object.errors end def test_should_reset_existing_error @object.errors = ['invalid'] @event.fire(@object) assert_equal ['cannot transition via "ignite"'], @object.errors end def test_should_run_failure_callbacks callback_args = nil @machine.after_failure {|*args| callback_args = args} @event.fire(@object) object, transition = callback_args assert_equal @object, object assert_not_nil transition assert_equal @object, transition.object assert_equal @machine, transition.machine assert_equal :ignite, transition.event assert_equal :parked, transition.from_name assert_equal :parked, transition.to_name end def teardown StateMachine::Integrations.send(:remove_const, 'Custom') end end class EventWithMatchingEnabledTransitionsTest < Test::Unit::TestCase def setup StateMachine::Integrations.const_set('Custom', Module.new do include StateMachine::Integrations::Base def invalidate(object, attribute, message, values = []) (object.errors ||= []) << generate_message(message, values) end def reset(object) object.errors = [] end end) @klass = Class.new do attr_accessor :errors end @machine = StateMachine::Machine.new(@klass, :integration => :custom) @machine.state :parked, :idling @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @event.transition(:parked => :idling) @object = @klass.new @object.state = 'parked' end def test_should_be_able_to_fire assert @event.can_fire?(@object) end def test_should_have_a_transition transition = @event.transition_for(@object) assert_not_nil transition assert_equal 'parked', transition.from assert_equal 'idling', transition.to assert_equal :ignite, transition.event end def test_should_fire assert @event.fire(@object) end def test_should_change_the_current_state @event.fire(@object) assert_equal 'idling', @object.state end def test_should_reset_existing_error @object.errors = ['invalid'] @event.fire(@object) assert_equal [], @object.errors end def test_should_not_invalidate_the_state @event.fire(@object) assert_equal [], @object.errors end def test_should_not_be_able_to_fire_on_reset @event.reset assert !@event.can_fire?(@object) end def teardown StateMachine::Integrations.send(:remove_const, 'Custom') end end class EventWithTransitionWithoutToStateTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked @machine.events << @event = StateMachine::Event.new(@machine, :park) @event.transition(:from => :parked) @object = @klass.new @object.state = 'parked' end def test_should_be_able_to_fire assert @event.can_fire?(@object) end def test_should_have_a_transition transition = @event.transition_for(@object) assert_not_nil transition assert_equal 'parked', transition.from assert_equal 'parked', transition.to assert_equal :park, transition.event end def test_should_fire assert @event.fire(@object) end def test_should_not_change_the_current_state @event.fire(@object) assert_equal 'parked', @object.state end end class EventWithTransitionWithNilToStateTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state nil, :idling @machine.events << @event = StateMachine::Event.new(@machine, :park) @event.transition(:idling => nil) @object = @klass.new @object.state = 'idling' end def test_should_be_able_to_fire assert @event.can_fire?(@object) end def test_should_have_a_transition transition = @event.transition_for(@object) assert_not_nil transition assert_equal 'idling', transition.from assert_equal nil, transition.to assert_equal :park, transition.event end def test_should_fire assert @event.fire(@object) end def test_should_not_change_the_current_state @event.fire(@object) assert_equal nil, @object.state end end class EventWithTransitionWithLoopbackStateTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked @machine.events << @event = StateMachine::Event.new(@machine, :park) @event.transition(:from => :parked, :to => StateMachine::LoopbackMatcher.instance) @object = @klass.new @object.state = 'parked' end def test_should_be_able_to_fire assert @event.can_fire?(@object) end def test_should_have_a_transition transition = @event.transition_for(@object) assert_not_nil transition assert_equal 'parked', transition.from assert_equal 'parked', transition.to assert_equal :park, transition.event end def test_should_fire assert @event.fire(@object) end def test_should_not_change_the_current_state @event.fire(@object) assert_equal 'parked', @object.state end end class EventWithTransitionWithBlacklistedToStateTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @machine.state :parked, :idling, :first_gear, :second_gear @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @event.transition(:from => :parked, :to => StateMachine::BlacklistMatcher.new([:parked, :idling])) @object = @klass.new @object.state = 'parked' end def test_should_be_able_to_fire assert @event.can_fire?(@object) end def test_should_have_a_transition transition = @event.transition_for(@object) assert_not_nil transition assert_equal 'parked', transition.from assert_equal 'first_gear', transition.to assert_equal :ignite, transition.event end def test_should_allow_loopback_first_when_possible @event.transition(:from => :second_gear, :to => StateMachine::BlacklistMatcher.new([:parked, :idling])) @object.state = 'second_gear' transition = @event.transition_for(@object) assert_not_nil transition assert_equal 'second_gear', transition.from assert_equal 'second_gear', transition.to assert_equal :ignite, transition.event end def test_should_allow_specific_transition_selection_using_to transition = @event.transition_for(@object, :from => :parked, :to => :second_gear) assert_not_nil transition assert_equal 'parked', transition.from assert_equal 'second_gear', transition.to assert_equal :ignite, transition.event end def test_should_not_allow_transition_selection_if_not_matching transition = @event.transition_for(@object, :from => :parked, :to => :parked) assert_nil transition end def test_should_fire assert @event.fire(@object) end def test_should_change_the_current_state @event.fire(@object) assert_equal 'first_gear', @object.state end end class EventWithTransitionWithWhitelistedToStateTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @machine.state :parked, :idling, :first_gear, :second_gear @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @event.transition(:from => :parked, :to => StateMachine::WhitelistMatcher.new([:first_gear, :second_gear])) @object = @klass.new @object.state = 'parked' end def test_should_be_able_to_fire assert @event.can_fire?(@object) end def test_should_have_a_transition transition = @event.transition_for(@object) assert_not_nil transition assert_equal 'parked', transition.from assert_equal 'first_gear', transition.to assert_equal :ignite, transition.event end def test_should_allow_specific_transition_selection_using_to transition = @event.transition_for(@object, :from => :parked, :to => :second_gear) assert_not_nil transition assert_equal 'parked', transition.from assert_equal 'second_gear', transition.to assert_equal :ignite, transition.event end def test_should_not_allow_transition_selection_if_not_matching transition = @event.transition_for(@object, :from => :parked, :to => :parked) assert_nil transition end def test_should_fire assert @event.fire(@object) end def test_should_change_the_current_state @event.fire(@object) assert_equal 'first_gear', @object.state end end class EventWithMultipleTransitionsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @event.transition(:idling => :idling) @event.transition(:parked => :idling) @event.transition(:parked => :parked) @object = @klass.new @object.state = 'parked' end def test_should_be_able_to_fire assert @event.can_fire?(@object) end def test_should_have_a_transition transition = @event.transition_for(@object) assert_not_nil transition assert_equal 'parked', transition.from assert_equal 'idling', transition.to assert_equal :ignite, transition.event end def test_should_allow_specific_transition_selection_using_from transition = @event.transition_for(@object, :from => :idling) assert_not_nil transition assert_equal 'idling', transition.from assert_equal 'idling', transition.to assert_equal :ignite, transition.event end def test_should_allow_specific_transition_selection_using_to transition = @event.transition_for(@object, :from => :parked, :to => :parked) assert_not_nil transition assert_equal 'parked', transition.from assert_equal 'parked', transition.to assert_equal :ignite, transition.event end def test_should_not_allow_specific_transition_selection_using_on exception = assert_raise(ArgumentError) { @event.transition_for(@object, :on => :park) } assert_equal 'Invalid key(s): on', exception.message end def test_should_fire assert @event.fire(@object) end def test_should_change_the_current_state @event.fire(@object) assert_equal 'idling', @object.state end end class EventWithMachineActionTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :saved def save @saved = true end end @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @event.transition(:parked => :idling) @object = @klass.new @object.state = 'parked' end def test_should_run_action_on_fire @event.fire(@object) assert @object.saved end def test_should_not_run_action_if_configured_to_skip @event.fire(@object, false) assert !@object.saved end end class EventWithInvalidCurrentStateTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @event.transition(:parked => :idling) @object = @klass.new @object.state = 'invalid' end def test_should_raise_exception_when_checking_availability exception = assert_raise(ArgumentError) { @event.can_fire?(@object) } assert_equal '"invalid" is not a known state value', exception.message end def test_should_raise_exception_when_finding_transition exception = assert_raise(ArgumentError) { @event.transition_for(@object) } assert_equal '"invalid" is not a known state value', exception.message end def test_should_raise_exception_when_firing exception = assert_raise(ArgumentError) { @event.fire(@object) } assert_equal '"invalid" is not a known state value', exception.message end end class EventOnFailureTest < Test::Unit::TestCase def setup StateMachine::Integrations.const_set('Custom', Module.new do include StateMachine::Integrations::Base def invalidate(object, attribute, message, values = []) (object.errors ||= []) << generate_message(message, values) end def reset(object) object.errors = [] end end) @klass = Class.new do attr_accessor :errors end @machine = StateMachine::Machine.new(@klass, :integration => :custom) @machine.state :parked @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @object = @klass.new @object.state = 'parked' end def test_should_invalidate_the_state @event.fire(@object) assert_equal ['cannot transition via "ignite"'], @object.errors end def test_should_run_failure_callbacks callback_args = nil @machine.after_failure {|*args| callback_args = args} @event.fire(@object) object, transition = callback_args assert_equal @object, object assert_not_nil transition assert_equal @object, transition.object assert_equal @machine, transition.machine assert_equal :ignite, transition.event assert_equal :parked, transition.from_name assert_equal :parked, transition.to_name end def teardown StateMachine::Integrations.send(:remove_const, 'Custom') end end class EventWithMarshallingTest < Test::Unit::TestCase def setup @klass = Class.new do def save true end end self.class.const_set('Example', @klass) @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.events << @event = StateMachine::Event.new(@machine, :ignite) @event.transition(:parked => :idling) @object = @klass.new @object.state = 'parked' end def test_should_marshal_during_before_callbacks @machine.before_transition {|object, transition| Marshal.dump(object)} assert_nothing_raised { @event.fire(@object) } end def test_should_marshal_during_action @klass.class_eval do remove_method :save def save Marshal.dump(self) end end assert_nothing_raised { @event.fire(@object) } end def test_should_marshal_during_after_callbacks @machine.after_transition {|object, transition| Marshal.dump(object)} assert_nothing_raised { @event.fire(@object) } end def teardown self.class.send(:remove_const, 'Example') end end begin # Load library require 'graphviz' class EventDrawingTest < Test::Unit::TestCase def setup states = [:parked, :idling, :first_gear] @machine = StateMachine::Machine.new(Class.new, :initial => :parked) @machine.other_states(*states) @graph = StateMachine::Graph.new('test') states.each {|state| @graph.add_nodes(state.to_s)} @machine.events << @event = StateMachine::Event.new(@machine , :park) @event.transition :parked => :idling @event.transition :first_gear => :parked @event.transition :except_from => :parked, :to => :parked @event.draw(@graph) end def test_should_generate_edges_for_each_transition assert_equal 4, @graph.edge_count end def test_should_use_event_name_for_edge_label assert_equal 'park', @graph.get_edge_at_index(0)['label'].to_s.gsub('"', '') end end class EventDrawingWithHumanNameTest < Test::Unit::TestCase def setup states = [:parked, :idling] @machine = StateMachine::Machine.new(Class.new, :initial => :parked) @machine.other_states(*states) graph = StateMachine::Graph.new('test') states.each {|state| graph.add_nodes(state.to_s)} @machine.events << @event = StateMachine::Event.new(@machine , :park, :human_name => 'Park') @event.transition :parked => :idling @event.draw(graph, :human_name => true) @edge = graph.get_edge_at_index(0) end def test_should_use_event_human_name_for_edge_label assert_equal 'Park', @edge['label'].to_s.gsub('"', '') end end rescue LoadError $stderr.puts 'Skipping GraphViz StateMachine::Event tests. `gem install ruby-graphviz` >= v0.9.17 and try again.' end unless ENV['TRAVIS'] state-machine-1.2.0/test/unit/invalid_parallel_transition_test.rb0000644000175000017500000000076012305405267024717 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class InvalidParallelTransitionTest < Test::Unit::TestCase def setup @object = Object.new @events = [:ignite, :disable_alarm] @invalid_transition = StateMachine::InvalidParallelTransition.new(@object, @events) end def test_should_have_an_object assert_equal @object, @invalid_transition.object end def test_should_have_events assert_equal @events, @invalid_transition.events end end state-machine-1.2.0/test/unit/helper_module_test.rb0000644000175000017500000000076512305405267021774 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class HelperModuleTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @helper_module = StateMachine::HelperModule.new(@machine, :instance) end def test_should_not_have_a_name assert_equal '', @helper_module.name.to_s end def test_should_provide_human_readable_to_s assert_equal "#{@klass} :state instance helpers", @helper_module.to_s end end state-machine-1.2.0/test/unit/assertions_test.rb0000644000175000017500000000316112305405267021333 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class AssertionsBaseTest < Test::Unit::TestCase include StateMachine::Assertions def default_test end end class AssertValidKeysTest < AssertionsBaseTest def test_should_not_raise_exception_if_key_is_valid assert_nothing_raised { assert_valid_keys({:name => 'foo', :value => 'bar'}, :name, :value, :force) } end def test_should_raise_exception_if_key_is_invalid exception = assert_raise(ArgumentError) { assert_valid_keys({:name => 'foo', :value => 'bar', :invalid => true}, :name, :value, :force) } assert_equal 'Invalid key(s): invalid', exception.message end end class AssertExclusiveKeysTest < AssertionsBaseTest def test_should_not_raise_exception_if_no_keys_found assert_nothing_raised { assert_exclusive_keys({:on => :park}, :only, :except) } end def test_should_not_raise_exception_if_one_key_found assert_nothing_raised { assert_exclusive_keys({:only => :parked}, :only, :except) } assert_nothing_raised { assert_exclusive_keys({:except => :parked}, :only, :except) } end def test_should_raise_exception_if_two_keys_found exception = assert_raise(ArgumentError) { assert_exclusive_keys({:only => :parked, :except => :parked}, :only, :except) } assert_equal 'Conflicting keys: only, except', exception.message end def test_should_raise_exception_if_multiple_keys_found exception = assert_raise(ArgumentError) { assert_exclusive_keys({:only => :parked, :except => :parked, :on => :park}, :only, :except, :with) } assert_equal 'Conflicting keys: only, except', exception.message end end state-machine-1.2.0/test/unit/path_collection_test.rb0000644000175000017500000001712512305405267022315 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class PathCollectionByDefaultTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked @object = @klass.new @object.state = 'parked' @paths = StateMachine::PathCollection.new(@object, @machine) end def test_should_have_an_object assert_equal @object, @paths.object end def test_should_have_a_machine assert_equal @machine, @paths.machine end def test_should_have_a_from_name assert_equal :parked, @paths.from_name end def test_should_not_have_a_to_name assert_nil @paths.to_name end def test_should_have_no_from_states assert_equal [], @paths.from_states end def test_should_have_no_to_states assert_equal [], @paths.to_states end def test_should_have_no_events assert_equal [], @paths.events end def test_should_have_no_paths assert @paths.empty? end end class PathCollectionTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @object = @klass.new end def test_should_raise_exception_if_invalid_option_specified exception = assert_raise(ArgumentError) {StateMachine::PathCollection.new(@object, @machine, :invalid => true)} assert_equal 'Invalid key(s): invalid', exception.message end def test_should_raise_exception_if_invalid_from_state_specified exception = assert_raise(IndexError) {StateMachine::PathCollection.new(@object, @machine, :from => :invalid)} assert_equal ':invalid is an invalid name', exception.message end def test_should_raise_exception_if_invalid_to_state_specified exception = assert_raise(IndexError) {StateMachine::PathCollection.new(@object, @machine, :to => :invalid)} assert_equal ':invalid is an invalid name', exception.message end end class PathCollectionWithPathsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling, :first_gear @machine.event :ignite do transition :parked => :idling end @machine.event :shift_up do transition :idling => :first_gear end @object = @klass.new @object.state = 'parked' @paths = StateMachine::PathCollection.new(@object, @machine) end def test_should_enumerate_paths assert_equal [[ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling), StateMachine::Transition.new(@object, @machine, :shift_up, :idling, :first_gear) ]], @paths end def test_should_have_a_from_name assert_equal :parked, @paths.from_name end def test_should_not_have_a_to_name assert_nil @paths.to_name end def test_should_have_from_states assert_equal [:parked, :idling], @paths.from_states end def test_should_have_to_states assert_equal [:idling, :first_gear], @paths.to_states end def test_should_have_no_events assert_equal [:ignite, :shift_up], @paths.events end end class PathWithGuardedPathsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling, :first_gear @machine.event :ignite do transition :parked => :idling, :if => lambda {false} end @object = @klass.new @object.state = 'parked' end def test_should_not_enumerate_paths_if_guard_enabled assert_equal [], StateMachine::PathCollection.new(@object, @machine) end def test_should_enumerate_paths_if_guard_disabled paths = StateMachine::PathCollection.new(@object, @machine, :guard => false) assert_equal [[ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]], paths end end class PathCollectionWithDuplicateNodesTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :shift_up do transition :parked => :idling, :idling => :first_gear end @machine.event :park do transition :first_gear => :idling end @object = @klass.new @object.state = 'parked' @paths = StateMachine::PathCollection.new(@object, @machine) end def test_should_not_include_duplicates_in_from_states assert_equal [:parked, :idling, :first_gear], @paths.from_states end def test_should_not_include_duplicates_in_to_states assert_equal [:idling, :first_gear], @paths.to_states end def test_should_not_include_duplicates_in_events assert_equal [:shift_up, :park], @paths.events end end class PathCollectionWithFromStateTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling, :first_gear @machine.event :park do transition :idling => :parked end @object = @klass.new @object.state = 'parked' @paths = StateMachine::PathCollection.new(@object, @machine, :from => :idling) end def test_should_generate_paths_from_custom_from_state assert_equal [[ StateMachine::Transition.new(@object, @machine, :park, :idling, :parked) ]], @paths end def test_should_have_a_from_name assert_equal :idling, @paths.from_name end end class PathCollectionWithToStateTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite do transition :parked => :idling end @machine.event :shift_up do transition :parked => :idling, :idling => :first_gear end @machine.event :shift_down do transition :first_gear => :idling end @object = @klass.new @object.state = 'parked' @paths = StateMachine::PathCollection.new(@object, @machine, :to => :idling) end def test_should_stop_paths_once_target_state_reached assert_equal [ [StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)], [StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :idling)] ], @paths end end class PathCollectionWithDeepPathsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite do transition :parked => :idling end @machine.event :shift_up do transition :parked => :idling, :idling => :first_gear end @machine.event :shift_down do transition :first_gear => :idling end @object = @klass.new @object.state = 'parked' @paths = StateMachine::PathCollection.new(@object, @machine, :to => :idling, :deep => true) end def test_should_allow_target_to_be_reached_more_than_once_per_path assert_equal [ [ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ], [ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling), StateMachine::Transition.new(@object, @machine, :shift_up, :idling, :first_gear), StateMachine::Transition.new(@object, @machine, :shift_down, :first_gear, :idling) ], [ StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :idling) ], [ StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :idling), StateMachine::Transition.new(@object, @machine, :shift_up, :idling, :first_gear), StateMachine::Transition.new(@object, @machine, :shift_down, :first_gear, :idling) ] ], @paths end end state-machine-1.2.0/test/unit/graph_test.rb0000644000175000017500000000526012305405267020244 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') begin # Load library require 'graphviz' class GraphDefaultTest < Test::Unit::TestCase def setup @graph = StateMachine::Graph.new('test') end def test_should_have_a_default_font assert_equal 'Arial', @graph.font end def test_should_use_current_directory_for_filepath assert_equal './test.png', @graph.file_path end def test_should_have_a_default_file_format assert_equal 'png', @graph.file_format end def test_should_have_a_default_orientation assert_equal 'TB', @graph[:rankdir].source end end class GraphNodesTest < Test::Unit::TestCase def setup @graph = StateMachine::Graph.new('test') @node = @graph.add_nodes('parked', :shape => 'ellipse') end def test_should_return_generated_node assert_not_nil @node end def test_should_use_specified_name assert_equal @node, @graph.get_node('parked') end def test_should_use_specified_options assert_equal 'ellipse', @node['shape'].to_s.gsub('"', '') end def test_should_set_default_font assert_equal 'Arial', @node['fontname'].to_s.gsub('"', '') end end class GraphEdgesTest < Test::Unit::TestCase def setup @graph = StateMachine::Graph.new('test') @graph.add_nodes('parked', :shape => 'ellipse') @graph.add_nodes('idling', :shape => 'ellipse') @edge = @graph.add_edges('parked', 'idling', :label => 'ignite') end def test_should_return_generated_edge assert_not_nil @edge end def test_should_use_specified_nodes assert_equal 'parked', @edge.node_one(false) assert_equal 'idling', @edge.node_two(false) end def test_should_use_specified_options assert_equal 'ignite', @edge['label'].to_s.gsub('"', '') end def test_should_set_default_font assert_equal 'Arial', @edge['fontname'].to_s.gsub('"', '') end end class GraphOutputTest < Test::Unit::TestCase def setup @graph_name = "test_#{rand(1000000)}" @graph = StateMachine::Graph.new(@graph_name) @graph.add_nodes('parked', :shape => 'ellipse') @graph.add_nodes('idling', :shape => 'ellipse') @graph.add_edges('parked', 'idling', :label => 'ignite') @graph.output end def test_should_save_file assert File.exist?("./#{@graph_name}.png") end def teardown FileUtils.rm Dir["./#{@graph_name}.png"] end end rescue LoadError $stderr.puts 'Skipping GraphViz StateMachine::Graph tests. `gem install ruby-graphviz` >= v0.9.17 and try again.' end unless ENV['TRAVIS'] state-machine-1.2.0/test/unit/state_collection_test.rb0000644000175000017500000002343312305405267022500 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class StateCollectionByDefaultTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @states = StateMachine::StateCollection.new(@machine) end def test_should_not_have_any_nodes assert_equal 0, @states.length end def test_should_have_a_machine assert_equal @machine, @states.machine end def test_should_be_empty_by_priority assert_equal [], @states.by_priority end end class StateCollectionTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @states = StateMachine::StateCollection.new(@machine) @states << @nil = StateMachine::State.new(@machine, nil) @states << @parked = StateMachine::State.new(@machine, :parked) @states << @idling = StateMachine::State.new(@machine, :idling) @machine.states.concat(@states) @object = @klass.new end def test_should_index_by_name assert_equal @parked, @states[:parked, :name] end def test_should_index_by_name_by_default assert_equal @parked, @states[:parked] end def test_should_index_by_string_name assert_equal @parked, @states['parked'] end def test_should_index_by_qualified_name assert_equal @parked, @states[:parked, :qualified_name] end def test_should_index_by_string_qualified_name assert_equal @parked, @states['parked', :qualified_name] end def test_should_index_by_value assert_equal @parked, @states['parked', :value] end def test_should_not_match_if_value_does_not_match assert !@states.matches?(@object, :parked) assert !@states.matches?(@object, :idling) end def test_should_match_if_value_matches assert @states.matches?(@object, nil) end def test_raise_exception_if_matching_invalid_state assert_raise(IndexError) { @states.matches?(@object, :invalid) } end def test_should_find_state_for_object_if_value_is_known @object.state = 'parked' assert_equal @parked, @states.match(@object) end def test_should_find_bang_state_for_object_if_value_is_known @object.state = 'parked' assert_equal @parked, @states.match!(@object) end def test_should_not_find_state_for_object_with_unknown_value @object.state = 'invalid' assert_nil @states.match(@object) end def test_should_raise_exception_if_finding_bang_state_for_object_with_unknown_value @object.state = 'invalid' exception = assert_raise(ArgumentError) { @states.match!(@object) } assert_equal '"invalid" is not a known state value', exception.message end end class StateCollectionStringTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @states = StateMachine::StateCollection.new(@machine) @states << @nil = StateMachine::State.new(@machine, nil) @states << @parked = StateMachine::State.new(@machine, 'parked') @machine.states.concat(@states) @object = @klass.new end def test_should_index_by_name assert_equal @parked, @states['parked', :name] end def test_should_index_by_name_by_default assert_equal @parked, @states['parked'] end def test_should_index_by_symbol_name assert_equal @parked, @states[:parked] end def test_should_index_by_qualified_name assert_equal @parked, @states['parked', :qualified_name] end def test_should_index_by_symbol_qualified_name assert_equal @parked, @states[:parked, :qualified_name] end end class StateCollectionWithNamespaceTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :namespace => 'vehicle') @states = StateMachine::StateCollection.new(@machine) @states << @state = StateMachine::State.new(@machine, :parked) @machine.states.concat(@states) end def test_should_index_by_name assert_equal @state, @states[:parked, :name] end def test_should_index_by_qualified_name assert_equal @state, @states[:vehicle_parked, :qualified_name] end end class StateCollectionWithCustomStateValuesTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @states = StateMachine::StateCollection.new(@machine) @states << @state = StateMachine::State.new(@machine, :parked, :value => 1) @machine.states.concat(@states) @object = @klass.new @object.state = 1 end def test_should_match_if_value_matches assert @states.matches?(@object, :parked) end def test_should_not_match_if_value_does_not_match @object.state = 2 assert !@states.matches?(@object, :parked) end def test_should_find_state_for_object_if_value_is_known assert_equal @state, @states.match(@object) end end class StateCollectionWithStateMatchersTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @states = StateMachine::StateCollection.new(@machine) @states << @state = StateMachine::State.new(@machine, :parked, :if => lambda {|value| !value.nil?}) @machine.states.concat(@states) @object = @klass.new @object.state = 1 end def test_should_match_if_value_matches assert @states.matches?(@object, :parked) end def test_should_not_match_if_value_does_not_match @object.state = nil assert !@states.matches?(@object, :parked) end def test_should_find_state_for_object_if_value_is_known assert_equal @state, @states.match(@object) end end class StateCollectionWithInitialStateTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @states = StateMachine::StateCollection.new(@machine) @states << @parked = StateMachine::State.new(@machine, :parked) @states << @idling = StateMachine::State.new(@machine, :idling) @machine.states.concat(@states) @parked.initial = true end def test_should_order_state_before_transition_states @machine.event :ignite do transition :to => :idling end assert_equal [@parked, @idling], @states.by_priority end def test_should_order_state_before_states_with_behaviors @idling.context do def speed 0 end end assert_equal [@parked, @idling], @states.by_priority end def test_should_order_state_before_other_states assert_equal [@parked, @idling], @states.by_priority end def test_should_order_state_before_callback_states @machine.before_transition :from => :idling, :do => lambda {} assert_equal [@parked, @idling], @states.by_priority end end class StateCollectionWithStateBehaviorsTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @states = StateMachine::StateCollection.new(@machine) @states << @parked = StateMachine::State.new(@machine, :parked) @states << @idling = StateMachine::State.new(@machine, :idling) @machine.states.concat(@states) @idling.context do def speed 0 end end end def test_should_order_states_after_initial_state @parked.initial = true assert_equal [@parked, @idling], @states.by_priority end def test_should_order_states_after_transition_states @machine.event :ignite do transition :from => :parked end assert_equal [@parked, @idling], @states.by_priority end def test_should_order_states_before_other_states assert_equal [@idling, @parked], @states.by_priority end def test_should_order_state_before_callback_states @machine.before_transition :from => :parked, :do => lambda {} assert_equal [@idling, @parked], @states.by_priority end end class StateCollectionWithEventTransitionsTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @states = StateMachine::StateCollection.new(@machine) @states << @parked = StateMachine::State.new(@machine, :parked) @states << @idling = StateMachine::State.new(@machine, :idling) @machine.states.concat(@states) @machine.event :ignite do transition :to => :idling end end def test_should_order_states_after_initial_state @parked.initial = true assert_equal [@parked, @idling], @states.by_priority end def test_should_order_states_before_states_with_behaviors @parked.context do def speed 0 end end assert_equal [@idling, @parked], @states.by_priority end def test_should_order_states_before_other_states assert_equal [@idling, @parked], @states.by_priority end def test_should_order_state_before_callback_states @machine.before_transition :from => :parked, :do => lambda {} assert_equal [@idling, @parked], @states.by_priority end end class StateCollectionWithTransitionCallbacksTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @states = StateMachine::StateCollection.new(@machine) @states << @parked = StateMachine::State.new(@machine, :parked) @states << @idling = StateMachine::State.new(@machine, :idling) @machine.states.concat(@states) @machine.before_transition :to => :idling, :do => lambda {} end def test_should_order_states_after_initial_state @parked.initial = true assert_equal [@parked, @idling], @states.by_priority end def test_should_order_states_after_transition_states @machine.event :ignite do transition :from => :parked end assert_equal [@parked, @idling], @states.by_priority end def test_should_order_states_after_states_with_behaviors @parked.context do def speed 0 end end assert_equal [@parked, @idling], @states.by_priority end def test_should_order_states_after_other_states assert_equal [@parked, @idling], @states.by_priority end end state-machine-1.2.0/test/unit/transition_test.rb0000644000175000017500000013577412305405267021353 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class TransitionTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_have_an_object assert_equal @object, @transition.object end def test_should_have_a_machine assert_equal @machine, @transition.machine end def test_should_have_an_event assert_equal :ignite, @transition.event end def test_should_have_a_qualified_event assert_equal :ignite, @transition.qualified_event end def test_should_have_a_human_event assert_equal 'ignite', @transition.human_event end def test_should_have_a_from_value assert_equal 'parked', @transition.from end def test_should_have_a_from_name assert_equal :parked, @transition.from_name end def test_should_have_a_qualified_from_name assert_equal :parked, @transition.qualified_from_name end def test_should_have_a_human_from_name assert_equal 'parked', @transition.human_from_name end def test_should_have_a_to_value assert_equal 'idling', @transition.to end def test_should_have_a_to_name assert_equal :idling, @transition.to_name end def test_should_have_a_qualified_to_name assert_equal :idling, @transition.qualified_to_name end def test_should_have_a_human_to_name assert_equal 'idling', @transition.human_to_name end def test_should_have_an_attribute assert_equal :state, @transition.attribute end def test_should_not_have_an_action assert_nil @transition.action end def test_should_not_be_transient assert_equal false, @transition.transient? end def test_should_generate_attributes expected = {:object => @object, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'} assert_equal expected, @transition.attributes end def test_should_have_empty_args assert_equal [], @transition.args end def test_should_not_have_a_result assert_nil @transition.result end def test_should_use_pretty_inspect assert_equal '#', @transition.inspect end end class TransitionWithInvalidNodesTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' end def test_should_raise_exception_without_event assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, nil, :parked, :idling) } end def test_should_raise_exception_with_invalid_event assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :invalid, :parked, :idling) } end def test_should_raise_exception_with_invalid_from_state assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :ignite, :invalid, :idling) } end def test_should_raise_exception_with_invalid_to_state assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :ignite, :parked, :invalid) } end end class TransitionWithDynamicToValueTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked @machine.state :idling, :value => lambda {1} @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_evaluate_to_value assert_equal 1, @transition.to end end class TransitionLoopbackTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked @machine.event :park @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :park, :parked, :parked) end def test_should_be_loopback assert @transition.loopback? end end class TransitionWithDifferentStatesTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_not_be_loopback assert !@transition.loopback? end end class TransitionWithNamespaceTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm') @machine.state :off, :active @machine.event :activate @object = @klass.new @object.state = 'off' @transition = StateMachine::Transition.new(@object, @machine, :activate, :off, :active) end def test_should_have_an_event assert_equal :activate, @transition.event end def test_should_have_a_qualified_event assert_equal :activate_alarm, @transition.qualified_event end def test_should_have_a_from_name assert_equal :off, @transition.from_name end def test_should_have_a_qualified_from_name assert_equal :alarm_off, @transition.qualified_from_name end def test_should_have_a_human_from_name assert_equal 'off', @transition.human_from_name end def test_should_have_a_to_name assert_equal :active, @transition.to_name end def test_should_have_a_qualified_to_name assert_equal :alarm_active, @transition.qualified_to_name end def test_should_have_a_human_to_name assert_equal 'active', @transition.human_to_name end end class TransitionWithCustomMachineAttributeTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :state, :attribute => :state_id) @machine.state :off, :value => 1 @machine.state :active, :value => 2 @machine.event :activate @object = @klass.new @object.state_id = 1 @transition = StateMachine::Transition.new(@object, @machine, :activate, :off, :active) end def test_should_persist @transition.persist assert_equal 2, @object.state_id end def test_should_rollback @object.state_id = 2 @transition.rollback assert_equal 1, @object.state_id end end class TransitionWithoutReadingStateTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'idling' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling, false) end def test_should_not_read_from_value_from_object assert_equal 'parked', @transition.from end def test_should_have_to_value assert_equal 'idling', @transition.to end end class TransitionWithActionTest < Test::Unit::TestCase def setup @klass = Class.new do def save end end @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_have_an_action assert_equal :save, @transition.action end def test_should_not_have_a_result assert_nil @transition.result end end class TransitionAfterBeingPersistedTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) @transition.persist end def test_should_update_state_value assert_equal 'idling', @object.state end def test_should_not_change_from_state assert_equal 'parked', @transition.from end def test_should_not_change_to_state assert_equal 'idling', @transition.to end def test_should_not_be_able_to_persist_twice @object.state = 'parked' @transition.persist assert_equal 'parked', @object.state end def test_should_be_able_to_persist_again_after_resetting @object.state = 'parked' @transition.reset @transition.persist assert_equal 'idling', @object.state end def test_should_revert_to_from_state_on_rollback @transition.rollback assert_equal 'parked', @object.state end end class TransitionAfterBeingRolledBackTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) @object.state = 'idling' @transition.rollback end def test_should_update_state_value_to_from_state assert_equal 'parked', @object.state end def test_should_not_change_from_state assert_equal 'parked', @transition.from end def test_should_not_change_to_state assert_equal 'idling', @transition.to end def test_should_still_be_able_to_persist @transition.persist assert_equal 'idling', @object.state end end class TransitionWithoutCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_succeed assert_equal true, @transition.run_callbacks end def test_should_succeed_if_after_callbacks_skipped assert_equal true, @transition.run_callbacks(:after => false) end def test_should_call_block_if_provided @transition.run_callbacks { @ran_block = true; {} } assert @ran_block end def test_should_track_block_result @transition.run_callbacks {{:result => 1}} assert_equal 1, @transition.result end end class TransitionWithBeforeCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_run_before_callbacks @machine.before_transition {@run = true} result = @transition.run_callbacks assert_equal true, result assert_equal true, @run end def test_should_only_run_those_that_match_transition_context @count = 0 callback = lambda {@count += 1} @machine.before_transition :from => :parked, :to => :idling, :on => :park, :do => callback @machine.before_transition :from => :parked, :to => :parked, :on => :park, :do => callback @machine.before_transition :from => :parked, :to => :idling, :on => :ignite, :do => callback @machine.before_transition :from => :idling, :to => :idling, :on => :park, :do => callback @transition.run_callbacks assert_equal 1, @count end def test_should_pass_transition_as_argument @machine.before_transition {|*args| @args = args} @transition.run_callbacks assert_equal [@object, @transition], @args end def test_should_catch_halts @machine.before_transition {throw :halt} result = nil assert_nothing_thrown { result = @transition.run_callbacks } assert_equal false, result end def test_should_not_catch_exceptions @machine.before_transition {raise ArgumentError} assert_raise(ArgumentError) { @transition.run_callbacks } end def test_should_not_be_able_to_run_twice @count = 0 @machine.before_transition {@count += 1} @transition.run_callbacks @transition.run_callbacks assert_equal 1, @count end def test_should_be_able_to_run_again_after_halt @count = 0 @machine.before_transition {@count += 1; throw :halt} @transition.run_callbacks @transition.run_callbacks assert_equal 2, @count end def test_should_be_able_to_run_again_after_resetting @count = 0 @machine.before_transition {@count += 1} @transition.run_callbacks @transition.reset @transition.run_callbacks assert_equal 2, @count end def test_should_succeed_if_block_result_is_false @machine.before_transition {@run = true} assert_equal true, @transition.run_callbacks {{:result => false}} assert @run end def test_should_succeed_if_block_result_is_true @machine.before_transition {@run = true} assert_equal true, @transition.run_callbacks {{:result => true}} assert @run end def test_should_succeed_if_block_success_is_false @machine.before_transition {@run = true} assert_equal true, @transition.run_callbacks {{:success => false}} assert @run end def test_should_succeed_if_block_success_is_true @machine.before_transition {@run = true} assert_equal true, @transition.run_callbacks {{:success => true}} assert @run end end class TransitionWithMultipleBeforeCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_run_in_the_order_they_were_defined @callbacks = [] @machine.before_transition {@callbacks << 1} @machine.before_transition {@callbacks << 2} @transition.run_callbacks assert_equal [1, 2], @callbacks end def test_should_not_run_further_callbacks_if_halted @callbacks = [] @machine.before_transition {@callbacks << 1; throw :halt} @machine.before_transition {@callbacks << 2} assert_equal false, @transition.run_callbacks assert_equal [1], @callbacks end def test_should_fail_if_any_callback_halted @machine.before_transition {true} @machine.before_transition {throw :halt} assert_equal false, @transition.run_callbacks end end class TransitionWithAfterCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_run_after_callbacks @machine.after_transition {|object| @run = true} result = @transition.run_callbacks assert_equal true, result assert_equal true, @run end def test_should_only_run_those_that_match_transition_context @count = 0 callback = lambda {@count += 1} @machine.after_transition :from => :parked, :to => :idling, :on => :park, :do => callback @machine.after_transition :from => :parked, :to => :parked, :on => :park, :do => callback @machine.after_transition :from => :parked, :to => :idling, :on => :ignite, :do => callback @machine.after_transition :from => :idling, :to => :idling, :on => :park, :do => callback @transition.run_callbacks assert_equal 1, @count end def test_should_not_run_if_not_successful @run = false @machine.after_transition {|object| @run = true} @transition.run_callbacks {{:success => false}} assert !@run end def test_should_run_if_successful @machine.after_transition {|object| @run = true} @transition.run_callbacks {{:success => true}} assert @run end def test_should_pass_transition_as_argument @machine.after_transition {|*args| @args = args} @transition.run_callbacks assert_equal [@object, @transition], @args end def test_should_catch_halts @machine.after_transition {throw :halt} result = nil assert_nothing_thrown { result = @transition.run_callbacks } assert_equal true, result end def test_should_not_catch_exceptions @machine.after_transition {raise ArgumentError} assert_raise(ArgumentError) { @transition.run_callbacks } end def test_should_not_be_able_to_run_twice @count = 0 @machine.after_transition {@count += 1} @transition.run_callbacks @transition.run_callbacks assert_equal 1, @count end def test_should_not_be_able_to_run_twice_if_halted @count = 0 @machine.after_transition {@count += 1; throw :halt} @transition.run_callbacks @transition.run_callbacks assert_equal 1, @count end def test_should_be_able_to_run_again_after_resetting @count = 0 @machine.after_transition {@count += 1} @transition.run_callbacks @transition.reset @transition.run_callbacks assert_equal 2, @count end end class TransitionWithMultipleAfterCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_run_in_the_order_they_were_defined @callbacks = [] @machine.after_transition {@callbacks << 1} @machine.after_transition {@callbacks << 2} @transition.run_callbacks assert_equal [1, 2], @callbacks end def test_should_not_run_further_callbacks_if_halted @callbacks = [] @machine.after_transition {@callbacks << 1; throw :halt} @machine.after_transition {@callbacks << 2} assert_equal true, @transition.run_callbacks assert_equal [1], @callbacks end def test_should_fail_if_any_callback_halted @machine.after_transition {true} @machine.after_transition {throw :halt} assert_equal true, @transition.run_callbacks end end class TransitionWithAroundCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_run_around_callbacks @machine.around_transition {|object, transition, block| @run_before = true; block.call; @run_after = true} result = @transition.run_callbacks assert_equal true, result assert_equal true, @run_before assert_equal true, @run_after end def test_should_only_run_those_that_match_transition_context @count = 0 callback = lambda {|object, transition, block| @count += 1; block.call} @machine.around_transition :from => :parked, :to => :idling, :on => :park, :do => callback @machine.around_transition :from => :parked, :to => :parked, :on => :park, :do => callback @machine.around_transition :from => :parked, :to => :idling, :on => :ignite, :do => callback @machine.around_transition :from => :idling, :to => :idling, :on => :park, :do => callback @transition.run_callbacks assert_equal 1, @count end def test_should_pass_transition_as_argument @machine.around_transition {|*args| block = args.pop; @args = args; block.call} @transition.run_callbacks assert_equal [@object, @transition], @args end def test_should_run_block_between_callback @callbacks = [] @machine.around_transition {|block| @callbacks << :before; block.call; @callbacks << :after} @transition.run_callbacks { @callbacks << :within; {:success => true} } assert_equal [:before, :within, :after], @callbacks end def test_should_have_access_to_result_after_yield @machine.around_transition {|block| @before_result = @transition.result; block.call; @after_result = @transition.result} @transition.run_callbacks {{:result => 1, :success => true}} assert_nil @before_result assert_equal 1, @after_result end def test_should_catch_before_yield_halts @machine.around_transition {throw :halt} result = nil assert_nothing_thrown { result = @transition.run_callbacks } assert_equal false, result end def test_should_catch_after_yield_halts @machine.around_transition {|block| block.call; throw :halt} result = nil assert_nothing_thrown { result = @transition.run_callbacks } assert_equal true, result end def test_should_not_catch_before_yield @machine.around_transition {raise ArgumentError} assert_raise(ArgumentError) { @transition.run_callbacks } end def test_should_not_catch_after_yield @machine.around_transition {|block| block.call; raise ArgumentError} assert_raise(ArgumentError) { @transition.run_callbacks } end def test_should_fail_if_not_yielded @machine.around_transition {} result = nil assert_nothing_thrown { result = @transition.run_callbacks } assert_equal false, result end def test_should_not_be_able_to_run_twice @before_count = 0 @after_count = 0 @machine.around_transition {|block| @before_count += 1; block.call; @after_count += 1} @transition.run_callbacks @transition.run_callbacks assert_equal 1, @before_count assert_equal 1, @after_count end def test_should_be_able_to_run_again_after_resetting @before_count = 0 @after_count = 0 @machine.around_transition {|block| @before_count += 1; block.call; @after_count += 1} @transition.run_callbacks @transition.reset @transition.run_callbacks assert_equal 2, @before_count assert_equal 2, @after_count end def test_should_succeed_if_block_result_is_false @machine.around_transition {|block| @before_run = true; block.call; @after_run = true} assert_equal true, @transition.run_callbacks {{:success => true, :result => false}} assert @before_run assert @after_run end def test_should_succeed_if_block_result_is_true @machine.around_transition {|block| @before_run = true; block.call; @after_run = true} assert_equal true, @transition.run_callbacks {{:success => true, :result => true}} assert @before_run assert @after_run end def test_should_only_run_before_if_block_success_is_false @after_run = false @machine.around_transition {|block| @before_run = true; block.call; @after_run = true} assert_equal true, @transition.run_callbacks {{:success => false}} assert @before_run assert !@after_run end def test_should_succeed_if_block_success_is_false @machine.around_transition {|block| @before_run = true; block.call; @after_run = true} assert_equal true, @transition.run_callbacks {{:success => true}} assert @before_run assert @after_run end end class TransitionWithMultipleAroundCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_before_yield_in_the_order_they_were_defined @callbacks = [] @machine.around_transition {|block| @callbacks << 1; block.call} @machine.around_transition {|block| @callbacks << 2; block.call} @transition.run_callbacks assert_equal [1, 2], @callbacks end def test_should_before_yield_multiple_methods_in_the_order_they_were_defined @callbacks = [] @machine.around_transition(lambda {|block| @callbacks << 1; block.call}, lambda {|block| @callbacks << 2; block.call}) @machine.around_transition(lambda {|block| @callbacks << 3; block.call}, lambda {|block| @callbacks << 4; block.call}) @transition.run_callbacks assert_equal [1, 2, 3, 4], @callbacks end def test_should_after_yield_in_the_reverse_order_they_were_defined @callbacks = [] @machine.around_transition {|block| block.call; @callbacks << 1} @machine.around_transition {|block| block.call; @callbacks << 2} @transition.run_callbacks assert_equal [2, 1], @callbacks end def test_should_after_yield_multiple_methods_in_the_reverse_order_they_were_defined @callbacks = [] @machine.around_transition(lambda {|block| block.call; @callbacks << 1}) {|block| block.call; @callbacks << 2} @machine.around_transition(lambda {|block| block.call; @callbacks << 3}) {|block| block.call; @callbacks << 4} @transition.run_callbacks assert_equal [4, 3, 2, 1], @callbacks end def test_should_run_block_between_callback @callbacks = [] @machine.around_transition {|block| @callbacks << :before_1; block.call; @callbacks << :after_1} @machine.around_transition {|block| @callbacks << :before_2; block.call; @callbacks << :after_2} @transition.run_callbacks { @callbacks << :within; {:success => true} } assert_equal [:before_1, :before_2, :within, :after_2, :after_1], @callbacks end def test_should_have_access_to_result_after_yield @machine.around_transition {|block| @before_result_1 = @transition.result; block.call; @after_result_1 = @transition.result} @machine.around_transition {|block| @before_result_2 = @transition.result; block.call; @after_result_2 = @transition.result} @transition.run_callbacks {{:result => 1, :success => true}} assert_nil @before_result_1 assert_nil @before_result_2 assert_equal 1, @after_result_1 assert_equal 1, @after_result_2 end def test_should_fail_if_any_before_yield_halted @machine.around_transition {|block| block.call} @machine.around_transition {throw :halt} assert_equal false, @transition.run_callbacks end def test_should_not_continue_around_callbacks_if_before_yield_halted @callbacks = [] @machine.around_transition {@callbacks << 1; throw :halt} @machine.around_transition {|block| @callbacks << 2; block.call; @callbacks << 3} assert_equal false, @transition.run_callbacks assert_equal [1], @callbacks end def test_should_not_continue_around_callbacks_if_later_before_yield_halted @callbacks = [] @machine.around_transition {|block| block.call; @callbacks << 1} @machine.around_transition {throw :halt} @transition.run_callbacks assert_equal [], @callbacks end def test_should_not_run_further_callbacks_if_after_yield_halted @callbacks = [] @machine.around_transition {|block| block.call; @callbacks << 1} @machine.around_transition {|block| block.call; throw :halt} assert_equal true, @transition.run_callbacks assert_equal [], @callbacks end def test_should_fail_if_any_fail_to_yield @callbacks = [] @machine.around_transition {@callbacks << 1} @machine.around_transition {|block| @callbacks << 2; block.call; @callbacks << 3} assert_equal false, @transition.run_callbacks assert_equal [1], @callbacks end end class TransitionWithFailureCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_only_run_those_that_match_transition_context @count = 0 callback = lambda {@count += 1} @machine.after_failure :do => callback @machine.after_failure :on => :park, :do => callback @machine.after_failure :on => :ignite, :do => callback @transition.run_callbacks {{:success => false}} assert_equal 2, @count end def test_should_run_if_not_successful @machine.after_failure {|object| @run = true} @transition.run_callbacks {{:success => false}} assert @run end def test_should_not_run_if_successful @run = false @machine.after_failure {|object| @run = true} @transition.run_callbacks {{:success => true}} assert !@run end def test_should_pass_transition_as_argument @machine.after_failure {|*args| @args = args} @transition.run_callbacks {{:success => false}} assert_equal [@object, @transition], @args end def test_should_catch_halts @machine.after_failure {throw :halt} result = nil assert_nothing_thrown { result = @transition.run_callbacks {{:success => false}} } assert_equal true, result end def test_should_not_catch_exceptions @machine.after_failure {raise ArgumentError} assert_raise(ArgumentError) { @transition.run_callbacks {{:success => false}} } end def test_should_not_be_able_to_run_twice @count = 0 @machine.after_failure {@count += 1} @transition.run_callbacks {{:success => false}} @transition.run_callbacks {{:success => false}} assert_equal 1, @count end def test_should_not_be_able_to_run_twice_if_halted @count = 0 @machine.after_failure {@count += 1; throw :halt} @transition.run_callbacks {{:success => false}} @transition.run_callbacks {{:success => false}} assert_equal 1, @count end def test_should_be_able_to_run_again_after_resetting @count = 0 @machine.after_failure {@count += 1} @transition.run_callbacks {{:success => false}} @transition.reset @transition.run_callbacks {{:success => false}} assert_equal 2, @count end end class TransitionWithMultipleFailureCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_run_in_the_order_they_were_defined @callbacks = [] @machine.after_failure {@callbacks << 1} @machine.after_failure {@callbacks << 2} @transition.run_callbacks {{:success => false}} assert_equal [1, 2], @callbacks end def test_should_not_run_further_callbacks_if_halted @callbacks = [] @machine.after_failure {@callbacks << 1; throw :halt} @machine.after_failure {@callbacks << 2} assert_equal true, @transition.run_callbacks {{:success => false}} assert_equal [1], @callbacks end def test_should_fail_if_any_callback_halted @machine.after_failure {true} @machine.after_failure {throw :halt} assert_equal true, @transition.run_callbacks {{:success => false}} end end class TransitionWithMixedCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_before_and_around_callbacks_in_order_defined @callbacks = [] @machine.before_transition {@callbacks << :before_1} @machine.around_transition {|block| @callbacks << :around; block.call} @machine.before_transition {@callbacks << :before_2} assert_equal true, @transition.run_callbacks assert_equal [:before_1, :around, :before_2], @callbacks end def test_should_run_around_callbacks_before_after_callbacks @callbacks = [] @machine.after_transition {@callbacks << :after_1} @machine.around_transition {|block| block.call; @callbacks << :after_2} @machine.after_transition {@callbacks << :after_3} assert_equal true, @transition.run_callbacks assert_equal [:after_2, :after_1, :after_3], @callbacks end def test_should_have_access_to_result_for_both_after_and_around_callbacks @machine.after_transition {@after_result = @transition.result} @machine.around_transition {|block| block.call; @around_result = @transition.result} @transition.run_callbacks {{:result => 1, :success => true}} assert_equal 1, @after_result assert_equal 1, @around_result end def test_should_not_run_further_callbacks_if_before_callback_halts @callbacks = [] @machine.before_transition {@callbacks << :before_1} @machine.around_transition {|block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1} @machine.before_transition {@callbacks << :before_2; throw :halt} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2} @machine.after_transition {@callbacks << :after} assert_equal false, @transition.run_callbacks assert_equal [:before_1, :before_around_1, :before_2], @callbacks end def test_should_not_run_further_callbacks_if_before_yield_halts @callbacks = [] @machine.before_transition {@callbacks << :before_1} @machine.around_transition {|block| @callbacks << :before_around_1; throw :halt} @machine.before_transition {@callbacks << :before_2; throw :halt} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2} @machine.after_transition {@callbacks << :after} assert_equal false, @transition.run_callbacks assert_equal [:before_1, :before_around_1], @callbacks end def test_should_not_run_further_callbacks_if_around_callback_fails_to_yield @callbacks = [] @machine.before_transition {@callbacks << :before_1} @machine.around_transition {|block| @callbacks << :before_around_1} @machine.before_transition {@callbacks << :before_2; throw :halt} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2} @machine.after_transition {@callbacks << :after} assert_equal false, @transition.run_callbacks assert_equal [:before_1, :before_around_1], @callbacks end def test_should_not_run_further_callbacks_if_after_yield_halts @callbacks = [] @machine.before_transition {@callbacks << :before_1} @machine.around_transition {|block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1; throw :halt} @machine.before_transition {@callbacks << :before_2} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2} @machine.after_transition {@callbacks << :after} assert_equal true, @transition.run_callbacks assert_equal [:before_1, :before_around_1, :before_2, :before_around_2, :after_around_2, :after_around_1], @callbacks end def test_should_not_run_further_callbacks_if_after_callback_halts @callbacks = [] @machine.before_transition {@callbacks << :before_1} @machine.around_transition {|block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1} @machine.before_transition {@callbacks << :before_2} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2} @machine.after_transition {@callbacks << :after_1; throw :halt} @machine.after_transition {@callbacks << :after_2} assert_equal true, @transition.run_callbacks assert_equal [:before_1, :before_around_1, :before_2, :before_around_2, :after_around_2, :after_around_1, :after_1], @callbacks end end class TransitionWithBeforeCallbacksSkippedTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_not_run_before_callbacks @run = false @machine.before_transition {@run = true} assert_equal false, @transition.run_callbacks(:before => false) assert !@run end def test_should_run_failure_callbacks @machine.after_failure {@run = true} assert_equal false, @transition.run_callbacks(:before => false) assert @run end end class TransitionWithAfterCallbacksSkippedTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_run_before_callbacks @machine.before_transition {@run = true} assert_equal true, @transition.run_callbacks(:after => false) assert @run end def test_should_not_run_after_callbacks @run = false @machine.after_transition {@run = true} assert_equal true, @transition.run_callbacks(:after => false) assert !@run end if StateMachine::Transition.pause_supported? def test_should_run_around_callbacks_before_yield @machine.around_transition {|block| @run = true; block.call} assert_equal true, @transition.run_callbacks(:after => false) assert @run end def test_should_not_run_around_callbacks_after_yield @run = false @machine.around_transition {|block| block.call; @run = true} assert_equal true, @transition.run_callbacks(:after => false) assert !@run end def test_should_continue_around_transition_execution_on_second_call @callbacks = [] @machine.around_transition {|block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2} @machine.after_transition {@callbacks << :after} assert_equal true, @transition.run_callbacks(:after => false) assert_equal [:before_around_1, :before_around_2], @callbacks assert_equal true, @transition.run_callbacks assert_equal [:before_around_1, :before_around_2, :after_around_2, :after_around_1, :after], @callbacks end def test_should_not_run_further_callbacks_if_halted_during_continue_around_transition @callbacks = [] @machine.around_transition {|block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2; throw :halt} @machine.after_transition {@callbacks << :after} assert_equal true, @transition.run_callbacks(:after => false) assert_equal [:before_around_1, :before_around_2], @callbacks assert_equal true, @transition.run_callbacks assert_equal [:before_around_1, :before_around_2, :after_around_2], @callbacks end def test_should_not_be_able_to_continue_twice @count = 0 @machine.around_transition {|block| block.call; @count += 1} @machine.after_transition {@count += 1} @transition.run_callbacks(:after => false) 2.times do assert_equal true, @transition.run_callbacks assert_equal 2, @count end end def test_should_not_be_able_to_continue_again_after_halted @count = 0 @machine.around_transition {|block| block.call; @count += 1; throw :halt} @machine.after_transition {@count += 1} @transition.run_callbacks(:after => false) 2.times do assert_equal true, @transition.run_callbacks assert_equal 1, @count end end def test_should_have_access_to_result_after_continued @machine.around_transition {|block| @around_before_result = @transition.result; block.call; @around_after_result = @transition.result} @machine.after_transition {@after_result = @transition.result} @transition.run_callbacks(:after => false) @transition.run_callbacks {{:result => 1}} assert_nil @around_before_result assert_equal 1, @around_after_result assert_equal 1, @after_result end def test_should_raise_exceptions_during_around_callbacks_after_yield_in_second_execution @machine.around_transition {|block| block.call; raise ArgumentError} assert_nothing_raised { @transition.run_callbacks(:after => false) } assert_raise(ArgumentError) { @transition.run_callbacks } end else def test_should_raise_exception_on_second_call @callbacks = [] @machine.around_transition {|block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2} @machine.after_transition {@callbacks << :after} assert_raise(ArgumentError) { @transition.run_callbacks(:after => false) } end end end class TransitionAfterBeingPerformedTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :saved, :save_state def save @save_state = state @saved = true 1 end end @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_have_empty_args assert_equal [], @transition.args end def test_should_have_a_result assert_equal 1, @transition.result end def test_should_be_successful assert_equal true, @result end def test_should_change_the_current_state assert_equal 'idling', @object.state end def test_should_run_the_action assert @object.saved end def test_should_run_the_action_after_saving_the_state assert_equal 'idling', @object.save_state end end class TransitionWithPerformArgumentsTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :saved def save @saved = true end end @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_have_arguments @transition.perform(1, 2) assert_equal [1, 2], @transition.args assert @object.saved end def test_should_not_include_run_action_in_arguments @transition.perform(1, 2, false) assert_equal [1, 2], @transition.args assert !@object.saved end end class TransitionWithoutRunningActionTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :saved def save @saved = true end end @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.event :ignite @machine.after_transition {|object| @run_after = true} @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) @result = @transition.perform(false) end def test_should_have_empty_args assert_equal [], @transition.args end def test_should_not_have_a_result assert_nil @transition.result end def test_should_be_successful assert_equal true, @result end def test_should_change_the_current_state assert_equal 'idling', @object.state end def test_should_not_run_the_action assert !@object.saved end def test_should_run_after_callbacks assert @run_after end end class TransitionWithTransactionsTest < Test::Unit::TestCase def setup @klass = Class.new do class << self attr_accessor :running_transaction end attr_accessor :result def save @result = self.class.running_transaction true end end @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) class << @machine def within_transaction(object) owner_class.running_transaction = object yield owner_class.running_transaction = false end end end def test_should_run_blocks_within_transaction_for_object @transition.within_transaction do @result = @klass.running_transaction end assert_equal @object, @result end end class TransitionTransientTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) @transition.transient = true end def test_should_be_transient assert @transition.transient? end end class TransitionEqualityTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_be_equal_with_same_properties transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) assert_equal transition, @transition end def test_should_not_be_equal_with_different_machines machine = StateMachine::Machine.new(@klass, :status, :namespace => :other) machine.state :parked, :idling machine.event :ignite transition = StateMachine::Transition.new(@object, machine, :ignite, :parked, :idling) assert_not_equal transition, @transition end def test_should_not_be_equal_with_different_objects transition = StateMachine::Transition.new(@klass.new, @machine, :ignite, :parked, :idling) assert_not_equal transition, @transition end def test_should_not_be_equal_with_different_event_names @machine.event :park transition = StateMachine::Transition.new(@object, @machine, :park, :parked, :idling) assert_not_equal transition, @transition end def test_should_not_be_equal_with_different_from_state_names @machine.state :first_gear transition = StateMachine::Transition.new(@object, @machine, :ignite, :first_gear, :idling) assert_not_equal transition, @transition end def test_should_not_be_equal_with_different_to_state_names @machine.state :first_gear transition = StateMachine::Transition.new(@object, @machine, :ignite, :idling, :first_gear) assert_not_equal transition, @transition end end state-machine-1.2.0/test/unit/eval_helpers_test.rb0000644000175000017500000001443512305405267021620 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class EvalHelpersBaseTest < Test::Unit::TestCase include StateMachine::EvalHelpers def default_test end end class EvalHelpersTest < EvalHelpersBaseTest def setup @object = Object.new end def test_should_raise_exception_if_method_is_not_symbol_string_or_proc exception = assert_raise(ArgumentError) { evaluate_method(@object, 1) } assert_match(/Methods must/, exception.message) end end class EvalHelpersSymbolTest < EvalHelpersBaseTest def setup class << (@object = Object.new) def callback true end end end def test_should_call_method_on_object_with_no_arguments assert_equal true, evaluate_method(@object, :callback, 1, 2, 3) end end class EvalHelpersSymbolProtectedTest < EvalHelpersBaseTest def setup class << (@object = Object.new) protected def callback true end end end def test_should_call_method_on_object_with_no_arguments assert_equal true, evaluate_method(@object, :callback, 1, 2, 3) end end class EvalHelpersSymbolPrivateTest < EvalHelpersBaseTest def setup class << (@object = Object.new) private def callback true end end end def test_should_call_method_on_object_with_no_arguments assert_equal true, evaluate_method(@object, :callback, 1, 2, 3) end end class EvalHelpersSymbolWithArgumentsTest < EvalHelpersBaseTest def setup class << (@object = Object.new) def callback(*args) args end end end def test_should_call_method_with_all_arguments assert_equal [1, 2, 3], evaluate_method(@object, :callback, 1, 2, 3) end end class EvalHelpersSymbolWithBlockTest < EvalHelpersBaseTest def setup class << (@object = Object.new) def callback yield end end end def test_should_call_method_on_object_with_block assert_equal true, evaluate_method(@object, :callback) { true } end end class EvalHelpersSymbolWithArgumentsAndBlockTest < EvalHelpersBaseTest def setup class << (@object = Object.new) def callback(*args) args << yield end end end def test_should_call_method_on_object_with_all_arguments_and_block assert_equal [1, 2, 3, true], evaluate_method(@object, :callback, 1, 2, 3) { true } end end class EvalHelpersSymbolTaintedMethodTest < EvalHelpersBaseTest def setup class << (@object = Object.new) def callback true end taint end end def test_should_not_raise_security_error assert_nothing_raised { evaluate_method(@object, :callback, 1, 2, 3) } end end class EvalHelpersSymbolMethodMissingTest < EvalHelpersBaseTest def setup class << (@object = Object.new) def method_missing(symbol, *args) send("method_missing_#{symbol}", *args) end def method_missing_callback(*args) args end end end def test_should_call_dynamic_method_with_all_arguments assert_equal [1, 2, 3], evaluate_method(@object, :callback, 1, 2, 3) end end class EvalHelpersStringTest < EvalHelpersBaseTest def setup @object = Object.new end def test_should_evaluate_string assert_equal 1, evaluate_method(@object, '1') end def test_should_evaluate_string_within_object_context @object.instance_variable_set('@value', 1) assert_equal 1, evaluate_method(@object, '@value') end def test_should_ignore_additional_arguments assert_equal 1, evaluate_method(@object, '1', 2, 3, 4) end end class EvalHelpersStringWithBlockTest < EvalHelpersBaseTest def setup @object = Object.new end def test_should_call_method_on_object_with_block assert_equal 1, evaluate_method(@object, 'yield') { 1 } end end class EvalHelpersProcTest < EvalHelpersBaseTest def setup @object = Object.new @proc = lambda {|obj| obj} end def test_should_call_proc_with_object_as_argument assert_equal @object, evaluate_method(@object, @proc, 1, 2, 3) end end class EvalHelpersProcWithoutArgumentsTest < EvalHelpersBaseTest def setup @object = Object.new @proc = lambda {|*args| args} class << @proc def arity 0 end end end def test_should_call_proc_with_no_arguments assert_equal [], evaluate_method(@object, @proc, 1, 2, 3) end end class EvalHelpersProcWithArgumentsTest < EvalHelpersBaseTest def setup @object = Object.new @proc = lambda {|*args| args} end def test_should_call_method_with_all_arguments assert_equal [@object, 1, 2, 3], evaluate_method(@object, @proc, 1, 2, 3) end end class EvalHelpersProcWithBlockTest < EvalHelpersBaseTest def setup @object = Object.new @proc = lambda {|obj, block| block.call} end def test_should_call_method_on_object_with_block assert_equal true, evaluate_method(@object, @proc, 1, 2, 3) { true } end end class EvalHelpersProcWithBlockWithoutArgumentsTest < EvalHelpersBaseTest def setup @object = Object.new @proc = lambda {|*args| args} class << @proc def arity 0 end end end def test_should_call_proc_without_arguments block = lambda { true } assert_equal [], evaluate_method(@object, @proc, 1, 2, 3, &block) end end class EvalHelpersProcWithBlockWithoutObjectTest < EvalHelpersBaseTest def setup @object = Object.new @proc = lambda {|block| [block]} end def test_should_call_proc_with_block_only block = lambda { true } assert_equal [block], evaluate_method(@object, @proc, 1, 2, 3, &block) end end class EvalHelpersProcBlockAndImplicitArgumentsTest < EvalHelpersBaseTest def setup @object = Object.new @proc = lambda {|*args| args} end def test_should_call_method_on_object_with_all_arguments_and_block block = lambda { true } assert_equal [@object, 1, 2, 3, block], evaluate_method(@object, @proc, 1, 2, 3, &block) end end class EvalHelpersProcBlockAndExplicitArgumentsTest < EvalHelpersBaseTest def setup @object = Object.new @proc = lambda {|object, arg1, arg2, arg3, block| [object, arg1, arg2, arg3, block]} end def test_should_call_method_on_object_with_all_arguments_and_block block = lambda { true } assert_equal [@object, 1, 2, 3, block], evaluate_method(@object, @proc, 1, 2, 3, &block) end end state-machine-1.2.0/test/unit/integrations_test.rb0000644000175000017500000000540112305405267021646 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class IntegrationMatcherTest < Test::Unit::TestCase def setup superclass = Class.new self.class.const_set('Vehicle', superclass) @klass = Class.new(superclass) end def test_should_return_nil_if_no_match_found assert_nil StateMachine::Integrations.match(@klass) end def test_should_return_integration_class_if_match_found integration = Module.new do include StateMachine::Integrations::Base def self.matching_ancestors ['IntegrationMatcherTest::Vehicle'] end end StateMachine::Integrations.const_set('Custom', integration) assert_equal integration, StateMachine::Integrations.match(@klass) ensure StateMachine::Integrations.send(:remove_const, 'Custom') end def test_should_return_nil_if_no_match_found_with_ancestors assert_nil StateMachine::Integrations.match_ancestors(['IntegrationMatcherTest::Fake']) end def test_should_return_integration_class_if_match_found_with_ancestors integration = Module.new do include StateMachine::Integrations::Base def self.matching_ancestors ['IntegrationMatcherTest::Vehicle'] end end StateMachine::Integrations.const_set('Custom', integration) assert_equal integration, StateMachine::Integrations.match_ancestors(['IntegrationMatcherTest::Fake', 'IntegrationMatcherTest::Vehicle']) ensure StateMachine::Integrations.send(:remove_const, 'Custom') end def teardown self.class.send(:remove_const, 'Vehicle') end end class IntegrationFinderTest < Test::Unit::TestCase def test_should_find_base assert_equal StateMachine::Integrations::Base, StateMachine::Integrations.find_by_name(:base) end def test_should_find_active_model assert_equal StateMachine::Integrations::ActiveModel, StateMachine::Integrations.find_by_name(:active_model) end def test_should_find_active_record assert_equal StateMachine::Integrations::ActiveRecord, StateMachine::Integrations.find_by_name(:active_record) end def test_should_find_data_mapper assert_equal StateMachine::Integrations::DataMapper, StateMachine::Integrations.find_by_name(:data_mapper) end def test_should_find_mongo_mapper assert_equal StateMachine::Integrations::MongoMapper, StateMachine::Integrations.find_by_name(:mongo_mapper) end def test_should_find_sequel assert_equal StateMachine::Integrations::Sequel, StateMachine::Integrations.find_by_name(:sequel) end def test_should_raise_an_exception_if_invalid exception = assert_raise(StateMachine::IntegrationNotFound) { StateMachine::Integrations.find_by_name(:invalid) } assert_equal ':invalid is an invalid integration', exception.message end end state-machine-1.2.0/test/unit/event_collection_test.rb0000644000175000017500000003043412305405267022500 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class EventCollectionByDefaultTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @events = StateMachine::EventCollection.new(@machine) @object = @klass.new end def test_should_not_have_any_nodes assert_equal 0, @events.length end def test_should_have_a_machine assert_equal @machine, @events.machine end def test_should_not_have_any_valid_events_for_an_object assert @events.valid_for(@object).empty? end def test_should_not_have_any_transitions_for_an_object assert @events.transitions_for(@object).empty? end end class EventCollectionTest < Test::Unit::TestCase def setup machine = StateMachine::Machine.new(Class.new, :namespace => 'alarm') @events = StateMachine::EventCollection.new(machine) @events << @open = StateMachine::Event.new(machine, :enable) machine.events.concat(@events) end def test_should_index_by_name assert_equal @open, @events[:enable, :name] end def test_should_index_by_name_by_default assert_equal @open, @events[:enable] end def test_should_index_by_string_name assert_equal @open, @events['enable'] end def test_should_index_by_qualified_name assert_equal @open, @events[:enable_alarm, :qualified_name] end def test_should_index_by_string_qualified_name assert_equal @open, @events['enable_alarm', :qualified_name] end end class EventStringCollectionTest < Test::Unit::TestCase def setup machine = StateMachine::Machine.new(Class.new, :namespace => 'alarm') @events = StateMachine::EventCollection.new(machine) @events << @open = StateMachine::Event.new(machine, 'enable') machine.events.concat(@events) end def test_should_index_by_name assert_equal @open, @events['enable', :name] end def test_should_index_by_name_by_default assert_equal @open, @events['enable'] end def test_should_index_by_symbol_name assert_equal @open, @events[:enable] end def test_should_index_by_qualified_name assert_equal @open, @events['enable_alarm', :qualified_name] end def test_should_index_by_symbol_qualified_name assert_equal @open, @events[:enable_alarm, :qualified_name] end end class EventCollectionWithEventsWithTransitionsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @events = StateMachine::EventCollection.new(@machine) @machine.state :idling, :first_gear @events << @ignite = StateMachine::Event.new(@machine, :ignite) @ignite.transition :parked => :idling @events << @park = StateMachine::Event.new(@machine, :park) @park.transition :idling => :parked @events << @shift_up = StateMachine::Event.new(@machine, :shift_up) @shift_up.transition :parked => :first_gear @shift_up.transition :idling => :first_gear, :if => lambda{false} @machine.events.concat(@events) @object = @klass.new end def test_should_find_valid_events_based_on_current_state assert_equal [@ignite, @shift_up], @events.valid_for(@object) end def test_should_filter_valid_events_by_from_state assert_equal [@park], @events.valid_for(@object, :from => :idling) end def test_should_filter_valid_events_by_to_state assert_equal [@shift_up], @events.valid_for(@object, :to => :first_gear) end def test_should_filter_valid_events_by_event assert_equal [@ignite], @events.valid_for(@object, :on => :ignite) end def test_should_filter_valid_events_by_multiple_requirements assert_equal [], @events.valid_for(@object, :from => :idling, :to => :first_gear) end def test_should_allow_finding_valid_events_without_guards assert_equal [@shift_up], @events.valid_for(@object, :from => :idling, :to => :first_gear, :guard => false) end def test_should_find_valid_transitions_based_on_current_state assert_equal [ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling), StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :first_gear) ], @events.transitions_for(@object) end def test_should_filter_valid_transitions_by_from_state assert_equal [StateMachine::Transition.new(@object, @machine, :park, :idling, :parked)], @events.transitions_for(@object, :from => :idling) end def test_should_filter_valid_transitions_by_to_state assert_equal [StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :first_gear)], @events.transitions_for(@object, :to => :first_gear) end def test_should_filter_valid_transitions_by_event assert_equal [StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)], @events.transitions_for(@object, :on => :ignite) end def test_should_filter_valid_transitions_by_multiple_requirements assert_equal [], @events.transitions_for(@object, :from => :idling, :to => :first_gear) end def test_should_allow_finding_valid_transitions_without_guards assert_equal [StateMachine::Transition.new(@object, @machine, :shift_up, :idling, :first_gear)], @events.transitions_for(@object, :from => :idling, :to => :first_gear, :guard => false) end end class EventCollectionWithMultipleEventsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @events = StateMachine::EventCollection.new(@machine) @machine.state :first_gear @park, @shift_down = @machine.event :park, :shift_down @events << @park @park.transition :first_gear => :parked @events << @shift_down @shift_down.transition :first_gear => :parked @machine.events.concat(@events) end def test_should_only_include_all_valid_events_for_an_object object = @klass.new object.state = 'first_gear' assert_equal [@park, @shift_down], @events.valid_for(object) end end class EventCollectionWithoutMachineActionTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @events = StateMachine::EventCollection.new(@machine) @events << StateMachine::Event.new(@machine, :ignite) @machine.events.concat(@events) @object = @klass.new end def test_should_not_have_an_attribute_transition assert_nil @events.attribute_transition_for(@object) end end class EventCollectionAttributeWithMachineActionTest < Test::Unit::TestCase def setup @klass = Class.new do def save end end @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save) @events = StateMachine::EventCollection.new(@machine) @machine.state :parked, :idling @events << @ignite = StateMachine::Event.new(@machine, :ignite) @machine.events.concat(@events) @object = @klass.new end def test_should_not_have_transition_if_nil @object.state_event = nil assert_nil @events.attribute_transition_for(@object) end def test_should_not_have_transition_if_empty @object.state_event = '' assert_nil @events.attribute_transition_for(@object) end def test_should_have_invalid_transition_if_invalid_event_specified @object.state_event = 'invalid' assert_equal false, @events.attribute_transition_for(@object) end def test_should_have_invalid_transition_if_event_cannot_be_fired @object.state_event = 'ignite' assert_equal false, @events.attribute_transition_for(@object) end def test_should_have_valid_transition_if_event_can_be_fired @ignite.transition :parked => :idling @object.state_event = 'ignite' assert_instance_of StateMachine::Transition, @events.attribute_transition_for(@object) end def test_should_have_valid_transition_if_already_defined_in_transition_cache @ignite.transition :parked => :idling @object.state_event = nil @object.send(:state_event_transition=, transition = @ignite.transition_for(@object)) assert_equal transition, @events.attribute_transition_for(@object) end def test_should_use_transition_cache_if_both_event_and_transition_are_present @ignite.transition :parked => :idling @object.state_event = 'ignite' @object.send(:state_event_transition=, transition = @ignite.transition_for(@object)) assert_equal transition, @events.attribute_transition_for(@object) end end class EventCollectionAttributeWithNamespacedMachineTest < Test::Unit::TestCase def setup @klass = Class.new do def save end end @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm', :initial => :active, :action => :save) @events = StateMachine::EventCollection.new(@machine) @machine.state :active, :off @events << @disable = StateMachine::Event.new(@machine, :disable) @machine.events.concat(@events) @object = @klass.new end def test_should_not_have_transition_if_nil @object.state_event = nil assert_nil @events.attribute_transition_for(@object) end def test_should_have_invalid_transition_if_event_cannot_be_fired @object.state_event = 'disable' assert_equal false, @events.attribute_transition_for(@object) end def test_should_have_valid_transition_if_event_can_be_fired @disable.transition :active => :off @object.state_event = 'disable' assert_instance_of StateMachine::Transition, @events.attribute_transition_for(@object) end end class EventCollectionWithValidationsTest < Test::Unit::TestCase def setup StateMachine::Integrations.const_set('Custom', Module.new do include StateMachine::Integrations::Base def invalidate(object, attribute, message, values = []) (object.errors ||= []) << generate_message(message, values) end def reset(object) object.errors = [] end end) @klass = Class.new do attr_accessor :errors def initialize @errors = [] super end end @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save, :integration => :custom) @events = StateMachine::EventCollection.new(@machine) @parked, @idling = @machine.state :parked, :idling @events << @ignite = StateMachine::Event.new(@machine, :ignite) @machine.events.concat(@events) @object = @klass.new end def test_should_invalidate_if_invalid_event_specified @object.state_event = 'invalid' @events.attribute_transition_for(@object, true) assert_equal ['is invalid'], @object.errors end def test_should_invalidate_if_event_cannot_be_fired @object.state = 'idling' @object.state_event = 'ignite' @events.attribute_transition_for(@object, true) assert_equal ['cannot transition when idling'], @object.errors end def test_should_invalidate_with_human_name_if_invalid_event_specified @idling.human_name = 'waiting' @object.state = 'idling' @object.state_event = 'ignite' @events.attribute_transition_for(@object, true) assert_equal ['cannot transition when waiting'], @object.errors end def test_should_not_invalidate_event_can_be_fired @ignite.transition :parked => :idling @object.state_event = 'ignite' @events.attribute_transition_for(@object, true) assert_equal [], @object.errors end def teardown StateMachine::Integrations.send(:remove_const, 'Custom') end end class EventCollectionWithCustomMachineAttributeTest < Test::Unit::TestCase def setup @klass = Class.new do def save end end @machine = StateMachine::Machine.new(@klass, :state, :attribute => :state_id, :initial => :parked, :action => :save) @events = StateMachine::EventCollection.new(@machine) @machine.state :parked, :idling @events << @ignite = StateMachine::Event.new(@machine, :ignite) @machine.events.concat(@events) @object = @klass.new end def test_should_not_have_transition_if_nil @object.state_event = nil assert_nil @events.attribute_transition_for(@object) end def test_should_have_valid_transition_if_event_can_be_fired @ignite.transition :parked => :idling @object.state_event = 'ignite' assert_instance_of StateMachine::Transition, @events.attribute_transition_for(@object) end end state-machine-1.2.0/test/unit/invalid_event_test.rb0000644000175000017500000000104112305405267021763 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class InvalidEventTest < Test::Unit::TestCase def setup @object = Object.new @invalid_event = StateMachine::InvalidEvent.new(@object, :invalid) end def test_should_have_an_object assert_equal @object, @invalid_event.object end def test_should_have_an_event assert_equal :invalid, @invalid_event.event end def test_should_generate_a_message assert_equal ':invalid is an unknown state machine event', @invalid_event.message end end state-machine-1.2.0/test/unit/state_context_test.rb0000644000175000017500000003202112305405267022022 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class Validateable class << self def validate(*args, &block) args << block if block_given? args end end end class StateContextTest < Test::Unit::TestCase def setup @klass = Class.new(Validateable) @machine = StateMachine::Machine.new(@klass, :initial => :parked) @state = @machine.state :parked @state_context = StateMachine::StateContext.new(@state) end def test_should_have_a_machine assert_equal @machine, @state_context.machine end def test_should_have_a_state assert_equal @state, @state_context.state end end class StateContextTransitionTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @state = @machine.state :parked @state_context = StateMachine::StateContext.new(@state) end def test_should_not_allow_except_to exception = assert_raise(ArgumentError) { @state_context.transition(:except_to => :idling) } assert_equal 'Invalid key(s): except_to', exception.message end def test_should_not_allow_except_from exception = assert_raise(ArgumentError) { @state_context.transition(:except_from => :idling) } assert_equal 'Invalid key(s): except_from', exception.message end def test_should_not_allow_implicit_transitions exception = assert_raise(ArgumentError) { @state_context.transition(:parked => :idling) } assert_equal 'Invalid key(s): parked', exception.message end def test_should_not_allow_except_on exception = assert_raise(ArgumentError) { @state_context.transition(:except_on => :park) } assert_equal 'Invalid key(s): except_on', exception.message end def test_should_require_on_event exception = assert_raise(ArgumentError) { @state_context.transition(:to => :idling) } assert_equal 'Must specify :on event', exception.message end def test_should_not_allow_missing_from_and_to exception = assert_raise(ArgumentError) { @state_context.transition(:on => :ignite) } assert_equal 'Must specify either :to or :from state', exception.message end def test_should_not_allow_from_and_to exception = assert_raise(ArgumentError) { @state_context.transition(:on => :ignite, :from => :parked, :to => :idling) } assert_equal 'Must specify either :to or :from state', exception.message end def test_should_allow_to_state_if_missing_from_state assert_nothing_raised { @state_context.transition(:on => :park, :from => :parked) } end def test_should_allow_from_state_if_missing_to_state assert_nothing_raised { @state_context.transition(:on => :ignite, :to => :idling) } end def test_should_automatically_set_to_option_with_from_state branch = @state_context.transition(:from => :idling, :on => :park) assert_instance_of StateMachine::Branch, branch state_requirements = branch.state_requirements assert_equal 1, state_requirements.length from_requirement = state_requirements[0][:to] assert_instance_of StateMachine::WhitelistMatcher, from_requirement assert_equal [:parked], from_requirement.values end def test_should_automatically_set_from_option_with_to_state branch = @state_context.transition(:to => :idling, :on => :ignite) assert_instance_of StateMachine::Branch, branch state_requirements = branch.state_requirements assert_equal 1, state_requirements.length from_requirement = state_requirements[0][:from] assert_instance_of StateMachine::WhitelistMatcher, from_requirement assert_equal [:parked], from_requirement.values end def test_should_allow_if_condition assert_nothing_raised {@state_context.transition(:to => :idling, :on => :park, :if => :seatbelt_on?)} end def test_should_allow_unless_condition assert_nothing_raised {@state_context.transition(:to => :idling, :on => :park, :unless => :seatbelt_off?)} end def test_should_include_all_transition_states_in_machine_states @state_context.transition(:to => :idling, :on => :ignite) assert_equal [:parked, :idling], @machine.states.map {|state| state.name} end def test_should_include_all_transition_events_in_machine_events @state_context.transition(:to => :idling, :on => :ignite) assert_equal [:ignite], @machine.events.map {|event| event.name} end def test_should_allow_multiple_events @state_context.transition(:to => :idling, :on => [:ignite, :shift_up]) assert_equal [:ignite, :shift_up], @machine.events.map {|event| event.name} end end class StateContextWithMatchingTransitionTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :parked) @state = @machine.state :parked @state_context = StateMachine::StateContext.new(@state) @state_context.transition(:to => :idling, :on => :ignite) @event = @machine.event(:ignite) @object = @klass.new end def test_should_be_able_to_fire assert @event.can_fire?(@object) end def test_should_have_a_transition transition = @event.transition_for(@object) assert_not_nil transition assert_equal 'parked', transition.from assert_equal 'idling', transition.to assert_equal :ignite, transition.event end end class StateContextProxyTest < Test::Unit::TestCase def setup @klass = Class.new(Validateable) machine = StateMachine::Machine.new(@klass, :initial => :parked) state = machine.state :parked @state_context = StateMachine::StateContext.new(state) end def test_should_call_class_with_same_arguments options = {} validation = @state_context.validate(:name, options) assert_equal [:name, options], validation end def test_should_pass_block_through_to_class options = {} proxy_block = lambda {} validation = @state_context.validate(:name, options, &proxy_block) assert_equal [:name, options, proxy_block], validation end end class StateContextProxyWithoutConditionsTest < Test::Unit::TestCase def setup @klass = Class.new(Validateable) machine = StateMachine::Machine.new(@klass, :initial => :parked) state = machine.state :parked @state_context = StateMachine::StateContext.new(state) @object = @klass.new @options = @state_context.validate[0] end def test_should_have_options_configuration assert_instance_of Hash, @options end def test_should_have_if_option assert_not_nil @options[:if] end def test_should_be_false_if_state_is_different @object.state = nil assert !@options[:if].call(@object) end def test_should_be_true_if_state_matches assert @options[:if].call(@object) end end class StateContextProxyWithIfConditionTest < Test::Unit::TestCase def setup @klass = Class.new(Validateable) machine = StateMachine::Machine.new(@klass, :initial => :parked) state = machine.state :parked @state_context = StateMachine::StateContext.new(state) @object = @klass.new @condition_result = nil @options = @state_context.validate(:if => lambda {@condition_result})[0] end def test_should_have_if_option assert_not_nil @options[:if] end def test_should_be_false_if_state_is_different @object.state = nil assert !@options[:if].call(@object) end def test_should_be_false_if_original_condition_is_false @condition_result = false assert !@options[:if].call(@object) end def test_should_be_true_if_state_matches_and_original_condition_is_true @condition_result = true assert @options[:if].call(@object) end def test_should_evaluate_symbol_condition @klass.class_eval do attr_accessor :callback end options = @state_context.validate(:if => :callback)[0] object = @klass.new object.callback = false assert !options[:if].call(object) object.callback = true assert options[:if].call(object) end def test_should_evaluate_string_condition @klass.class_eval do attr_accessor :callback end options = @state_context.validate(:if => '@callback')[0] object = @klass.new object.callback = false assert !options[:if].call(object) object.callback = true assert options[:if].call(object) end end class StateContextProxyWithMultipleIfConditionsTest < Test::Unit::TestCase def setup @klass = Class.new(Validateable) machine = StateMachine::Machine.new(@klass, :initial => :parked) state = machine.state :parked @state_context = StateMachine::StateContext.new(state) @object = @klass.new @first_condition_result = nil @second_condition_result = nil @options = @state_context.validate(:if => [lambda {@first_condition_result}, lambda {@second_condition_result}])[0] end def test_should_be_true_if_all_conditions_are_true @first_condition_result = true @second_condition_result = true assert @options[:if].call(@object) end def test_should_be_false_if_any_condition_is_false @first_condition_result = true @second_condition_result = false assert !@options[:if].call(@object) @first_condition_result = false @second_condition_result = true assert !@options[:if].call(@object) end end class StateContextProxyWithUnlessConditionTest < Test::Unit::TestCase def setup @klass = Class.new(Validateable) machine = StateMachine::Machine.new(@klass, :initial => :parked) state = machine.state :parked @state_context = StateMachine::StateContext.new(state) @object = @klass.new @condition_result = nil @options = @state_context.validate(:unless => lambda {@condition_result})[0] end def test_should_have_if_option assert_not_nil @options[:if] end def test_should_be_false_if_state_is_different @object.state = nil assert !@options[:if].call(@object) end def test_should_be_false_if_original_condition_is_true @condition_result = true assert !@options[:if].call(@object) end def test_should_be_true_if_state_matches_and_original_condition_is_false @condition_result = false assert @options[:if].call(@object) end def test_should_evaluate_symbol_condition @klass.class_eval do attr_accessor :callback end options = @state_context.validate(:unless => :callback)[0] object = @klass.new object.callback = true assert !options[:if].call(object) object.callback = false assert options[:if].call(object) end def test_should_evaluate_string_condition @klass.class_eval do attr_accessor :callback end options = @state_context.validate(:unless => '@callback')[0] object = @klass.new object.callback = true assert !options[:if].call(object) object.callback = false assert options[:if].call(object) end end class StateContextProxyWithMultipleUnlessConditionsTest < Test::Unit::TestCase def setup @klass = Class.new(Validateable) machine = StateMachine::Machine.new(@klass, :initial => :parked) state = machine.state :parked @state_context = StateMachine::StateContext.new(state) @object = @klass.new @first_condition_result = nil @second_condition_result = nil @options = @state_context.validate(:unless => [lambda {@first_condition_result}, lambda {@second_condition_result}])[0] end def test_should_be_true_if_all_conditions_are_false @first_condition_result = false @second_condition_result = false assert @options[:if].call(@object) end def test_should_be_false_if_any_condition_is_true @first_condition_result = true @second_condition_result = false assert !@options[:if].call(@object) @first_condition_result = false @second_condition_result = true assert !@options[:if].call(@object) end end class StateContextProxyWithIfAndUnlessConditionsTest < Test::Unit::TestCase def setup @klass = Class.new(Validateable) machine = StateMachine::Machine.new(@klass, :initial => :parked) state = machine.state :parked @state_context = StateMachine::StateContext.new(state) @object = @klass.new @if_condition_result = nil @unless_condition_result = nil @options = @state_context.validate(:if => lambda {@if_condition_result}, :unless => lambda {@unless_condition_result})[0] end def test_should_be_false_if_if_condition_is_false @if_condition_result = false @unless_condition_result = false assert !@options[:if].call(@object) @if_condition_result = false @unless_condition_result = true assert !@options[:if].call(@object) end def test_should_be_false_if_unless_condition_is_true @if_condition_result = false @unless_condition_result = true assert !@options[:if].call(@object) @if_condition_result = true @unless_condition_result = true assert !@options[:if].call(@object) end def test_should_be_true_if_if_condition_is_true_and_unless_condition_is_false @if_condition_result = true @unless_condition_result = false assert @options[:if].call(@object) end end state-machine-1.2.0/test/unit/state_test.rb0000644000175000017500000006451512305405267020273 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class StateByDefaultTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.states << @state = StateMachine::State.new(@machine, :parked) end def test_should_have_a_machine assert_equal @machine, @state.machine end def test_should_have_a_name assert_equal :parked, @state.name end def test_should_have_a_qualified_name assert_equal :parked, @state.qualified_name end def test_should_have_a_human_name assert_equal 'parked', @state.human_name end def test_should_use_stringify_the_name_as_the_value assert_equal 'parked', @state.value end def test_should_not_be_initial assert !@state.initial end def test_should_not_have_a_matcher assert_nil @state.matcher end def test_should_not_have_any_methods expected = {} assert_equal expected, @state.methods end end class StateTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.states << @state = StateMachine::State.new(@machine, :parked) end def test_should_raise_exception_if_invalid_option_specified exception = assert_raise(ArgumentError) {StateMachine::State.new(@machine, :parked, :invalid => true)} assert_equal 'Invalid key(s): invalid', exception.message end def test_should_allow_changing_machine new_machine = StateMachine::Machine.new(Class.new) @state.machine = new_machine assert_equal new_machine, @state.machine end def test_should_allow_changing_value @state.value = 1 assert_equal 1, @state.value end def test_should_allow_changing_initial @state.initial = true assert @state.initial end def test_should_allow_changing_matcher matcher = lambda {} @state.matcher = matcher assert_equal matcher, @state.matcher end def test_should_allow_changing_human_name @state.human_name = 'stopped' assert_equal 'stopped', @state.human_name end def test_should_use_pretty_inspect assert_equal '#', @state.inspect end end class StateWithoutNameTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.states << @state = StateMachine::State.new(@machine, nil) end def test_should_have_a_nil_name assert_nil @state.name end def test_should_have_a_nil_qualified_name assert_nil @state.qualified_name end def test_should_have_an_empty_human_name assert_equal 'nil', @state.human_name end def test_should_have_a_nil_value assert_nil @state.value end def test_should_not_redefine_nil_predicate object = @klass.new assert !object.nil? assert !object.respond_to?('?') end def test_should_have_a_description assert_equal 'nil', @state.description end def test_should_have_a_description_using_human_name assert_equal 'nil', @state.description(:human_name => true) end end class StateWithNameTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.states << @state = StateMachine::State.new(@machine, :parked) end def test_should_have_a_name assert_equal :parked, @state.name end def test_should_have_a_qualified_name assert_equal :parked, @state.name end def test_should_have_a_human_name assert_equal 'parked', @state.human_name end def test_should_use_stringify_the_name_as_the_value assert_equal 'parked', @state.value end def test_should_match_stringified_name assert @state.matches?('parked') assert !@state.matches?('idling') end def test_should_not_include_value_in_description assert_equal 'parked', @state.description end def test_should_allow_using_human_name_in_description @state.human_name = 'Parked' assert_equal 'Parked', @state.description(:human_name => true) end def test_should_define_predicate assert @klass.new.respond_to?(:parked?) end end class StateWithNilValueTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.states << @state = StateMachine::State.new(@machine, :parked, :value => nil) end def test_should_have_a_name assert_equal :parked, @state.name end def test_should_have_a_nil_value assert_nil @state.value end def test_should_match_nil_values assert @state.matches?(nil) end def test_should_have_a_description assert_equal 'parked (nil)', @state.description end def test_should_have_a_description_with_human_name @state.human_name = 'Parked' assert_equal 'Parked (nil)', @state.description(:human_name => true) end def test_should_define_predicate object = @klass.new assert object.respond_to?(:parked?) end end class StateWithSymbolicValueTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.states << @state = StateMachine::State.new(@machine, :parked, :value => :parked) end def test_should_use_custom_value assert_equal :parked, @state.value end def test_should_not_include_value_in_description assert_equal 'parked', @state.description end def test_should_allow_human_name_in_description @state.human_name = 'Parked' assert_equal 'Parked', @state.description(:human_name => true) end def test_should_match_symbolic_value assert @state.matches?(:parked) assert !@state.matches?('parked') end def test_should_define_predicate object = @klass.new assert object.respond_to?(:parked?) end end class StateWithIntegerValueTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.states << @state = StateMachine::State.new(@machine, :parked, :value => 1) end def test_should_use_custom_value assert_equal 1, @state.value end def test_should_include_value_in_description assert_equal 'parked (1)', @state.description end def test_should_allow_human_name_in_description @state.human_name = 'Parked' assert_equal 'Parked (1)', @state.description(:human_name => true) end def test_should_match_integer_value assert @state.matches?(1) assert !@state.matches?(2) end def test_should_define_predicate object = @klass.new assert object.respond_to?(:parked?) end end class StateWithLambdaValueTest < Test::Unit::TestCase def setup @klass = Class.new @args = nil @machine = StateMachine::Machine.new(@klass) @value = lambda {|*args| @args = args; :parked} @machine.states << @state = StateMachine::State.new(@machine, :parked, :value => @value) end def test_should_use_evaluated_value_by_default assert_equal :parked, @state.value end def test_should_allow_access_to_original_value assert_equal @value, @state.value(false) end def test_should_include_masked_value_in_description assert_equal 'parked (*)', @state.description end def test_should_not_pass_in_any_arguments @state.value assert_equal [], @args end def test_should_define_predicate object = @klass.new assert object.respond_to?(:parked?) end def test_should_match_evaluated_value assert @state.matches?(:parked) end end class StateWithCachedLambdaValueTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @dynamic_value = lambda {'value'} @machine.states << @state = StateMachine::State.new(@machine, :parked, :value => @dynamic_value, :cache => true) end def test_should_be_caching assert @state.cache end def test_should_evaluate_value assert_equal 'value', @state.value end def test_should_only_evaluate_value_once value = @state.value assert_same value, @state.value end def test_should_update_value_index_for_state_collection @state.value assert_equal @state, @machine.states['value', :value] assert_nil @machine.states[@dynamic_value, :value] end end class StateWithoutCachedLambdaValueTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @dynamic_value = lambda {'value'} @machine.states << @state = StateMachine::State.new(@machine, :parked, :value => @dynamic_value) end def test_should_not_be_caching assert !@state.cache end def test_should_evaluate_value_each_time value = @state.value assert_not_same value, @state.value end def test_should_not_update_value_index_for_state_collection @state.value assert_nil @machine.states['value', :value] assert_equal @state, @machine.states[@dynamic_value, :value] end end class StateWithMatcherTest < Test::Unit::TestCase def setup @klass = Class.new @args = nil @machine = StateMachine::Machine.new(@klass) @machine.states << @state = StateMachine::State.new(@machine, :parked, :if => lambda {|value| value == 1}) end def test_should_not_match_actual_value assert !@state.matches?('parked') end def test_should_match_evaluated_block assert @state.matches?(1) end end class StateWithHumanNameTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.states << @state = StateMachine::State.new(@machine, :parked, :human_name => 'stopped') end def test_should_use_custom_human_name assert_equal 'stopped', @state.human_name end end class StateWithDynamicHumanNameTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.states << @state = StateMachine::State.new(@machine, :parked, :human_name => lambda {|state, object| ['stopped', object]}) end def test_should_use_custom_human_name human_name, klass = @state.human_name assert_equal 'stopped', human_name assert_equal @klass, klass end def test_should_allow_custom_class_to_be_passed_through human_name, klass = @state.human_name(1) assert_equal 'stopped', human_name assert_equal 1, klass end def test_should_not_cache_value assert_not_same @state.human_name, @state.human_name end end class StateInitialTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.states << @state = StateMachine::State.new(@machine, :parked, :initial => true) end def test_should_be_initial assert @state.initial assert @state.initial? end end class StateNotInitialTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.states << @state = StateMachine::State.new(@machine, :parked, :initial => false) end def test_should_not_be_initial assert !@state.initial assert !@state.initial? end end class StateFinalTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.states << @state = StateMachine::State.new(@machine, :parked) end def test_should_be_final_without_input_transitions assert @state.final? end def test_should_be_final_with_input_transitions @machine.event :park do transition :idling => :parked end assert @state.final? end def test_should_be_final_with_loopback @machine.event :ignite do transition :parked => same end assert @state.final? end end class StateNotFinalTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.states << @state = StateMachine::State.new(@machine, :parked) end def test_should_not_be_final_with_outgoing_whitelist_transitions @machine.event :ignite do transition :parked => :idling end assert !@state.final? end def test_should_not_be_final_with_outgoing_all_transitions @machine.event :ignite do transition all => :idling end assert !@state.final? end def test_should_not_be_final_with_outgoing_blacklist_transitions @machine.event :ignite do transition all - :first_gear => :idling end assert !@state.final? end end class StateWithConflictingHelpersBeforeDefinitionTest < Test::Unit::TestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @superclass = Class.new do def parked? 0 end end @klass = Class.new(@superclass) @machine = StateMachine::Machine.new(@klass) @machine.state :parked @object = @klass.new end def test_should_not_override_state_predicate assert_equal 0, @object.parked? end def test_should_output_warning assert_equal "Instance method \"parked?\" is already defined in #{@superclass.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string end def teardown $stderr = @original_stderr end end class StateWithConflictingHelpersAfterDefinitionTest < Test::Unit::TestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @klass = Class.new do def parked? 0 end end @machine = StateMachine::Machine.new(@klass) @machine.state :parked @object = @klass.new end def test_should_not_override_state_predicate assert_equal 0, @object.parked? end def test_should_still_allow_super_chaining @klass.class_eval do def parked? super end end assert_equal false, @object.parked? end def test_should_not_output_warning assert_equal '', $stderr.string end def teardown $stderr = @original_stderr end end class StateWithConflictingMachineTest < Test::Unit::TestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @klass = Class.new @state_machine = StateMachine::Machine.new(@klass, :state) @state_machine.states << @state = StateMachine::State.new(@state_machine, :parked) end def test_should_output_warning_if_using_different_attribute @status_machine = StateMachine::Machine.new(@klass, :status) @status_machine.states << @state = StateMachine::State.new(@status_machine, :parked) assert_equal "State :parked for :status is already defined in :state\n", $stderr.string end def test_should_not_output_warning_if_using_same_attribute @status_machine = StateMachine::Machine.new(@klass, :status, :attribute => :state) @status_machine.states << @state = StateMachine::State.new(@status_machine, :parked) assert_equal '', $stderr.string end def test_should_not_output_warning_if_using_different_namespace @status_machine = StateMachine::Machine.new(@klass, :status, :namespace => 'alarm') @status_machine.states << @state = StateMachine::State.new(@status_machine, :parked) assert_equal '', $stderr.string end def teardown $stderr = @original_stderr end end class StateWithConflictingMachineNameTest < Test::Unit::TestCase def setup require 'stringio' @original_stderr, $stderr = $stderr, StringIO.new @klass = Class.new @state_machine = StateMachine::Machine.new(@klass, :state) end def test_should_output_warning_if_name_conflicts StateMachine::State.new(@state_machine, :state) assert_equal "Instance method \"state?\" is already defined in #{@klass} :state instance helpers, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string end def teardown $stderr = @original_stderr end end class StateWithNamespaceTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm') @machine.states << @state = StateMachine::State.new(@machine, :active) @object = @klass.new end def test_should_have_a_name assert_equal :active, @state.name end def test_should_have_a_qualified_name assert_equal :alarm_active, @state.qualified_name end def test_should_namespace_predicate assert @object.respond_to?(:alarm_active?) end end class StateAfterBeingCopiedTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.states << @state = StateMachine::State.new(@machine, :parked) @copied_state = @state.dup end def test_should_not_have_the_same_collection_of_methods assert_not_same @state.methods, @copied_state.methods end end class StateWithContextTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @ancestors = @klass.ancestors @machine.states << @state = StateMachine::State.new(@machine, :idling) speed_method = nil rpm_method = nil @state.context do def speed 0 end speed_method = instance_method(:speed) def rpm 1000 end rpm_method = instance_method(:rpm) end @speed_method = speed_method @rpm_method = rpm_method end def test_should_include_new_module_in_owner_class assert_not_equal @ancestors, @klass.ancestors assert_equal 1, @klass.ancestors.size - @ancestors.size end def test_should_define_each_context_method_in_owner_class %w(speed rpm).each {|method| assert @klass.method_defined?(method)} end def test_should_not_use_context_methods_as_owner_class_methods assert_not_equal @speed_method, @klass.instance_method(:speed) assert_not_equal @rpm_method, @klass.instance_method(:rpm) end def test_should_include_context_methods_in_state_methods assert_equal @speed_method, @state.methods[:speed] assert_equal @rpm_method, @state.methods[:rpm] end end class StateWithMultipleContextsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @ancestors = @klass.ancestors @machine.states << @state = StateMachine::State.new(@machine, :idling) speed_method = nil @state.context do def speed 0 end speed_method = instance_method(:speed) end @speed_method = speed_method rpm_method = nil @state.context do def rpm 1000 end rpm_method = instance_method(:rpm) end @rpm_method = rpm_method end def test_should_include_new_module_in_owner_class assert_not_equal @ancestors, @klass.ancestors assert_equal 2, @klass.ancestors.size - @ancestors.size end def test_should_define_each_context_method_in_owner_class %w(speed rpm).each {|method| assert @klass.method_defined?(method)} end def test_should_not_use_context_methods_as_owner_class_methods assert_not_equal @speed_method, @klass.instance_method(:speed) assert_not_equal @rpm_method, @klass.instance_method(:rpm) end def test_should_include_context_methods_in_state_methods assert_equal @speed_method, @state.methods[:speed] assert_equal @rpm_method, @state.methods[:rpm] end end class StateWithExistingContextMethodTest < Test::Unit::TestCase def setup @klass = Class.new do def speed 60 end end @original_speed_method = @klass.instance_method(:speed) @machine = StateMachine::Machine.new(@klass) @machine.states << @state = StateMachine::State.new(@machine, :idling) @state.context do def speed 0 end end end def test_should_not_override_method assert_equal @original_speed_method, @klass.instance_method(:speed) end end class StateWithRedefinedContextMethodTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.states << @state = StateMachine::State.new(@machine, 'on') old_speed_method = nil @state.context do def speed 0 end old_speed_method = instance_method(:speed) end @old_speed_method = old_speed_method current_speed_method = nil @state.context do def speed 'green' end current_speed_method = instance_method(:speed) end @current_speed_method = current_speed_method end def test_should_track_latest_defined_method assert_equal @current_speed_method, @state.methods[:speed] end end class StateWithInvalidMethodCallTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @ancestors = @klass.ancestors @machine.states << @state = StateMachine::State.new(@machine, :idling) @state.context do def speed 0 end end @object = @klass.new end def test_should_call_method_missing_arg assert_equal 1, @state.call(@object, :invalid, lambda {1}) end end class StateWithValidMethodCallForDifferentStateTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @ancestors = @klass.ancestors @machine.states << @state = StateMachine::State.new(@machine, :idling) @state.context do def speed 0 end end @object = @klass.new end def test_should_call_method_missing_arg assert_equal 1, @state.call(@object, :speed, lambda {1}) end end class StateWithValidMethodCallForCurrentStaeTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :initial => :idling) @ancestors = @klass.ancestors @state = @machine.state(:idling) @state.context do def speed(arg = nil) block_given? ? [arg, yield] : arg end end @object = @klass.new end def test_should_not_raise_an_exception assert_nothing_raised { @state.call(@object, :speed, lambda {raise}) } end def test_should_pass_arguments_through assert_equal 1, @state.call(@object, :speed, lambda {}, 1) end def test_should_pass_blocks_through assert_equal [nil, 1], @state.call(@object, :speed) {1} end def test_should_pass_both_arguments_and_blocks_through assert_equal [1, 2], @state.call(@object, :speed, lambda {}, 1) {2} end end begin # Load library require 'graphviz' class StateDrawingTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.states << @state = StateMachine::State.new(@machine, :parked, :value => 1) @machine.event :ignite do transition :parked => :idling end graph = StateMachine::Graph.new('test') @state.draw(graph) @node = graph.get_node('parked') end def test_should_use_ellipse_shape assert_equal 'ellipse', @node['shape'].to_s.gsub('"', '') end def test_should_set_width_to_one assert_equal '1', @node['width'].to_s.gsub('"', '') end def test_should_set_height_to_one assert_equal '1', @node['height'].to_s.gsub('"', '') end def test_should_use_description_as_label assert_equal 'parked (1)', @node['label'].to_s.gsub('"', '') end end class StateDrawingInitialTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.states << @state = StateMachine::State.new(@machine, :parked, :initial => true) @machine.event :ignite do transition :parked => :idling end @graph = StateMachine::Graph.new('test') @state.draw(@graph) @node = @graph.get_node('parked') end def test_should_use_ellipse_as_shape assert_equal 'ellipse', @node['shape'].to_s.gsub('"', '') end def test_should_draw_edge_between_point_and_state assert_equal 2, @graph.node_count assert_equal 1, @graph.edge_count end end class StateDrawingNilNameTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.states << @state = StateMachine::State.new(@machine, nil) graph = StateMachine::Graph.new('test') @state.draw(graph) @node = graph.get_node('nil') end def test_should_have_a_node assert_not_nil @node end def test_should_use_description_as_label assert_equal 'nil', @node['label'].to_s.gsub('"', '') end end class StateDrawingLambdaValueTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.states << @state = StateMachine::State.new(@machine, :parked, :value => lambda {}) graph = StateMachine::Graph.new('test') @state.draw(graph) @node = graph.get_node('parked') end def test_should_have_a_node assert_not_nil @node end def test_should_use_description_as_label assert_equal 'parked (*)', @node['label'].to_s.gsub('"', '') end end class StateDrawingNonFinalTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.states << @state = StateMachine::State.new(@machine, :parked) @machine.event :ignite do transition :parked => :idling end graph = StateMachine::Graph.new('test') @state.draw(graph) @node = graph.get_node('parked') end def test_should_use_ellipse_as_shape assert_equal 'ellipse', @node['shape'].to_s.gsub('"', '') end end class StateDrawingFinalTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.states << @state = StateMachine::State.new(@machine, :parked) graph = StateMachine::Graph.new('test') @state.draw(graph) @node = graph.get_node('parked') end def test_should_use_doublecircle_as_shape assert_equal 'doublecircle', @node['shape'].to_s.gsub('"', '') end end class StateDrawingWithHumanNameTest < Test::Unit::TestCase def setup @machine = StateMachine::Machine.new(Class.new) @machine.states << @state = StateMachine::State.new(@machine, :parked, :human_name => 'Parked') @machine.event :ignite do transition :parked => :idling end graph = StateMachine::Graph.new('test') @state.draw(graph, :human_name => true) @node = graph.get_node('parked') end def test_should_use_description_with_human_name_as_label assert_equal 'Parked', @node['label'].to_s.gsub('"', '') end end rescue LoadError $stderr.puts 'Skipping GraphViz StateMachine::State tests. `gem install ruby-graphviz` >= v0.9.17 and try again.' end unless ENV['TRAVIS'] state-machine-1.2.0/test/unit/matcher_helpers_test.rb0000644000175000017500000000145412305405267022311 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class MatcherHelpersAllTest < Test::Unit::TestCase include StateMachine::MatcherHelpers def setup @matcher = all end def test_should_build_an_all_matcher assert_equal StateMachine::AllMatcher.instance, @matcher end end class MatcherHelpersAnyTest < Test::Unit::TestCase include StateMachine::MatcherHelpers def setup @matcher = any end def test_should_build_an_all_matcher assert_equal StateMachine::AllMatcher.instance, @matcher end end class MatcherHelpersSameTest < Test::Unit::TestCase include StateMachine::MatcherHelpers def setup @matcher = same end def test_should_build_a_loopback_matcher assert_equal StateMachine::LoopbackMatcher.instance, @matcher end end state-machine-1.2.0/test/unit/path_test.rb0000644000175000017500000003237512305405267020106 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class PathByDefaultTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @object = @klass.new @path = StateMachine::Path.new(@object, @machine) end def test_should_have_an_object assert_equal @object, @path.object end def test_should_have_a_machine assert_equal @machine, @path.machine end def test_should_not_have_walked_anywhere assert_equal [], @path end def test_should_not_have_a_from_name assert_nil @path.from_name end def test_should_have_no_from_states assert_equal [], @path.from_states end def test_should_not_have_a_to_name assert_nil @path.to_name end def test_should_have_no_to_states assert_equal [], @path.to_states end def test_should_have_no_events assert_equal [], @path.events end def test_should_not_be_able_to_walk_anywhere walked = false @path.walk { walked = true } assert_equal false, walked end def test_should_not_be_complete assert_equal false, @path.complete? end end class PathTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @object = @klass.new end def test_should_raise_exception_if_invalid_option_specified exception = assert_raise(ArgumentError) {StateMachine::Path.new(@object, @machine, :invalid => true)} assert_equal 'Invalid key(s): invalid', exception.message end end class PathWithoutTransitionsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @path = StateMachine::Path.new(@object, @machine) @path.concat([ @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) end def test_should_not_be_able_to_walk_anywhere walked = false @path.walk { walked = true } assert_equal false, walked end end class PathWithTransitionsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling, :first_gear @machine.event :ignite, :shift_up @object = @klass.new @object.state = 'parked' @path = StateMachine::Path.new(@object, @machine) @path.concat([ @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling), @shift_up_transition = StateMachine::Transition.new(@object, @machine, :shift_up, :idling, :first_gear) ]) end def test_should_enumerate_transitions assert_equal [@ignite_transition, @shift_up_transition], @path end def test_should_have_a_from_name assert_equal :parked, @path.from_name end def test_should_have_from_states assert_equal [:parked, :idling], @path.from_states end def test_should_have_a_to_name assert_equal :first_gear, @path.to_name end def test_should_have_to_states assert_equal [:idling, :first_gear], @path.to_states end def test_should_have_events assert_equal [:ignite, :shift_up], @path.events end def test_should_not_be_able_to_walk_anywhere walked = false @path.walk { walked = true } assert_equal false, walked end def test_should_be_complete assert_equal true, @path.complete? end end class PathWithDuplicatesTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :park, :ignite @object = @klass.new @object.state = 'parked' @path = StateMachine::Path.new(@object, @machine) @path.concat([ @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling), @park_transition = StateMachine::Transition.new(@object, @machine, :park, :idling, :parked), @ignite_again_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) end def test_should_not_include_duplicates_in_from_states assert_equal [:parked, :idling], @path.from_states end def test_should_not_include_duplicates_in_to_states assert_equal [:idling, :parked], @path.to_states end def test_should_not_include_duplicates_in_events assert_equal [:ignite, :park], @path.events end end class PathWithAvailableTransitionsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling, :first_gear @machine.event :ignite @machine.event :shift_up do transition :idling => :first_gear end @machine.event :park do transition :idling => :parked end @object = @klass.new @object.state = 'parked' @path = StateMachine::Path.new(@object, @machine) @path.concat([ @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) end def test_should_not_be_complete assert !@path.complete? end def test_should_walk_each_available_transition paths = [] @path.walk {|path| paths << path} assert_equal [ [@ignite_transition, StateMachine::Transition.new(@object, @machine, :shift_up, :idling, :first_gear)], [@ignite_transition, StateMachine::Transition.new(@object, @machine, :park, :idling, :parked)] ], paths end def test_should_yield_path_instances_when_walking @path.walk do |path| assert_instance_of StateMachine::Path, path end end def test_should_not_modify_current_path_after_walking @path.walk {} assert_equal [@ignite_transition], @path end def test_should_not_modify_object_after_walking @path.walk {} assert_equal 'parked', @object.state end end class PathWithGuardedTransitionsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @machine.event :shift_up do transition :idling => :first_gear, :if => lambda {false} end @object = @klass.new @object.state = 'parked' end def test_should_not_walk_transitions_if_guard_enabled path = StateMachine::Path.new(@object, @machine) path.concat([ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) paths = [] path.walk {|next_path| paths << next_path} assert_equal [], paths end def test_should_not_walk_transitions_if_guard_disabled path = StateMachine::Path.new(@object, @machine, :guard => false) path.concat([ ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) paths = [] path.walk {|next_path| paths << next_path} assert_equal [ [ignite_transition, StateMachine::Transition.new(@object, @machine, :shift_up, :idling, :first_gear)] ], paths end end class PathWithEncounteredTransitionsTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling, :first_gear @machine.event :ignite do transition :parked => :idling end @machine.event :park do transition :idling => :parked end @object = @klass.new @object.state = 'parked' @path = StateMachine::Path.new(@object, @machine) @path.concat([ @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling), @park_transition = StateMachine::Transition.new(@object, @machine, :park, :idling, :parked) ]) end def test_should_be_complete assert_equal true, @path.complete? end def test_should_not_be_able_to_walk walked = false @path.walk { walked = true } assert_equal false, walked end end class PathWithUnreachedTargetTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite do transition :parked => :idling end @object = @klass.new @object.state = 'parked' @path = StateMachine::Path.new(@object, @machine, :target => :parked) @path.concat([ @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) ]) end def test_should_not_be_complete assert_equal false, @path.complete? end def test_should_not_be_able_to_walk walked = false @path.walk { walked = true } assert_equal false, walked end end class PathWithReachedTargetTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite do transition :parked => :idling end @machine.event :park do transition :idling => :parked end @object = @klass.new @object.state = 'parked' @path = StateMachine::Path.new(@object, @machine, :target => :parked) @path.concat([ @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling), @park_transition = StateMachine::Transition.new(@object, @machine, :park, :idling, :parked) ]) end def test_should_be_complete assert_equal true, @path.complete? end def test_should_not_be_able_to_walk walked = false @path.walk { walked = true } assert_equal false, walked end end class PathWithAvailableTransitionsAfterReachingTargetTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite do transition :parked => :idling end @machine.event :shift_up do transition :parked => :first_gear end @machine.event :park do transition [:idling, :first_gear] => :parked end @object = @klass.new @object.state = 'parked' @path = StateMachine::Path.new(@object, @machine, :target => :parked) @path.concat([ @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling), @park_transition = StateMachine::Transition.new(@object, @machine, :park, :idling, :parked) ]) end def test_should_be_complete assert_equal true, @path.complete? end def test_should_be_able_to_walk paths = [] @path.walk {|path| paths << path} assert_equal [ [@ignite_transition, @park_transition, StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :first_gear)] ], paths end end class PathWithDeepTargetTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite do transition :parked => :idling end @machine.event :shift_up do transition :parked => :first_gear end @machine.event :park do transition [:idling, :first_gear] => :parked end @object = @klass.new @object.state = 'parked' @path = StateMachine::Path.new(@object, @machine, :target => :parked) @path.concat([ @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling), @park_transition = StateMachine::Transition.new(@object, @machine, :park, :idling, :parked), @shift_up_transition = StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :first_gear) ]) end def test_should_not_be_complete assert_equal false, @path.complete? end def test_should_be_able_to_walk paths = [] @path.walk {|path| paths << path} assert_equal [ [@ignite_transition, @park_transition, @shift_up_transition, StateMachine::Transition.new(@object, @machine, :park, :first_gear, :parked)] ], paths end end class PathWithDeepTargetReachedTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite do transition :parked => :idling end @machine.event :shift_up do transition :parked => :first_gear end @machine.event :park do transition [:idling, :first_gear] => :parked end @object = @klass.new @object.state = 'parked' @path = StateMachine::Path.new(@object, @machine, :target => :parked) @path.concat([ @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling), @park_transition = StateMachine::Transition.new(@object, @machine, :park, :idling, :parked), @shift_up_transition = StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :first_gear), @park_transition_2 = StateMachine::Transition.new(@object, @machine, :park, :first_gear, :parked) ]) end def test_should_be_complete assert_equal true, @path.complete? end def test_should_not_be_able_to_walk walked = false @path.walk { walked = true } assert_equal false, walked end def test_should_not_be_able_to_walk_with_available_transitions @machine.event :park do transition :parked => same end walked = false @path.walk { walked = true } assert_equal false, walked end end state-machine-1.2.0/test/test_helper.rb0000644000175000017500000000020412305405267017434 0ustar boutilboutilif ENV['COVERAGE'] require 'simplecov' SimpleCov.start { add_filter '/test/' } end require 'test/unit' require 'state_machine' state-machine-1.2.0/test/functional/0000755000175000017500000000000012305405267016737 5ustar boutilboutilstate-machine-1.2.0/test/functional/state_machine_test.rb0000644000175000017500000005545012305405267023140 0ustar boutilboutilrequire File.expand_path(File.dirname(__FILE__) + '/../test_helper') class AutoShop attr_accessor :num_customers def initialize @num_customers = 0 super end state_machine :initial => :available do after_transition :available => any, :do => :increment_customers after_transition :busy => any, :do => :decrement_customers event :tow_vehicle do transition :available => :busy end event :fix_vehicle do transition :busy => :available end end # Increments the number of customers in service def increment_customers self.num_customers += 1 end # Decrements the number of customers in service def decrement_customers self.num_customers -= 1 end end class ModelBase def save @saved = true self end end class Vehicle < ModelBase attr_accessor :auto_shop, :seatbelt_on, :insurance_premium, :force_idle, :callbacks, :saved, :time_elapsed, :last_transition_args def initialize(attributes = {}) attributes = { :auto_shop => AutoShop.new, :seatbelt_on => false, :insurance_premium => 50, :force_idle => false, :callbacks => [], :saved => false }.merge(attributes) attributes.each {|attr, value| send("#{attr}=", value)} super() end # Defines the state machine for the state of the vehicled state_machine :initial => lambda {|vehicle| vehicle.force_idle ? :idling : :parked}, :action => :save do before_transition {|vehicle, transition| vehicle.last_transition_args = transition.args} before_transition :parked => any, :do => :put_on_seatbelt before_transition any => :stalled, :do => :increase_insurance_premium after_transition any => :parked, :do => lambda {|vehicle| vehicle.seatbelt_on = false} after_transition :on => :crash, :do => :tow after_transition :on => :repair, :do => :fix # Callback tracking for initial state callbacks after_transition any => :parked, :do => lambda {|vehicle| vehicle.callbacks << 'before_enter_parked'} before_transition any => :idling, :do => lambda {|vehicle| vehicle.callbacks << 'before_enter_idling'} around_transition do |vehicle, transition, block| time = Time.now block.call vehicle.time_elapsed = Time.now - time end event all do transition :locked => :parked end event :park do transition [:idling, :first_gear] => :parked end event :ignite do transition :stalled => :stalled transition :parked => :idling end event :idle do transition :first_gear => :idling end event :shift_up do transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear end event :shift_down do transition :third_gear => :second_gear transition :second_gear => :first_gear end event :crash do transition [:first_gear, :second_gear, :third_gear] => :stalled, :if => lambda {|vehicle| vehicle.auto_shop.available?} end event :repair do transition :stalled => :parked, :if => :auto_shop_busy? end end state_machine :insurance_state, :initial => :inactive, :namespace => 'insurance' do event :buy do transition :inactive => :active end event :cancel do transition :active => :inactive end end def save super end def new_record? @saved == false end def park super end # Tows the vehicle to the auto shop def tow auto_shop.tow_vehicle end # Fixes the vehicle; it will no longer be in the auto shop def fix auto_shop.fix_vehicle end def decibels 0.0 end private # Safety first! Puts on our seatbelt def put_on_seatbelt self.seatbelt_on = true end # We crashed! Increase the insurance premium on the vehicle def increase_insurance_premium self.insurance_premium += 100 end # Is the auto shop currently servicing another customer? def auto_shop_busy? auto_shop.busy? end end class Car < Vehicle state_machine do event :reverse do transition [:parked, :idling, :first_gear] => :backing_up end event :park do transition :backing_up => :parked end event :idle do transition :backing_up => :idling end event :shift_up do transition :backing_up => :first_gear end end end class Motorcycle < Vehicle state_machine :initial => :idling do state :first_gear do def decibels 1.0 end end end end class TrafficLight state_machine :initial => :stop do event :cycle do transition :stop => :proceed, :proceed => :caution, :caution => :stop end state :stop do def color(transform) value = 'red' if block_given? yield value else value.send(transform) end value end end state all - :proceed do def capture_violations? true end end state :proceed do def color(transform) 'green' end def capture_violations? false end end state :caution do def color(transform) 'yellow' end end end def color(transform = :to_s) super end end class VehicleTest < Test::Unit::TestCase def setup @vehicle = Vehicle.new end def test_should_not_allow_access_to_subclass_events assert !@vehicle.respond_to?(:reverse) end def test_should_have_human_state_names assert_equal 'parked', Vehicle.human_state_name(:parked) end def test_should_have_human_state_event_names assert_equal 'park', Vehicle.human_state_event_name(:park) end end class VehicleUnsavedTest < Test::Unit::TestCase def setup @vehicle = Vehicle.new end def test_should_be_in_parked_state assert_equal 'parked', @vehicle.state end def test_should_raise_exception_if_checking_invalid_state assert_raise(IndexError) { @vehicle.state?(:invalid) } end def test_should_raise_exception_if_getting_name_of_invalid_state @vehicle.state = 'invalid' assert_raise(ArgumentError) { @vehicle.state_name } end def test_should_be_parked assert @vehicle.parked? assert @vehicle.state?(:parked) assert_equal :parked, @vehicle.state_name assert_equal 'parked', @vehicle.human_state_name end def test_should_not_be_idling assert !@vehicle.idling? end def test_should_not_be_first_gear assert !@vehicle.first_gear? end def test_should_not_be_second_gear assert !@vehicle.second_gear? end def test_should_not_be_stalled assert !@vehicle.stalled? end def test_should_not_be_able_to_park assert !@vehicle.can_park? end def test_should_not_have_a_transition_for_park assert_nil @vehicle.park_transition end def test_should_not_allow_park assert !@vehicle.park end def test_should_be_able_to_ignite assert @vehicle.can_ignite? end def test_should_have_a_transition_for_ignite transition = @vehicle.ignite_transition assert_not_nil transition assert_equal 'parked', transition.from assert_equal 'idling', transition.to assert_equal :ignite, transition.event assert_equal :state, transition.attribute assert_equal @vehicle, transition.object end def test_should_have_a_list_of_possible_events assert_equal [:ignite], @vehicle.state_events end def test_should_have_a_list_of_possible_transitions assert_equal [{:object => @vehicle, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'}], @vehicle.state_transitions.map {|transition| transition.attributes} end def test_should_have_a_list_of_possible_paths assert_equal [[ StateMachine::Transition.new(@vehicle, Vehicle.state_machine, :ignite, :parked, :idling), StateMachine::Transition.new(@vehicle, Vehicle.state_machine, :shift_up, :idling, :first_gear) ]], @vehicle.state_paths(:to => :first_gear) end def test_should_allow_generic_event_to_fire assert @vehicle.fire_state_event(:ignite) assert_equal 'idling', @vehicle.state end def test_should_pass_arguments_through_to_generic_event_runner @vehicle.fire_state_event(:ignite, 1, 2, 3) assert_equal [1, 2, 3], @vehicle.last_transition_args end def test_should_allow_skipping_action_through_generic_event_runner @vehicle.fire_state_event(:ignite, false) assert_equal false, @vehicle.saved end def test_should_raise_error_with_invalid_event_through_generic_event_runer assert_raise(IndexError) { @vehicle.fire_state_event(:invalid) } end def test_should_allow_ignite assert @vehicle.ignite assert_equal 'idling', @vehicle.state end def test_should_allow_ignite_with_skipped_action assert @vehicle.ignite(false) assert @vehicle.new_record? end def test_should_allow_ignite_bang assert @vehicle.ignite! end def test_should_allow_ignite_bang_with_skipped_action assert @vehicle.ignite!(false) assert @vehicle.new_record? end def test_should_be_saved_after_successful_event @vehicle.ignite assert !@vehicle.new_record? end def test_should_not_allow_idle assert !@vehicle.idle end def test_should_not_allow_shift_up assert !@vehicle.shift_up end def test_should_not_allow_shift_down assert !@vehicle.shift_down end def test_should_not_allow_crash assert !@vehicle.crash end def test_should_not_allow_repair assert !@vehicle.repair end def test_should_be_insurance_inactive assert @vehicle.insurance_inactive? end def test_should_be_able_to_buy assert @vehicle.can_buy_insurance? end def test_should_allow_buying_insurance assert @vehicle.buy_insurance end def test_should_allow_buying_insurance_bang assert @vehicle.buy_insurance! end def test_should_allow_ignite_buying_insurance_with_skipped_action assert @vehicle.buy_insurance!(false) assert @vehicle.new_record? end def test_should_not_be_insurance_active assert !@vehicle.insurance_active? end def test_should_not_be_able_to_cancel assert !@vehicle.can_cancel_insurance? end def test_should_not_allow_cancelling_insurance assert !@vehicle.cancel_insurance end end class VehicleParkedTest < Test::Unit::TestCase def setup @vehicle = Vehicle.new end def test_should_be_in_parked_state assert_equal 'parked', @vehicle.state end def test_should_not_have_the_seatbelt_on assert !@vehicle.seatbelt_on end def test_should_not_allow_park assert !@vehicle.park end def test_should_allow_ignite assert @vehicle.ignite assert_equal 'idling', @vehicle.state end def test_should_not_allow_idle assert !@vehicle.idle end def test_should_not_allow_shift_up assert !@vehicle.shift_up end def test_should_not_allow_shift_down assert !@vehicle.shift_down end def test_should_not_allow_crash assert !@vehicle.crash end def test_should_not_allow_repair assert !@vehicle.repair end def test_should_raise_exception_if_repair_not_allowed! exception = assert_raise(StateMachine::InvalidTransition) {@vehicle.repair!} assert_equal @vehicle, exception.object assert_equal Vehicle.state_machine(:state), exception.machine assert_equal :repair, exception.event assert_equal 'parked', exception.from end end class VehicleIdlingTest < Test::Unit::TestCase def setup @vehicle = Vehicle.new @vehicle.ignite end def test_should_be_in_idling_state assert_equal 'idling', @vehicle.state end def test_should_be_idling assert @vehicle.idling? end def test_should_have_seatbelt_on assert @vehicle.seatbelt_on end def test_should_track_time_elapsed assert_not_nil @vehicle.time_elapsed end def test_should_allow_park assert @vehicle.park end def test_should_call_park_with_bang_action class << @vehicle def park super && 1 end end assert_equal 1, @vehicle.park! end def test_should_not_allow_idle assert !@vehicle.idle end def test_should_allow_shift_up assert @vehicle.shift_up end def test_should_not_allow_shift_down assert !@vehicle.shift_down end def test_should_not_allow_crash assert !@vehicle.crash end def test_should_not_allow_repair assert !@vehicle.repair end end class VehicleFirstGearTest < Test::Unit::TestCase def setup @vehicle = Vehicle.new @vehicle.ignite @vehicle.shift_up end def test_should_be_in_first_gear_state assert_equal 'first_gear', @vehicle.state end def test_should_be_first_gear assert @vehicle.first_gear? end def test_should_allow_park assert @vehicle.park end def test_should_allow_idle assert @vehicle.idle end def test_should_allow_shift_up assert @vehicle.shift_up end def test_should_not_allow_shift_down assert !@vehicle.shift_down end def test_should_allow_crash assert @vehicle.crash end def test_should_not_allow_repair assert !@vehicle.repair end end class VehicleSecondGearTest < Test::Unit::TestCase def setup @vehicle = Vehicle.new @vehicle.ignite 2.times {@vehicle.shift_up} end def test_should_be_in_second_gear_state assert_equal 'second_gear', @vehicle.state end def test_should_be_second_gear assert @vehicle.second_gear? end def test_should_not_allow_park assert !@vehicle.park end def test_should_not_allow_idle assert !@vehicle.idle end def test_should_allow_shift_up assert @vehicle.shift_up end def test_should_allow_shift_down assert @vehicle.shift_down end def test_should_allow_crash assert @vehicle.crash end def test_should_not_allow_repair assert !@vehicle.repair end end class VehicleThirdGearTest < Test::Unit::TestCase def setup @vehicle = Vehicle.new @vehicle.ignite 3.times {@vehicle.shift_up} end def test_should_be_in_third_gear_state assert_equal 'third_gear', @vehicle.state end def test_should_be_third_gear assert @vehicle.third_gear? end def test_should_not_allow_park assert !@vehicle.park end def test_should_not_allow_idle assert !@vehicle.idle end def test_should_not_allow_shift_up assert !@vehicle.shift_up end def test_should_allow_shift_down assert @vehicle.shift_down end def test_should_allow_crash assert @vehicle.crash end def test_should_not_allow_repair assert !@vehicle.repair end end class VehicleStalledTest < Test::Unit::TestCase def setup @vehicle = Vehicle.new @vehicle.ignite @vehicle.shift_up @vehicle.crash end def test_should_be_in_stalled_state assert_equal 'stalled', @vehicle.state end def test_should_be_stalled assert @vehicle.stalled? end def test_should_be_towed assert @vehicle.auto_shop.busy? assert_equal 1, @vehicle.auto_shop.num_customers end def test_should_have_an_increased_insurance_premium assert_equal 150, @vehicle.insurance_premium end def test_should_not_allow_park assert !@vehicle.park end def test_should_allow_ignite assert @vehicle.ignite end def test_should_not_change_state_when_ignited assert_equal 'stalled', @vehicle.state end def test_should_not_allow_idle assert !@vehicle.idle end def test_should_now_allow_shift_up assert !@vehicle.shift_up end def test_should_not_allow_shift_down assert !@vehicle.shift_down end def test_should_not_allow_crash assert !@vehicle.crash end def test_should_allow_repair_if_auto_shop_is_busy assert @vehicle.repair end def test_should_not_allow_repair_if_auto_shop_is_available @vehicle.auto_shop.fix_vehicle assert !@vehicle.repair end end class VehicleRepairedTest < Test::Unit::TestCase def setup @vehicle = Vehicle.new @vehicle.ignite @vehicle.shift_up @vehicle.crash @vehicle.repair end def test_should_be_in_parked_state assert_equal 'parked', @vehicle.state end def test_should_not_have_a_busy_auto_shop assert @vehicle.auto_shop.available? end end class VehicleLockedTest < Test::Unit::TestCase def setup @vehicle = Vehicle.new @vehicle.state = 'locked' end def test_should_be_parked_after_park @vehicle.park assert @vehicle.parked? end def test_should_be_parked_after_ignite @vehicle.ignite assert @vehicle.parked? end def test_should_be_parked_after_shift_up @vehicle.shift_up assert @vehicle.parked? end def test_should_be_parked_after_shift_down @vehicle.shift_down assert @vehicle.parked? end end class VehicleWithParallelEventsTest < Test::Unit::TestCase def setup @vehicle = Vehicle.new end def test_should_fail_if_any_event_cannot_transition assert !@vehicle.fire_events(:ignite, :cancel_insurance) end def test_should_be_successful_if_all_events_transition assert @vehicle.fire_events(:ignite, :buy_insurance) end def test_should_not_save_if_skipping_action assert @vehicle.fire_events(:ignite, :buy_insurance, false) assert !@vehicle.saved end def test_should_raise_exception_if_any_event_cannot_transition_on_bang exception = assert_raise(StateMachine::InvalidParallelTransition) { @vehicle.fire_events!(:ignite, :cancel_insurance) } assert_equal @vehicle, exception.object assert_equal [:ignite, :cancel_insurance], exception.events end def test_should_not_raise_exception_if_all_events_transition_on_bang assert @vehicle.fire_events!(:ignite, :buy_insurance) end def test_should_not_save_if_skipping_action_on_bang assert @vehicle.fire_events!(:ignite, :buy_insurance, false) assert !@vehicle.saved end end class VehicleWithEventAttributesTest < Test::Unit::TestCase def setup @vehicle = Vehicle.new @vehicle.state_event = 'ignite' end def test_should_fail_if_event_is_invalid @vehicle.state_event = 'invalid' assert !@vehicle.save assert_equal 'parked', @vehicle.state end def test_should_fail_if_event_has_no_transition @vehicle.state_event = 'park' assert !@vehicle.save assert_equal 'parked', @vehicle.state end def test_should_return_original_action_value_on_success assert_equal @vehicle, @vehicle.save end def test_should_transition_state_on_success @vehicle.save assert_equal 'idling', @vehicle.state end end class MotorcycleTest < Test::Unit::TestCase def setup @motorcycle = Motorcycle.new end def test_should_be_in_idling_state assert_equal 'idling', @motorcycle.state end def test_should_allow_park assert @motorcycle.park end def test_should_not_allow_ignite assert !@motorcycle.ignite end def test_should_allow_shift_up assert @motorcycle.shift_up end def test_should_not_allow_shift_down assert !@motorcycle.shift_down end def test_should_not_allow_crash assert !@motorcycle.crash end def test_should_not_allow_repair assert !@motorcycle.repair end def test_should_inherit_decibels_from_superclass @motorcycle.park assert_equal 0.0, @motorcycle.decibels end def test_should_use_decibels_defined_in_state @motorcycle.shift_up assert_equal 1.0, @motorcycle.decibels end end class CarTest < Test::Unit::TestCase def setup @car = Car.new end def test_should_be_in_parked_state assert_equal 'parked', @car.state end def test_should_not_have_the_seatbelt_on assert !@car.seatbelt_on end def test_should_not_allow_park assert !@car.park end def test_should_allow_ignite assert @car.ignite assert_equal 'idling', @car.state end def test_should_not_allow_idle assert !@car.idle end def test_should_not_allow_shift_up assert !@car.shift_up end def test_should_not_allow_shift_down assert !@car.shift_down end def test_should_not_allow_crash assert !@car.crash end def test_should_not_allow_repair assert !@car.repair end def test_should_allow_reverse assert @car.reverse end end class CarBackingUpTest < Test::Unit::TestCase def setup @car = Car.new @car.reverse end def test_should_be_in_backing_up_state assert_equal 'backing_up', @car.state end def test_should_allow_park assert @car.park end def test_should_not_allow_ignite assert !@car.ignite end def test_should_allow_idle assert @car.idle end def test_should_allow_shift_up assert @car.shift_up end def test_should_not_allow_shift_down assert !@car.shift_down end def test_should_not_allow_crash assert !@car.crash end def test_should_not_allow_repair assert !@car.repair end def test_should_not_allow_reverse assert !@car.reverse end end class AutoShopAvailableTest < Test::Unit::TestCase def setup @auto_shop = AutoShop.new end def test_should_be_in_available_state assert_equal 'available', @auto_shop.state end def test_should_allow_tow_vehicle assert @auto_shop.tow_vehicle end def test_should_not_allow_fix_vehicle assert !@auto_shop.fix_vehicle end end class AutoShopBusyTest < Test::Unit::TestCase def setup @auto_shop = AutoShop.new @auto_shop.tow_vehicle end def test_should_be_in_busy_state assert_equal 'busy', @auto_shop.state end def test_should_have_incremented_number_of_customers assert_equal 1, @auto_shop.num_customers end def test_should_not_allow_tow_vehicle assert !@auto_shop.tow_vehicle end def test_should_allow_fix_vehicle assert @auto_shop.fix_vehicle end end class TrafficLightStopTest < Test::Unit::TestCase def setup @light = TrafficLight.new @light.state = 'stop' end def test_should_use_stop_color assert_equal 'red', @light.color end def test_should_pass_arguments_through assert_equal 'RED', @light.color(:upcase!) end def test_should_pass_block_through color = @light.color {|value| value.upcase!} assert_equal 'RED', color end def test_should_use_stop_capture_violations assert_equal true, @light.capture_violations? end end class TrafficLightProceedTest < Test::Unit::TestCase def setup @light = TrafficLight.new @light.state = 'proceed' end def test_should_use_proceed_color assert_equal 'green', @light.color end def test_should_use_proceed_capture_violations assert_equal false, @light.capture_violations? end end class TrafficLightCautionTest < Test::Unit::TestCase def setup @light = TrafficLight.new @light.state = 'caution' end def test_should_use_caution_color assert_equal 'yellow', @light.color end def test_should_use_caution_capture_violations assert_equal true, @light.capture_violations? end end state-machine-1.2.0/.travis.yml0000644000175000017500000000341512305405267015732 0ustar boutilboutillanguage: ruby script: "bundle exec rake appraisal:integration test" rvm: - 1.8.7 - 1.9.2 - 1.9.3 - jruby-18mode - jruby-19mode - rbx-18mode - rbx-19mode env: - INTEGRATION=default - INTEGRATION=active_model - INTEGRATION=active_record - INTEGRATION=data_mapper - INTEGRATION=mongoid - INTEGRATION=mongo_mapper - INTEGRATION=sequel services: - mongodb notifications: disabled: true matrix: exclude: - rvm: jruby-18mode env: INTEGRATION=active_model - rvm: jruby-18mode env: INTEGRATION=active_record - rvm: jruby-18mode env: INTEGRATION=data_mapper - rvm: jruby-18mode env: INTEGRATION=mongoid - rvm: jruby-18mode env: INTEGRATION=mongo_mapper - rvm: jruby-18mode env: INTEGRATION=sequel - rvm: jruby-19mode env: INTEGRATION=active_model - rvm: jruby-19mode env: INTEGRATION=active_record - rvm: jruby-19mode env: INTEGRATION=data_mapper - rvm: jruby-19mode env: INTEGRATION=mongoid - rvm: jruby-19mode env: INTEGRATION=mongo_mapper - rvm: jruby-19mode env: INTEGRATION=sequel - rvm: rbx-18mode env: INTEGRATION=active_model - rvm: rbx-18mode env: INTEGRATION=active_record - rvm: rbx-18mode env: INTEGRATION=data_mapper - rvm: rbx-18mode env: INTEGRATION=mongoid - rvm: rbx-18mode env: INTEGRATION=mongo_mapper - rvm: rbx-18mode env: INTEGRATION=sequel - rvm: rbx-19mode env: INTEGRATION=active_model - rvm: rbx-19mode env: INTEGRATION=active_record - rvm: rbx-19mode env: INTEGRATION=data_mapper - rvm: rbx-19mode env: INTEGRATION=mongoid - rvm: rbx-19mode env: INTEGRATION=mongo_mapper - rvm: rbx-19mode env: INTEGRATION=sequel state-machine-1.2.0/LICENSE0000644000175000017500000000204612305405267014625 0ustar boutilboutilCopyright (c) 2006-2012 Aaron Pfeifer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. state-machine-1.2.0/README.md0000644000175000017500000010640712305405267015105 0ustar boutilboutil# state_machine [![Build Status](https://secure.travis-ci.org/pluginaweek/state_machine.png "Build Status")](http://travis-ci.org/pluginaweek/state_machine) [![Dependency Status](https://gemnasium.com/pluginaweek/state_machine.png "Dependency Status")](https://gemnasium.com/pluginaweek/state_machine) *state_machine* adds support for creating state machines for attributes on any Ruby class. ## Resources API * http://rdoc.info/github/pluginaweek/state_machine/master/frames Bugs * http://github.com/pluginaweek/state_machine/issues Development * http://github.com/pluginaweek/state_machine Testing * http://travis-ci.org/pluginaweek/state_machine Source * git://github.com/pluginaweek/state_machine.git Mailing List * http://groups.google.com/group/pluginaweek-talk ## Description State machines make it dead-simple to manage the behavior of a class. Too often, the state of an object is kept by creating multiple boolean attributes and deciding how to behave based on the values. This can become cumbersome and difficult to maintain when the complexity of your class starts to increase. *state_machine* simplifies this design by introducing the various parts of a real state machine, including states, events, transitions, and callbacks. However, the api is designed to be so simple you don't even need to know what a state machine is :) Some brief, high-level features include: * Defining state machines on any Ruby class * Multiple state machines on a single class * Namespaced state machines * before/after/around/failure transition hooks with explicit transition requirements * Integration with ActiveModel, ActiveRecord, DataMapper, Mongoid, MongoMapper, and Sequel * State predicates * State-driven instance / class behavior * State values of any data type * Dynamically-generated state values * Event parallelization * Attribute-based event transitions * Path analysis * Inheritance * Internationalization * GraphViz visualization creator * YARD integration (Ruby 1.9+ only) * Flexible machine syntax Examples of the usage patterns for some of the above features are shown below. You can find much more detailed documentation in the actual API. ## Usage ### Example Below is an example of many of the features offered by this plugin, including: * Initial states * Namespaced states * Transition callbacks * Conditional transitions * State-driven instance behavior * Customized state values * Parallel events * Path analysis Class definition: ```ruby class Vehicle attr_accessor :seatbelt_on, :time_used, :auto_shop_busy state_machine :state, :initial => :parked do before_transition :parked => any - :parked, :do => :put_on_seatbelt after_transition :on => :crash, :do => :tow after_transition :on => :repair, :do => :fix after_transition any => :parked do |vehicle, transition| vehicle.seatbelt_on = false end after_failure :on => :ignite, :do => :log_start_failure around_transition do |vehicle, transition, block| start = Time.now block.call vehicle.time_used += Time.now - start end event :park do transition [:idling, :first_gear] => :parked end event :ignite do transition :stalled => same, :parked => :idling end event :idle do transition :first_gear => :idling end event :shift_up do transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear end event :shift_down do transition :third_gear => :second_gear, :second_gear => :first_gear end event :crash do transition all - [:parked, :stalled] => :stalled, :if => lambda {|vehicle| !vehicle.passed_inspection?} end event :repair do # The first transition that matches the state and passes its conditions # will be used transition :stalled => :parked, :unless => :auto_shop_busy transition :stalled => same end state :parked do def speed 0 end end state :idling, :first_gear do def speed 10 end end state all - [:parked, :stalled, :idling] do def moving? true end end state :parked, :stalled, :idling do def moving? false end end end state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do event :enable do transition all => :active end event :disable do transition all => :off end state :active, :value => 1 state :off, :value => 0 end def initialize @seatbelt_on = false @time_used = 0 @auto_shop_busy = true super() # NOTE: This *must* be called, otherwise states won't get initialized end def put_on_seatbelt @seatbelt_on = true end def passed_inspection? false end def tow # tow the vehicle end def fix # get the vehicle fixed by a mechanic end def log_start_failure # log a failed attempt to start the vehicle end end ``` **Note** the comment made on the `initialize` method in the class. In order for state machine attributes to be properly initialized, `super()` must be called. See `StateMachine::MacroMethods` for more information about this. Using the above class as an example, you can interact with the state machine like so: ```ruby vehicle = Vehicle.new # => # vehicle.state # => "parked" vehicle.state_name # => :parked vehicle.human_state_name # => "parked" vehicle.parked? # => true vehicle.can_ignite? # => true vehicle.ignite_transition # => # vehicle.state_events # => [:ignite] vehicle.state_transitions # => [#] vehicle.speed # => 0 vehicle.moving? # => false vehicle.ignite # => true vehicle.parked? # => false vehicle.idling? # => true vehicle.speed # => 10 vehicle # => # vehicle.shift_up # => true vehicle.speed # => 10 vehicle.moving? # => true vehicle # => # # A generic event helper is available to fire without going through the event's instance method vehicle.fire_state_event(:shift_up) # => true # Call state-driven behavior that's undefined for the state raises a NoMethodError vehicle.speed # => NoMethodError: super: no superclass method `speed' for # vehicle # => # # The bang (!) operator can raise exceptions if the event fails vehicle.park! # => StateMachine::InvalidTransition: Cannot transition state via :park from :second_gear # Generic state predicates can raise exceptions if the value does not exist vehicle.state?(:parked) # => false vehicle.state?(:invalid) # => IndexError: :invalid is an invalid name # Namespaced machines have uniquely-generated methods vehicle.alarm_state # => 1 vehicle.alarm_state_name # => :active vehicle.can_disable_alarm? # => true vehicle.disable_alarm # => true vehicle.alarm_state # => 0 vehicle.alarm_state_name # => :off vehicle.can_enable_alarm? # => true vehicle.alarm_off? # => true vehicle.alarm_active? # => false # Events can be fired in parallel vehicle.fire_events(:shift_down, :enable_alarm) # => true vehicle.state_name # => :first_gear vehicle.alarm_state_name # => :active vehicle.fire_events!(:ignite, :enable_alarm) # => StateMachine::InvalidTransition: Cannot run events in parallel: ignite, enable_alarm # Human-friendly names can be accessed for states/events Vehicle.human_state_name(:first_gear) # => "first gear" Vehicle.human_alarm_state_name(:active) # => "active" Vehicle.human_state_event_name(:shift_down) # => "shift down" Vehicle.human_alarm_state_event_name(:enable) # => "enable" # States / events can also be references by the string version of their name Vehicle.human_state_name('first_gear') # => "first gear" Vehicle.human_state_event_name('shift_down') # => "shift down" # Available transition paths can be analyzed for an object vehicle.state_paths # => [[# [:parked, :idling, :first_gear, :stalled, :second_gear, :third_gear] vehicle.state_paths.events # => [:park, :ignite, :shift_up, :idle, :crash, :repair, :shift_down] # Find all paths that start and end on certain states vehicle.state_paths(:from => :parked, :to => :first_gear) # => [[ # #, # # # ]] # Skipping state_machine and writing to attributes directly vehicle.state = "parked" vehicle.state # => "parked" vehicle.state_name # => :parked # *Note* that the following is not supported (see StateMachine::MacroMethods#state_machine): # vehicle.state = :parked ``` ## Integrations In addition to being able to define state machines on all Ruby classes, a set of out-of-the-box integrations are available for some of the more popular Ruby libraries. These integrations add library-specific behavior, allowing for state machines to work more tightly with the conventions defined by those libraries. The integrations currently available include: * ActiveModel classes * ActiveRecord models * DataMapper resources * Mongoid models * MongoMapper models * Sequel models A brief overview of these integrations is described below. ### ActiveModel The ActiveModel integration is useful for both standalone usage and for providing the base implementation for ORMs which implement the ActiveModel API. This integration adds support for validation errors, dirty attribute tracking, and observers. For example, ```ruby class Vehicle include ActiveModel::Dirty include ActiveModel::Validations include ActiveModel::Observing attr_accessor :state define_attribute_methods [:state] state_machine :initial => :parked do before_transition :parked => any - :parked, :do => :put_on_seatbelt after_transition any => :parked do |vehicle, transition| vehicle.seatbelt = 'off' end around_transition :benchmark event :ignite do transition :parked => :idling end state :first_gear, :second_gear do validates_presence_of :seatbelt_on end end def put_on_seatbelt ... end def benchmark ... yield ... end end class VehicleObserver < ActiveModel::Observer # Callback for :ignite event *before* the transition is performed def before_ignite(vehicle, transition) # log message end # Generic transition callback *after* the transition is performed def after_transition(vehicle, transition) Audit.log(vehicle, transition) end # Generic callback after the transition fails to perform def after_failure_to_transition(vehicle, transition) Audit.error(vehicle, transition) end end ``` For more information about the various behaviors added for ActiveModel state machines and how to build new integrations that use ActiveModel, see `StateMachine::Integrations::ActiveModel`. ### ActiveRecord The ActiveRecord integration adds support for database transactions, automatically saving the record, named scopes, validation errors, and observers. For example, ```ruby class Vehicle < ActiveRecord::Base state_machine :initial => :parked do before_transition :parked => any - :parked, :do => :put_on_seatbelt after_transition any => :parked do |vehicle, transition| vehicle.seatbelt = 'off' end around_transition :benchmark event :ignite do transition :parked => :idling end state :first_gear, :second_gear do validates_presence_of :seatbelt_on end end def put_on_seatbelt ... end def benchmark ... yield ... end end class VehicleObserver < ActiveRecord::Observer # Callback for :ignite event *before* the transition is performed def before_ignite(vehicle, transition) # log message end # Generic transition callback *after* the transition is performed def after_transition(vehicle, transition) Audit.log(vehicle, transition) end end ``` For more information about the various behaviors added for ActiveRecord state machines, see `StateMachine::Integrations::ActiveRecord`. ### DataMapper Like the ActiveRecord integration, the DataMapper integration adds support for database transactions, automatically saving the record, named scopes, Extlib-like callbacks, validation errors, and observers. For example, ```ruby class Vehicle include DataMapper::Resource property :id, Serial property :state, String state_machine :initial => :parked do before_transition :parked => any - :parked, :do => :put_on_seatbelt after_transition any => :parked do |transition| self.seatbelt = 'off' # self is the record end around_transition :benchmark event :ignite do transition :parked => :idling end state :first_gear, :second_gear do validates_presence_of :seatbelt_on end end def put_on_seatbelt ... end def benchmark ... yield ... end end class VehicleObserver include DataMapper::Observer observe Vehicle # Callback for :ignite event *before* the transition is performed before_transition :on => :ignite do |transition| # log message (self is the record) end # Generic transition callback *after* the transition is performed after_transition do |transition| Audit.log(self, transition) # self is the record end around_transition do |transition, block| # mark start time block.call # mark stop time end # Generic callback after the transition fails to perform after_transition_failure do |transition| Audit.log(self, transition) # self is the record end end ``` **Note** that the DataMapper::Observer integration is optional and only available when the dm-observer library is installed. For more information about the various behaviors added for DataMapper state machines, see `StateMachine::Integrations::DataMapper`. ### Mongoid The Mongoid integration adds support for automatically saving the record, basic scopes, validation errors, and observers. For example, ```ruby class Vehicle include Mongoid::Document state_machine :initial => :parked do before_transition :parked => any - :parked, :do => :put_on_seatbelt after_transition any => :parked do |vehicle, transition| vehicle.seatbelt = 'off' # self is the record end around_transition :benchmark event :ignite do transition :parked => :idling end state :first_gear, :second_gear do validates_presence_of :seatbelt_on end end def put_on_seatbelt ... end def benchmark ... yield ... end end class VehicleObserver < Mongoid::Observer # Callback for :ignite event *before* the transition is performed def before_ignite(vehicle, transition) # log message end # Generic transition callback *after* the transition is performed def after_transition(vehicle, transition) Audit.log(vehicle, transition) end end ``` For more information about the various behaviors added for Mongoid state machines, see `StateMachine::Integrations::Mongoid`. ### MongoMapper The MongoMapper integration adds support for automatically saving the record, basic scopes, validation errors and callbacks. For example, ```ruby class Vehicle include MongoMapper::Document state_machine :initial => :parked do before_transition :parked => any - :parked, :do => :put_on_seatbelt after_transition any => :parked do |vehicle, transition| vehicle.seatbelt = 'off' # self is the record end around_transition :benchmark event :ignite do transition :parked => :idling end state :first_gear, :second_gear do validates_presence_of :seatbelt_on end end def put_on_seatbelt ... end def benchmark ... yield ... end end ``` For more information about the various behaviors added for MongoMapper state machines, see `StateMachine::Integrations::MongoMapper`. ### Sequel Like the ActiveRecord integration, the Sequel integration adds support for database transactions, automatically saving the record, named scopes, validation errors and callbacks. For example, ```ruby class Vehicle < Sequel::Model plugin :validation_class_methods state_machine :initial => :parked do before_transition :parked => any - :parked, :do => :put_on_seatbelt after_transition any => :parked do |transition| self.seatbelt = 'off' # self is the record end around_transition :benchmark event :ignite do transition :parked => :idling end state :first_gear, :second_gear do validates_presence_of :seatbelt_on end end def put_on_seatbelt ... end def benchmark ... yield ... end end ``` For more information about the various behaviors added for Sequel state machines, see `StateMachine::Integrations::Sequel`. ## Additional Topics ### Explicit vs. Implicit Event Transitions Every event defined for a state machine generates an instance method on the class that allows the event to be explicitly triggered. Most of the examples in the state_machine documentation use this technique. However, with some types of integrations, like ActiveRecord, you can also *implicitly* fire events by setting a special attribute on the instance. Suppose you're using the ActiveRecord integration and the following model is defined: ```ruby class Vehicle < ActiveRecord::Base state_machine :initial => :parked do event :ignite do transition :parked => :idling end end end ``` To trigger the `ignite` event, you would typically call the `Vehicle#ignite` method like so: ```ruby vehicle = Vehicle.create # => # vehicle.ignite # => true vehicle.state # => "idling" ``` This is referred to as an *explicit* event transition. The same behavior can also be achieved *implicitly* by setting the state event attribute and invoking the action associated with the state machine. For example: ```ruby vehicle = Vehicle.create # => # vehicle.state_event = "ignite" # => "ignite" vehicle.save # => true vehicle.state # => "idling" vehicle.state_event # => nil ``` As you can see, the `ignite` event was automatically triggered when the `save` action was called. This is particularly useful if you want to allow users to drive the state transitions from a web API. See each integration's API documentation for more information on the implicit approach. ### Symbols vs. Strings In all of the examples used throughout the documentation, you'll notice that states and events are almost always referenced as symbols. This isn't a requirement, but rather a suggested best practice. You can very well define your state machine with Strings like so: ```ruby class Vehicle state_machine :initial => 'parked' do event 'ignite' do transition 'parked' => 'idling' end # ... end end ``` You could even use numbers as your state / event names. The **important** thing to keep in mind is that the type being used for referencing states / events in your machine definition must be **consistent**. If you're using Symbols, then all states / events must use Symbols. Otherwise you'll encounter the following error: ```ruby class Vehicle state_machine do event :ignite do transition :parked => 'idling' end end end # => ArgumentError: "idling" state defined as String, :parked defined as Symbol; all states must be consistent ``` There **is** an exception to this rule. The consistency is only required within the definition itself. However, when the machine's helper methods are called with input from external sources, such as a web form, state_machine will map that input to a String / Symbol. For example: ```ruby class Vehicle state_machine :initial => :parked do event :ignite do transition :parked => :idling end end end v = Vehicle.new # => # v.state?('parked') # => true v.state?(:parked) # => true ``` **Note** that none of this actually has to do with the type of the value that gets stored. By default, all state values are assumed to be string -- regardless of whether the state names are symbols or strings. If you want to store states as symbols instead you'll have to be explicit about it: ```ruby class Vehicle state_machine :initial => :parked do event :ignite do transition :parked => :idling end states.each do |state| self.state(state.name, :value => state.name.to_sym) end end end v = Vehicle.new # => # v.state?('parked') # => true v.state?(:parked) # => true ``` ### Syntax flexibility Although state_machine introduces a simplified syntax, it still remains backwards compatible with previous versions and other state-related libraries by providing some flexibility around how transitions are defined. See below for an overview of these syntaxes. #### Verbose syntax In general, it's recommended that state machines use the implicit syntax for transitions. However, you can be a little more explicit and verbose about transitions by using the `:from`, `:except_from`, `:to`, and `:except_to` options. For example, transitions and callbacks can be defined like so: ```ruby class Vehicle state_machine :initial => :parked do before_transition :from => :parked, :except_to => :parked, :do => :put_on_seatbelt after_transition :to => :parked do |transition| self.seatbelt = 'off' # self is the record end event :ignite do transition :from => :parked, :to => :idling end end end ``` #### Transition context Some flexibility is provided around the context in which transitions can be defined. In almost all examples throughout the documentation, transitions are defined within the context of an event. If you prefer to have state machines defined in the context of a **state** either out of preference or in order to easily migrate from a different library, you can do so as shown below: ```ruby class Vehicle state_machine :initial => :parked do ... state :parked do transition :to => :idling, :on => [:ignite, :shift_up], :if => :seatbelt_on? def speed 0 end end state :first_gear do transition :to => :second_gear, :on => :shift_up def speed 10 end end state :idling, :first_gear do transition :to => :parked, :on => :park end end end ``` In the above example, there's no need to specify the `from` state for each transition since it's inferred from the context. You can also define transitions completely outside the context of a particular state / event. This may be useful in cases where you're building a state machine from a data store instead of part of the class definition. See the example below: ```ruby class Vehicle state_machine :initial => :parked do ... transition :parked => :idling, :on => [:ignite, :shift_up] transition :first_gear => :second_gear, :second_gear => :third_gear, :on => :shift_up transition [:idling, :first_gear] => :parked, :on => :park transition [:idling, :first_gear] => :parked, :on => :park transition all - [:parked, :stalled] => :stalled, :unless => :auto_shop_busy? end end ``` Notice that in these alternative syntaxes: * You can continue to configure `:if` and `:unless` conditions * You can continue to define `from` states (when in the machine context) using the `all`, `any`, and `same` helper methods ### Static / Dynamic definitions In most cases, the definition of a state machine is **static**. That is to say, the states, events and possible transitions are known ahead of time even though they may depend on data that's only known at runtime. For example, certain transitions may only be available depending on an attribute on that object it's being run on. All of the documentation in this library define static machines like so: ```ruby class Vehicle state_machine :state, :initial => :parked do event :park do transition [:idling, :first_gear] => :parked end ... end end ``` However, there may be cases where the definition of a state machine is **dynamic**. This means that you don't know the possible states or events for a machine until runtime. For example, you may allow users in your application to manage the state machine of a project or task in your system. This means that the list of transitions (and their associated states / events) could be stored externally, such as in a database. In a case like this, you can define dynamically-generated state machines like so: ```ruby class Vehicle attr_accessor :state # Make sure the machine gets initialized so the initial state gets set properly def initialize(*) super machine end # Replace this with an external source (like a db) def transitions [ {:parked => :idling, :on => :ignite}, {:idling => :first_gear, :first_gear => :second_gear, :on => :shift_up} # ... ] end # Create a state machine for this vehicle instance dynamically based on the # transitions defined from the source above def machine vehicle = self @machine ||= Machine.new(vehicle, :initial => :parked, :action => :save) do vehicle.transitions.each {|attrs| transition(attrs)} end end def save # Save the state change... true end end # Generic class for building machines class Machine def self.new(object, *args, &block) machine_class = Class.new machine = machine_class.state_machine(*args, &block) attribute = machine.attribute action = machine.action # Delegate attributes machine_class.class_eval do define_method(:definition) { machine } define_method(attribute) { object.send(attribute) } define_method("#{attribute}=") {|value| object.send("#{attribute}=", value) } define_method(action) { object.send(action) } if action end machine_class.new end end vehicle = Vehicle.new # => # vehicle.state # => "parked" vehicle.machine.ignite # => true vehicle.machine.state # => "idling vehicle.state # => "idling" vehicle.machine.state_transitions # => [#] vehicle.machine.definition.states.keys # => :first_gear, :second_gear, :parked, :idling ``` As you can see, state_machine provides enough flexibility for you to be able to create new machine definitions on the fly based on an external source of transitions. ### Core Extensions By default, state_machine extends the Ruby core with a `state_machine` method on `Class`. All other parts of the library are confined within the `StateMachine` namespace. While this isn't wholly necessary, it also doesn't have any performance impact and makes it truly feel like an extension to the language. This is very similar to the way that you'll find `yaml`, `json`, or other libraries adding a simple method to all objects just by loading the library. However, if you'd like to avoid having state_machine add this extension to the Ruby core, you can do so like so: ```ruby require 'state_machine/core' class Vehicle extend StateMachine::MacroMethods state_machine do # ... end end ``` If you're using a gem loader like Bundler, you can explicitly indicate which file to load: ```ruby # In Gemfile ... gem 'state_machine', :require => 'state_machine/core' ``` ## Tools ### Generating graphs This library comes with built-in support for generating di-graphs based on the events, states, and transitions defined for a state machine using [GraphViz](http://www.graphviz.org). This requires that both the `ruby-graphviz` gem and graphviz library be installed on the system. #### Examples To generate a graph for a specific file / class: ```bash rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle ``` To save files to a specific path: ```bash rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle TARGET=files ``` To customize the image format / orientation: ```bash rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle FORMAT=jpg ORIENTATION=landscape ``` See http://rdoc.info/github/glejeune/Ruby-Graphviz/Constants for the list of supported image formats. If resolution is an issue, the svg format may offer better results. To generate multiple state machine graphs: ```bash rake state_machine:draw FILE=vehicle.rb,car.rb CLASS=Vehicle,Car ``` To use human state / event names: ```bash rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle HUMAN_NAMES=true ``` **Note** that this will generate a different file for every state machine defined in the class. The generated files will use an output filename of the format `#{class_name}_#{machine_name}.#{format}`. For examples of actual images generated using this task, see those under the examples folder. ### Interactive graphs Jean Bovet's [Visual Automata Simulator](http://www.cs.usfca.edu/~jbovet/vas.html) is a great tool for "simulating, visualizing and transforming finite state automata and Turing Machines". It can help in the creation of states and events for your models. It is cross-platform, written in Java. ### Generating documentation If you use YARD to generate documentation for your projects, state_machine can be enabled to generate API docs for auto-generated methods from each state machine definition as well as providing embedded visualizations. See the generated API documentation under the examples folder to see what the output looks like. To enable the YARD integration, you'll need to add state_machine to the list of YARD's plugins by editing the global YARD config: ~/.yard/config: ```yaml load_plugins: true autoload_plugins: - state_machine ``` Once enabled, simply generate your documentation like you normally do. *Note* that this only works for Ruby 1.9+. ## Web Frameworks ### Ruby on Rails Integrating state_machine into your Ruby on Rails application is straightforward and provides a few additional features specific to the framework. To get started, following the steps below. #### 1. Install the gem If using Rails 2.x: ```ruby # In config/environment.rb ... Rails::Initializer.run do |config| ... config.gem 'state_machine', :version => '~> 1.0' ... end ``` If using Rails 3.x or up: ```ruby # In Gemfile ... gem 'state_machine' gem 'ruby-graphviz', :require => 'graphviz' # Optional: only required for graphing ``` As usual, run `bundle install` to load the gems. #### 2. Create a model Create a model with a field to store the state, along with other any other fields your application requires: ```bash $ rails generate model Vehicle state:string $ rake db:migrate ``` #### 3. Configure the state machine Add the state machine to your model. Following the examples above, *app/models/vehicle.rb* might become: ```ruby class Vehicle < ActiveRecord::Base state_machine :initial => :parked do before_transition :parked => any - :parked, :do => :put_on_seatbelt ... end end ``` #### Rake tasks There is a special integration Rake task for generating state machines for classes used in a Ruby on Rails application. This task will load the application environment, meaning that it's unnecessary to specify the actual file to load. For example, ```bash rake state_machine:draw CLASS=Vehicle ``` If you are using this library as a gem in Rails 2.x, the following must be added to the end of your application's Rakefile in order for the above task to work: ```ruby require 'tasks/state_machine' ``` ### Merb #### Rake tasks Like Ruby on Rails, there is a special integration Rake task for generating state machines for classes used in a Merb application. This task will load the application environment, meaning that it's unnecessary to specify the actual files to load. For example, ```bash rake state_machine:draw CLASS=Vehicle ``` ## Testing To run the core test suite (does **not** test any of the integrations): ```bash bundle install bundle exec rake test ``` To run integration tests: ```bash bundle install rake appraisal:install rake appraisal:test ``` You can also test a specific version: ```bash rake appraisal:active_model-3.0.0 test rake appraisal:active_record-2.0.0 test rake appraisal:data_mapper-0.9.4 test rake appraisal:mongoid-2.0.0 test rake appraisal:mongo_mapper-0.5.5 test rake appraisal:sequel-2.8.0 test ``` ## Caveats The following caveats should be noted when using state_machine: * Overridden event methods won't get invoked when using attribute-based event transitions * **DataMapper**: Attribute-based event transitions are disabled when using dm-validations 0.9.4 - 0.9.6 * **DataMapper**: Transitions cannot persist states when run from after :create / :save callbacks * **JRuby / Rubinius**: around_transition callbacks in ORM integrations won't work on JRuby since it doesn't support continuations * **Factory Girl**: Dynamic initial states don't work because of the way factory_girl builds objects. You can work around this in a few ways: 1. Use a default state that is common across all objects and rely on events to determine the actual initial state for your object. 2. Assuming you're not using state-driven behavior on initialization, you can re-initialize states after the fact: ```ruby # Re-initialize in FactoryGirl FactoryGirl.define do factory :vehicle do after_build {|user| user.send(:initialize_state_machines, :dynamic => :force)} end end # Alternatively re-initialize in your model class Vehicle < ActiveRecord::Base ... before_validation :on => :create {|user| user.send(:initialize_state_machines, :dynamic => :force)} end ``` ## Dependencies Ruby versions officially supported and tested: * Ruby (MRI) 1.8.6+ * JRuby (1.8, 1.9) * Rubinius (1.8, 1.9) ORM versions officially supported and tested: * [ActiveModel](http://rubyonrails.org) integration: 3.0.0 or later * [ActiveRecord](http://rubyonrails.org) integration: 2.0.0 or later * [DataMapper](http://datamapper.org) integration: 0.9.4 or later * [Mongoid](http://mongoid.org) integration: 2.0.0 or later * [MongoMapper](http://mongomapper.com) integration: 0.5.5 or later * [Sequel](http://sequel.rubyforge.org) integration: 2.8.0 or later If graphing state machine: * [ruby-graphviz](http://github.com/glejeune/Ruby-Graphviz): 0.9.17 or later state-machine-1.2.0/init.rb0000644000175000017500000000003012305405267015077 0ustar boutilboutilrequire 'state_machine' state-machine-1.2.0/Rakefile0000644000175000017500000000206612305405267015267 0ustar boutilboutilrequire 'rubygems' require 'bundler' Bundler.setup require 'rake' require 'rake/testtask' require 'appraisal' desc 'Default: run all tests.' task :default => :test desc "Test state_machine." Rake::TestTask.new(:test) do |t| integration = %w(active_model active_record data_mapper mongoid mongo_mapper sequel).detect do |name| Bundler.default_gemfile.to_s.include?(name) end t.libs << 'lib' t.test_files = integration ? Dir["test/unit/integrations/#{integration}_test.rb"] : Dir['test/{functional,unit}/*_test.rb'] + ['test/unit/integrations/base_test.rb'] t.verbose = true t.warning = true if ENV['WARNINGS'] end namespace :appraisal do desc "Run the given task for a particular integration's appraisals" task :integration do integration = ENV['INTEGRATION'] Appraisal::File.each do |appraisal| if appraisal.name.include?(integration) appraisal.install Appraisal::Command.from_args(appraisal.gemfile_path).run end end exit end end load File.dirname(__FILE__) + '/lib/tasks/state_machine.rake' state-machine-1.2.0/lib/0000755000175000017500000000000012305405267014364 5ustar boutilboutilstate-machine-1.2.0/lib/yard-state_machine.rb0000644000175000017500000000007212305405267020451 0ustar boutilboutilrequire 'state_machine/core' require 'state_machine/yard' state-machine-1.2.0/lib/tasks/0000755000175000017500000000000012305405267015511 5ustar boutilboutilstate-machine-1.2.0/lib/tasks/state_machine.rake0000644000175000017500000000007512305405267021163 0ustar boutilboutilrequire File.join("#{File.dirname(__FILE__)}/state_machine") state-machine-1.2.0/lib/tasks/state_machine.rb0000644000175000017500000000233612305405267020646 0ustar boutilboutilnamespace :state_machine do desc 'Draws state machines using GraphViz (options: CLASS=User,Vehicle; FILE=user.rb,vehicle.rb [not required in Rails / Merb]; FONT=Arial; FORMAT=png; ORIENTATION=portrait; HUMAN_NAMES=true' task :draw do # Build drawing options options = {} options[:file] = ENV['FILE'] if ENV['FILE'] options[:path] = ENV['TARGET'] if ENV['TARGET'] options[:format] = ENV['FORMAT'] if ENV['FORMAT'] options[:font] = ENV['FONT'] if ENV['FONT'] options[:orientation] = ENV['ORIENTATION'] if ENV['ORIENTATION'] options[:human_names] = ENV['HUMAN_NAMES'] == 'true' if ENV['HUMAN_NAMES'] if defined?(Rails) puts "Files are automatically loaded in Rails; ignoring FILE option" if options.delete(:file) Rake::Task['environment'].invoke elsif defined?(Merb) puts "Files are automatically loaded in Merb; ignoring FILE option" if options.delete(:file) Rake::Task['merb_env'].invoke # Fix ruby-graphviz being incompatible with Merb's process title $0 = 'rake' else # Load the library $:.unshift(File.dirname(__FILE__) + '/..') require 'state_machine' end StateMachine::Machine.draw(ENV['CLASS'], options) end end state-machine-1.2.0/lib/state_machine.rb0000644000175000017500000000062112305405267017514 0ustar boutilboutil# By default, requiring "state_machine" means that both the core implementation # *and* extensions to the Ruby core (Class in particular) will be pulled in. # # If you want to skip the Ruby core extensions, simply require "state_machine/core" # and extend StateMachine::MacroMethods in your class. See the README for more # information. require 'state_machine/core' require 'state_machine/core_ext' state-machine-1.2.0/lib/state_machine/0000755000175000017500000000000012305405267017170 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/node_collection.rb0000644000175000017500000001710612305405267022662 0ustar boutilboutilrequire 'state_machine/assertions' module StateMachine # Represents a collection of nodes in a state machine, be it events or states. # Nodes will not differentiate between the String and Symbol versions of the # values being indexed. class NodeCollection include Enumerable include Assertions # The machine associated with the nodes attr_reader :machine # Creates a new collection of nodes for the given state machine. By default, # the collection is empty. # # Configuration options: # * :index - One or more attributes to automatically generate # hashed indices for in order to perform quick lookups. Default is to # index by the :name attribute def initialize(machine, options = {}) assert_valid_keys(options, :index) options = {:index => :name}.merge(options) @machine = machine @nodes = [] @index_names = Array(options[:index]) @indices = @index_names.inject({}) do |indices, name| indices[name] = {} indices[:"#{name}_to_s"] = {} indices[:"#{name}_to_sym"] = {} indices end @default_index = Array(options[:index]).first @contexts = [] end # Creates a copy of this collection such that modifications don't affect # the original collection def initialize_copy(orig) #:nodoc: super nodes = @nodes contexts = @contexts @nodes = [] @contexts = [] @indices = @indices.inject({}) {|indices, (name, *)| indices[name] = {}; indices} # Add nodes *prior* to copying over the contexts so that they don't get # evaluated multiple times concat(nodes.map {|n| n.dup}) @contexts = contexts.dup end # Changes the current machine associated with the collection. In turn, this # will change the state machine associated with each node in the collection. def machine=(new_machine) @machine = new_machine each {|node| node.machine = new_machine} end # Gets the number of nodes in this collection def length @nodes.length end # Gets the set of unique keys for the given index def keys(index_name = @default_index) index(index_name).keys end # Tracks a context that should be evaluated for any nodes that get added # which match the given set of nodes. Matchers can be used so that the # context can get added once and evaluated after multiple adds. def context(nodes, &block) nodes = nodes.first.is_a?(Matcher) ? nodes.first : WhitelistMatcher.new(nodes) @contexts << context = {:nodes => nodes, :block => block} # Evaluate the new context for existing nodes each {|node| eval_context(context, node)} context end # Adds a new node to the collection. By doing so, this will also add it to # the configured indices. This will also evaluate any existings contexts # that match the new node. def <<(node) @nodes << node @index_names.each {|name| add_to_index(name, value(node, name), node)} @contexts.each {|context| eval_context(context, node)} self end # Appends a group of nodes to the collection def concat(nodes) nodes.each {|node| self << node} end # Updates the indexed keys for the given node. If the node's attribute # has changed since it was added to the collection, the old indexed keys # will be replaced with the updated ones. def update(node) @index_names.each {|name| update_index(name, node)} end # Calls the block once for each element in self, passing that element as a # parameter. # # states = StateMachine::NodeCollection.new # states << StateMachine::State.new(machine, :parked) # states << StateMachine::State.new(machine, :idling) # states.each {|state| puts state.name, ' -- '} # # ...produces: # # parked -- idling -- def each @nodes.each {|node| yield node} self end # Gets the node at the given index. # # states = StateMachine::NodeCollection.new # states << StateMachine::State.new(machine, :parked) # states << StateMachine::State.new(machine, :idling) # # states.at(0).name # => :parked # states.at(1).name # => :idling def at(index) @nodes[index] end # Gets the node indexed by the given key. By default, this will look up the # key in the first index configured for the collection. A custom index can # be specified like so: # # collection['parked', :value] # # The above will look up the "parked" key in a hash indexed by each node's # +value+ attribute. # # If the key cannot be found, then nil will be returned. def [](key, index_name = @default_index) self.index(index_name)[key] || self.index(:"#{index_name}_to_s")[key.to_s] || to_sym?(key) && self.index(:"#{index_name}_to_sym")[:"#{key}"] || nil end # Gets the node indexed by the given key. By default, this will look up the # key in the first index configured for the collection. A custom index can # be specified like so: # # collection['parked', :value] # # The above will look up the "parked" key in a hash indexed by each node's # +value+ attribute. # # If the key cannot be found, then an IndexError exception will be raised: # # collection['invalid', :value] # => IndexError: "invalid" is an invalid value def fetch(key, index_name = @default_index) self[key, index_name] || raise(IndexError, "#{key.inspect} is an invalid #{index_name}") end protected # Gets the given index. If the index does not exist, then an ArgumentError # is raised. def index(name) raise ArgumentError, 'No indices configured' unless @indices.any? @indices[name] || raise(ArgumentError, "Invalid index: #{name.inspect}") end # Gets the value for the given attribute on the node def value(node, attribute) node.send(attribute) end # Adds the given key / node combination to an index, including the string # and symbol versions of the index def add_to_index(name, key, node) index(name)[key] = node index(:"#{name}_to_s")[key.to_s] = node index(:"#{name}_to_sym")[:"#{key}"] = node if to_sym?(key) end # Removes the given key from an index, including the string and symbol # versions of the index def remove_from_index(name, key) index(name).delete(key) index(:"#{name}_to_s").delete(key.to_s) index(:"#{name}_to_sym").delete(:"#{key}") if to_sym?(key) end # Updates the node for the given index, including the string and symbol # versions of the index def update_index(name, node) index = self.index(name) old_key = RUBY_VERSION < '1.9' ? index.index(node) : index.key(node) new_key = value(node, name) # Only replace the key if it's changed if old_key != new_key remove_from_index(name, old_key) add_to_index(name, new_key, node) end end # Determines whether the given value can be converted to a symbol def to_sym?(value) "#{value}" != '' end # Evaluates the given context for a particular node. This will only # evaluate the context if the node matches. def eval_context(context, node) node.context(&context[:block]) if context[:nodes].matches?(node.name) end end end state-machine-1.2.0/lib/state_machine/machine.rb0000644000175000017500000026721512305405267021136 0ustar boutilboutilrequire 'state_machine/extensions' require 'state_machine/assertions' require 'state_machine/integrations' require 'state_machine/helper_module' require 'state_machine/state' require 'state_machine/event' require 'state_machine/callback' require 'state_machine/node_collection' require 'state_machine/state_collection' require 'state_machine/event_collection' require 'state_machine/path_collection' require 'state_machine/matcher_helpers' module StateMachine # Represents a state machine for a particular attribute. State machines # consist of states, events and a set of transitions that define how the # state changes after a particular event is fired. # # A state machine will not know all of the possible states for an object # unless they are referenced *somewhere* in the state machine definition. # As a result, any unused states should be defined with the +other_states+ # or +state+ helper. # # == Actions # # When an action is configured for a state machine, it is invoked when an # object transitions via an event. The success of the event becomes # dependent on the success of the action. If the action is successful, then # the transitioned state remains persisted. However, if the action fails # (by returning false), the transitioned state will be rolled back. # # For example, # # class Vehicle # attr_accessor :fail, :saving_state # # state_machine :initial => :parked, :action => :save do # event :ignite do # transition :parked => :idling # end # # event :park do # transition :idling => :parked # end # end # # def save # @saving_state = state # fail != true # end # end # # vehicle = Vehicle.new # => # # vehicle.save # => true # vehicle.saving_state # => "parked" # The state was "parked" was save was called # # # Successful event # vehicle.ignite # => true # vehicle.saving_state # => "idling" # The state was "idling" when save was called # vehicle.state # => "idling" # # # Failed event # vehicle.fail = true # vehicle.park # => false # vehicle.saving_state # => "parked" # vehicle.state # => "idling" # # As shown, even though the state is set prior to calling the +save+ action # on the object, it will be rolled back to the original state if the action # fails. *Note* that this will also be the case if an exception is raised # while calling the action. # # === Indirect transitions # # In addition to the action being run as the _result_ of an event, the action # can also be used to run events itself. For example, using the above as an # example: # # vehicle = Vehicle.new # => # # # vehicle.state_event = 'ignite' # vehicle.save # => true # vehicle.state # => "idling" # vehicle.state_event # => nil # # As can be seen, the +save+ action automatically invokes the event stored in # the +state_event+ attribute (:ignite in this case). # # One important note about using this technique for running transitions is # that if the class in which the state machine is defined *also* defines the # action being invoked (and not a superclass), then it must manually run the # StateMachine hook that checks for event attributes. # # For example, in ActiveRecord, DataMapper, Mongoid, MongoMapper, and Sequel, # the default action (+save+) is already defined in a base class. As a result, # when a state machine is defined in a model / resource, StateMachine can # automatically hook into the +save+ action. # # On the other hand, the Vehicle class from above defined its own +save+ # method (and there is no +save+ method in its superclass). As a result, it # must be modified like so: # # def save # self.class.state_machines.transitions(self, :save).perform do # @saving_state = state # fail != true # end # end # # This will add in the functionality for firing the event stored in the # +state_event+ attribute. # # == Callbacks # # Callbacks are supported for hooking before and after every possible # transition in the machine. Each callback is invoked in the order in which # it was defined. See StateMachine::Machine#before_transition and # StateMachine::Machine#after_transition for documentation on how to define # new callbacks. # # *Note* that callbacks only get executed within the context of an event. As # a result, if a class has an initial state when it's created, any callbacks # that would normally get executed when the object enters that state will # *not* get triggered. # # For example, # # class Vehicle # state_machine :initial => :parked do # after_transition all => :parked do # raise ArgumentError # end # ... # end # end # # vehicle = Vehicle.new # => # # vehicle.save # => true (no exception raised) # # If you need callbacks to get triggered when an object is created, this # should be done by one of the following techniques: # * Use a before :create or equivalent hook: # # class Vehicle # before :create, :track_initial_transition # # state_machine do # ... # end # end # # * Set an initial state and use the correct event to create the # object with the proper state, resulting in callbacks being triggered and # the object getting persisted (note that the :pending state is # actually stored as nil): # # class Vehicle # state_machine :initial => :pending # after_transition :pending => :parked, :do => :track_initial_transition # # event :park do # transition :pending => :parked # end # # state :pending, :value => nil # end # end # # vehicle = Vehicle.new # vehicle.park # # * Use a default event attribute that will automatically trigger when the # configured action gets run (note that the :pending state is # actually stored as nil): # # class Vehicle < ActiveRecord::Base # state_machine :initial => :pending # after_transition :pending => :parked, :do => :track_initial_transition # # event :park do # transition :pending => :parked # end # # state :pending, :value => nil # end # # def initialize(*) # super # self.state_event = 'park' # end # end # # vehicle = Vehicle.new # vehicle.save # # === Canceling callbacks # # Callbacks can be canceled by throwing :halt at any point during the # callback. For example, # # ... # throw :halt # ... # # If a +before+ callback halts the chain, the associated transition and all # later callbacks are canceled. If an +after+ callback halts the chain, # the later callbacks are canceled, but the transition is still successful. # # These same rules apply to +around+ callbacks with the exception that any # +around+ callback that doesn't yield will essentially result in :halt being # thrown. Any code executed after the yield will behave in the same way as # +after+ callbacks. # # *Note* that if a +before+ callback fails and the bang version of an event # was invoked, an exception will be raised instead of returning false. For # example, # # class Vehicle # state_machine :initial => :parked do # before_transition any => :idling, :do => lambda {|vehicle| throw :halt} # ... # end # end # # vehicle = Vehicle.new # vehicle.park # => false # vehicle.park! # => StateMachine::InvalidTransition: Cannot transition state via :park from "idling" # # == Observers # # Observers, in the sense of external classes and *not* Ruby's Observable # mechanism, can hook into state machines as well. Such observers use the # same callback api that's used internally. # # Below are examples of defining observers for the following state machine: # # class Vehicle # state_machine do # event :park do # transition :idling => :parked # end # ... # end # ... # end # # Event/Transition behaviors: # # class VehicleObserver # def self.before_park(vehicle, transition) # logger.info "#{vehicle} instructed to park... state is: #{transition.from}, state will be: #{transition.to}" # end # # def self.after_park(vehicle, transition, result) # logger.info "#{vehicle} instructed to park... state was: #{transition.from}, state is: #{transition.to}" # end # # def self.before_transition(vehicle, transition) # logger.info "#{vehicle} instructed to #{transition.event}... #{transition.attribute} is: #{transition.from}, #{transition.attribute} will be: #{transition.to}" # end # # def self.after_transition(vehicle, transition) # logger.info "#{vehicle} instructed to #{transition.event}... #{transition.attribute} was: #{transition.from}, #{transition.attribute} is: #{transition.to}" # end # # def self.around_transition(vehicle, transition) # logger.info Benchmark.measure { yield } # end # end # # Vehicle.state_machine do # before_transition :on => :park, :do => VehicleObserver.method(:before_park) # before_transition VehicleObserver.method(:before_transition) # # after_transition :on => :park, :do => VehicleObserver.method(:after_park) # after_transition VehicleObserver.method(:after_transition) # # around_transition VehicleObserver.method(:around_transition) # end # # One common callback is to record transitions for all models in the system # for auditing/debugging purposes. Below is an example of an observer that # can easily automate this process for all models: # # class StateMachineObserver # def self.before_transition(object, transition) # Audit.log_transition(object.attributes) # end # end # # [Vehicle, Switch, Project].each do |klass| # klass.state_machines.each do |attribute, machine| # machine.before_transition StateMachineObserver.method(:before_transition) # end # end # # Additional observer-like behavior may be exposed by the various integrations # available. See below for more information on integrations. # # == Overriding instance / class methods # # Hooking in behavior to the generated instance / class methods from the # state machine, events, and states is very simple because of the way these # methods are generated on the class. Using the class's ancestors, the # original generated method can be referred to via +super+. For example, # # class Vehicle # state_machine do # event :park do # ... # end # end # # def park(*args) # logger.info "..." # super # end # end # # In the above example, the +park+ instance method that's generated on the # Vehicle class (by the associated event) is overridden with custom behavior. # Once this behavior is complete, the original method from the state machine # is invoked by simply calling +super+. # # The same technique can be used for +state+, +state_name+, and all other # instance *and* class methods on the Vehicle class. # # == Method conflicts # # By default state_machine does not redefine methods that exist on # superclasses (*including* Object) or any modules (*including* Kernel) that # were included before it was defined. This is in order to ensure that # existing behavior on the class is not broken by the inclusion of # state_machine. # # If a conflicting method is detected, state_machine will generate a warning. # For example, consider the following class: # # class Vehicle # state_machine do # event :open do # ... # end # end # end # # In the above class, an event named "open" is defined for its state machine. # However, "open" is already defined as an instance method in Ruby's Kernel # module that gets included in every Object. As a result, state_machine will # generate the following warning: # # Instance method "open" is already defined in Object, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true. # # Even though you may not be using Kernel's implementation of the "open" # instance method, state_machine isn't aware of this and, as a result, stays # safe and just skips redefining the method. # # As with almost all helpers methods defined by state_machine in your class, # there are generic methods available for working around this method conflict. # In the example above, you can invoke the "open" event like so: # # vehicle = Vehicle.new # => # # vehicle.fire_events(:open) # => true # # # This will not work # vehicle.open # => NoMethodError: private method `open' called for # # # If you want to take on the risk of overriding existing methods and just # ignore method conflicts altogether, you can do so by setting the following # configuration: # # StateMachine::Machine.ignore_method_conflicts = true # # This will allow you to define events like "open" as described above and # still generate the "open" instance helper method. For example: # # StateMachine::Machine.ignore_method_conflicts = true # # class Vehicle # state_machine do # event :open do # ... # end # end # # vehicle = Vehicle.new # => # # vehicle.open # => true # # By default, state_machine helps prevent you from making mistakes and # accidentally overriding methods that you didn't intend to. Once you # understand this and what the consequences are, setting the # +ignore_method_conflicts+ option is a perfectly reasonable workaround. # # == Integrations # # By default, state machines are library-agnostic, meaning that they work # on any Ruby class and have no external dependencies. However, there are # certain libraries which expose additional behavior that can be taken # advantage of by state machines. # # This library is built to work out of the box with a few popular Ruby # libraries that allow for additional behavior to provide a cleaner and # smoother experience. This is especially the case for objects backed by a # database that may allow for transactions, persistent storage, # search/filters, callbacks, etc. # # When a state machine is defined for classes using any of the above libraries, # it will try to automatically determine the integration to use (Agnostic, # ActiveModel, ActiveRecord, DataMapper, Mongoid, MongoMapper, or Sequel) # based on the class definition. To see how each integration affects the # machine's behavior, refer to all constants defined under the # StateMachine::Integrations namespace. class Machine include Assertions include EvalHelpers include MatcherHelpers class << self # Attempts to find or create a state machine for the given class. For # example, # # StateMachine::Machine.find_or_create(Vehicle) # StateMachine::Machine.find_or_create(Vehicle, :initial => :parked) # StateMachine::Machine.find_or_create(Vehicle, :status) # StateMachine::Machine.find_or_create(Vehicle, :status, :initial => :parked) # # If a machine of the given name already exists in one of the class's # superclasses, then a copy of that machine will be created and stored # in the new owner class (the original will remain unchanged). def find_or_create(owner_class, *args, &block) options = args.last.is_a?(Hash) ? args.pop : {} name = args.first || :state # Find an existing machine if owner_class.respond_to?(:state_machines) && machine = owner_class.state_machines[name] # Only create a new copy if changes are being made to the machine in # a subclass if machine.owner_class != owner_class && (options.any? || block_given?) machine = machine.clone machine.initial_state = options[:initial] if options.include?(:initial) machine.owner_class = owner_class end # Evaluate DSL machine.instance_eval(&block) if block_given? else # No existing machine: create a new one machine = new(owner_class, name, options, &block) end machine end # Draws the state machines defined in the given classes using GraphViz. # The given classes must be a comma-delimited string of class names. # # Configuration options: # * :file - A comma-delimited string of files to load that # contain the state machine definitions to draw # * :path - The path to write the graph file to # * :format - The image format to generate the graph in # * :font - The name of the font to draw state names in def draw(class_names, options = {}) raise ArgumentError, 'At least one class must be specified' unless class_names && class_names.split(',').any? # Load any files if files = options.delete(:file) files.split(',').each {|file| require file} end class_names.split(',').each do |class_name| # Navigate through the namespace structure to get to the class klass = Object class_name.split('::').each do |name| klass = klass.const_defined?(name) ? klass.const_get(name) : klass.const_missing(name) end # Draw each of the class's state machines klass.state_machines.each_value do |machine| machine.draw(options) end end end end # Default messages to use for validation errors in ORM integrations class << self; attr_accessor :default_messages; end @default_messages = { :invalid => 'is invalid', :invalid_event => 'cannot transition when %s', :invalid_transition => 'cannot transition via "%1$s"' } # Whether to ignore any conflicts that are detected for helper methods that # get generated for a machine's owner class. Default is false. class << self; attr_accessor :ignore_method_conflicts; end @ignore_method_conflicts = false # The class that the machine is defined in attr_reader :owner_class # The name of the machine, used for scoping methods generated for the # machine as a whole (not states or events) attr_reader :name # The events that trigger transitions. These are sorted, by default, in # the order in which they were defined. attr_reader :events # A list of all of the states known to this state machine. This will pull # states from the following sources: # * Initial state # * State behaviors # * Event transitions (:to, :from, and :except_from options) # * Transition callbacks (:to, :from, :except_to, and :except_from options) # * Unreferenced states (using +other_states+ helper) # # These are sorted, by default, in the order in which they were referenced. attr_reader :states # The callbacks to invoke before/after a transition is performed # # Maps :before => callbacks and :after => callbacks attr_reader :callbacks # The action to invoke when an object transitions attr_reader :action # An identifier that forces all methods (including state predicates and # event methods) to be generated with the value prefixed or suffixed, # depending on the context. attr_reader :namespace # Whether the machine will use transactions when firing events attr_reader :use_transactions # Creates a new state machine for the given attribute def initialize(owner_class, *args, &block) options = args.last.is_a?(Hash) ? args.pop : {} assert_valid_keys(options, :attribute, :initial, :initialize, :action, :plural, :namespace, :integration, :messages, :use_transactions) # Find an integration that matches this machine's owner class if options.include?(:integration) @integration = options[:integration] && StateMachine::Integrations.find_by_name(options[:integration]) else @integration = StateMachine::Integrations.match(owner_class) end if @integration extend @integration options = (@integration.defaults || {}).merge(options) end # Add machine-wide defaults options = {:use_transactions => true, :initialize => true}.merge(options) # Set machine configuration @name = args.first || :state @attribute = options[:attribute] || @name @events = EventCollection.new(self) @states = StateCollection.new(self) @callbacks = {:before => [], :after => [], :failure => []} @namespace = options[:namespace] @messages = options[:messages] || {} @action = options[:action] @use_transactions = options[:use_transactions] @initialize_state = options[:initialize] @action_hook_defined = false self.owner_class = owner_class self.initial_state = options[:initial] unless sibling_machines.any? # Merge with sibling machine configurations add_sibling_machine_configs # Define class integration define_helpers define_scopes(options[:plural]) after_initialize # Evaluate DSL instance_eval(&block) if block_given? end # Creates a copy of this machine in addition to copies of each associated # event/states/callback, so that the modifications to those collections do # not affect the original machine. def initialize_copy(orig) #:nodoc: super @events = @events.dup @events.machine = self @states = @states.dup @states.machine = self @callbacks = {:before => @callbacks[:before].dup, :after => @callbacks[:after].dup, :failure => @callbacks[:failure].dup} end # Sets the class which is the owner of this state machine. Any methods # generated by states, events, or other parts of the machine will be defined # on the given owner class. def owner_class=(klass) @owner_class = klass # Create modules for extending the class with state/event-specific methods @helper_modules = helper_modules = {:instance => HelperModule.new(self, :instance), :class => HelperModule.new(self, :class)} owner_class.class_eval do extend helper_modules[:class] include helper_modules[:instance] end # Add class-/instance-level methods to the owner class for state initialization unless owner_class < StateMachine::InstanceMethods owner_class.class_eval do extend StateMachine::ClassMethods include StateMachine::InstanceMethods end define_state_initializer if @initialize_state end # Record this machine as matched to the name in the current owner class. # This will override any machines mapped to the same name in any superclasses. owner_class.state_machines[name] = self end # Sets the initial state of the machine. This can be either the static name # of a state or a lambda block which determines the initial state at # creation time. def initial_state=(new_initial_state) @initial_state = new_initial_state add_states([@initial_state]) unless dynamic_initial_state? # Update all states to reflect the new initial state states.each {|state| state.initial = (state.name == @initial_state)} # Output a warning if there are conflicting initial states for the machine's # attribute initial_state = states.detect {|state| state.initial} if !owner_class_attribute_default.nil? && (dynamic_initial_state? || !owner_class_attribute_default_matches?(initial_state)) warn( "Both #{owner_class.name} and its #{name.inspect} machine have defined "\ "a different default for \"#{attribute}\". Use only one or the other for "\ "defining defaults to avoid unexpected behaviors." ) end end # Gets the initial state of the machine for the given object. If a dynamic # initial state was configured for this machine, then the object will be # passed into the lambda block to help determine the actual state. # # == Examples # # With a static initial state: # # class Vehicle # state_machine :initial => :parked do # ... # end # end # # vehicle = Vehicle.new # Vehicle.state_machine.initial_state(vehicle) # => # # # With a dynamic initial state: # # class Vehicle # attr_accessor :force_idle # # state_machine :initial => lambda {|vehicle| vehicle.force_idle ? :idling : :parked} do # ... # end # end # # vehicle = Vehicle.new # # vehicle.force_idle = true # Vehicle.state_machine.initial_state(vehicle) # => # # # vehicle.force_idle = false # Vehicle.state_machine.initial_state(vehicle) # => # def initial_state(object) states.fetch(dynamic_initial_state? ? evaluate_method(object, @initial_state) : @initial_state) if instance_variable_defined?('@initial_state') end # Whether a dynamic initial state is being used in the machine def dynamic_initial_state? instance_variable_defined?('@initial_state') && @initial_state.is_a?(Proc) end # Initializes the state on the given object. Initial values are only set if # the machine's attribute hasn't been previously initialized. # # Configuration options: # * :force - Whether to initialize the state regardless of its # current value # * :to - A hash to set the initial value in instead of writing # directly to the object def initialize_state(object, options = {}) state = initial_state(object) if state && (options[:force] || initialize_state?(object)) value = state.value if hash = options[:to] hash[attribute.to_s] = value else write(object, :state, value) end end end # Gets the actual name of the attribute on the machine's owner class that # stores data with the given name. def attribute(name = :state) name == :state ? @attribute : :"#{self.name}_#{name}" end # Defines a new helper method in an instance or class scope with the given # name. If the method is already defined in the scope, then this will not # override it. # # If passing in a block, there are two side effects to be aware of # 1. The method cannot be chained, meaning that the block cannot call +super+ # 2. If the method is already defined in an ancestor, then it will not get # overridden and a warning will be output. # # Example: # # # Instance helper # machine.define_helper(:instance, :state_name) do |machine, object| # machine.states.match(object).name # end # # # Class helper # machine.define_helper(:class, :state_machine_name) do |machine, klass| # "State" # end # # You can also define helpers using string evaluation like so: # # # Instance helper # machine.define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 # def state_name # self.class.state_machine(:state).states.match(self).name # end # end_eval # # # Class helper # machine.define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1 # def state_machine_name # "State" # end # end_eval def define_helper(scope, method, *args, &block) helper_module = @helper_modules.fetch(scope) if block_given? if !self.class.ignore_method_conflicts && conflicting_ancestor = owner_class_ancestor_has_method?(scope, method) ancestor_name = conflicting_ancestor.name && !conflicting_ancestor.name.empty? ? conflicting_ancestor.name : conflicting_ancestor.to_s warn "#{scope == :class ? 'Class' : 'Instance'} method \"#{method}\" is already defined in #{ancestor_name}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true." else name = self.name helper_module.class_eval do define_method(method) do |*block_args| block.call((scope == :instance ? self.class : self).state_machine(name), self, *block_args) end end end else helper_module.class_eval(method, *args) end end # Customizes the definition of one or more states in the machine. # # Configuration options: # * :value - The actual value to store when an object transitions # to the state. Default is the name (stringified). # * :cache - If a dynamic value (via a lambda block) is being used, # then setting this to true will cache the evaluated result # * :if - Determines whether an object's value matches the state # (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}). # By default, the configured value is matched. # * :human_name - The human-readable version of this state's name. # By default, this is either defined by the integration or stringifies the # name and converts underscores to spaces. # # == Customizing the stored value # # Whenever a state is automatically discovered in the state machine, its # default value is assumed to be the stringified version of the name. For # example, # # class Vehicle # state_machine :initial => :parked do # event :ignite do # transition :parked => :idling # end # end # end # # In the above state machine, there are two states automatically discovered: # :parked and :idling. These states, by default, will store their stringified # equivalents when an object moves into that state (e.g. "parked" / "idling"). # # For legacy systems or when tying state machines into existing frameworks, # it's oftentimes necessary to need to store a different value for a state # than the default. In order to continue taking advantage of an expressive # state machine and helper methods, every defined state can be re-configured # with a custom stored value. For example, # # class Vehicle # state_machine :initial => :parked do # event :ignite do # transition :parked => :idling # end # # state :idling, :value => 'IDLING' # state :parked, :value => 'PARKED # end # end # # This is also useful if being used in association with a database and, # instead of storing the state name in a column, you want to store the # state's foreign key: # # class VehicleState < ActiveRecord::Base # end # # class Vehicle < ActiveRecord::Base # state_machine :attribute => :state_id, :initial => :parked do # event :ignite do # transition :parked => :idling # end # # states.each do |state| # self.state(state.name, :value => lambda { VehicleState.find_by_name(state.name.to_s).id }, :cache => true) # end # end # end # # In the above example, each known state is configured to store it's # associated database id in the +state_id+ attribute. Also, notice that a # lambda block is used to define the state's value. This is required in # situations (like testing) where the model is loaded without any existing # data (i.e. no VehicleState records available). # # One caveat to the above example is to keep performance in mind. To avoid # constant db hits for looking up the VehicleState ids, the value is cached # by specifying the :cache option. Alternatively, a custom # caching strategy can be used like so: # # class VehicleState < ActiveRecord::Base # cattr_accessor :cache_store # self.cache_store = ActiveSupport::Cache::MemoryStore.new # # def self.find_by_name(name) # cache_store.fetch(name) { find(:first, :conditions => {:name => name}) } # end # end # # === Dynamic values # # In addition to customizing states with other value types, lambda blocks # can also be specified to allow for a state's value to be determined # dynamically at runtime. For example, # # class Vehicle # state_machine :purchased_at, :initial => :available do # event :purchase do # transition all => :purchased # end # # event :restock do # transition all => :available # end # # state :available, :value => nil # state :purchased, :if => lambda {|value| !value.nil?}, :value => lambda {Time.now} # end # end # # In the above definition, the :purchased state is customized with # both a dynamic value *and* a value matcher. # # When an object transitions to the purchased state, the value's lambda # block will be called. This will get the current time and store it in the # object's +purchased_at+ attribute. # # *Note* that the custom matcher is very important here. Since there's no # way for the state machine to figure out an object's state when it's set to # a runtime value, it must be explicitly defined. If the :if option # were not configured for the state, then an ArgumentError exception would # be raised at runtime, indicating that the state machine could not figure # out what the current state of the object was. # # == Behaviors # # Behaviors define a series of methods to mixin with objects when the current # state matches the given one(s). This allows instance methods to behave # a specific way depending on what the value of the object's state is. # # For example, # # class Vehicle # attr_accessor :driver # attr_accessor :passenger # # state_machine :initial => :parked do # event :ignite do # transition :parked => :idling # end # # state :parked do # def speed # 0 # end # # def rotate_driver # driver = self.driver # self.driver = passenger # self.passenger = driver # true # end # end # # state :idling, :first_gear do # def speed # 20 # end # # def rotate_driver # self.state = 'parked' # rotate_driver # end # end # # other_states :backing_up # end # end # # In the above example, there are two dynamic behaviors defined for the # class: # * +speed+ # * +rotate_driver+ # # Each of these behaviors are instance methods on the Vehicle class. However, # which method actually gets invoked is based on the current state of the # object. Using the above class as the example: # # vehicle = Vehicle.new # vehicle.driver = 'John' # vehicle.passenger = 'Jane' # # # Behaviors in the "parked" state # vehicle.state # => "parked" # vehicle.speed # => 0 # vehicle.rotate_driver # => true # vehicle.driver # => "Jane" # vehicle.passenger # => "John" # # vehicle.ignite # => true # # # Behaviors in the "idling" state # vehicle.state # => "idling" # vehicle.speed # => 20 # vehicle.rotate_driver # => true # vehicle.driver # => "John" # vehicle.passenger # => "Jane" # # As can be seen, both the +speed+ and +rotate_driver+ instance method # implementations changed how they behave based on what the current state # of the vehicle was. # # === Invalid behaviors # # If a specific behavior has not been defined for a state, then a # NoMethodError exception will be raised, indicating that that method would # not normally exist for an object with that state. # # Using the example from before: # # vehicle = Vehicle.new # vehicle.state = 'backing_up' # vehicle.speed # => NoMethodError: undefined method 'speed' for # in state "backing_up" # # === Using matchers # # The +all+ / +any+ matchers can be used to easily define behaviors for a # group of states. Note, however, that you cannot use these matchers to # set configurations for states. Behaviors using these matchers can be # defined at any point in the state machine and will always get applied to # the proper states. # # For example: # # state_machine :initial => :parked do # ... # # state all - [:parked, :idling, :stalled] do # validates_presence_of :speed # # def speed # gear * 10 # end # end # end # # == State-aware class methods # # In addition to defining scopes for instance methods that are state-aware, # the same can be done for certain types of class methods. # # Some libraries have support for class-level methods that only run certain # behaviors based on a conditions hash passed in. For example: # # class Vehicle < ActiveRecord::Base # state_machine do # ... # state :first_gear, :second_gear, :third_gear do # validates_presence_of :speed # validates_inclusion_of :speed, :in => 0..25, :if => :in_school_zone? # end # end # end # # In the above ActiveRecord model, two validations have been defined which # will *only* run when the Vehicle object is in one of the three states: # +first_gear+, +second_gear+, or +third_gear. Notice, also, that if/unless # conditions can continue to be used. # # This functionality is not library-specific and can work for any class-level # method that is defined like so: # # def validates_presence_of(attribute, options = {}) # ... # end # # The minimum requirement is that the last argument in the method be an # options hash which contains at least :if condition support. def state(*names, &block) options = names.last.is_a?(Hash) ? names.pop : {} assert_valid_keys(options, :value, :cache, :if, :human_name) # Store the context so that it can be used for / matched against any state # that gets added @states.context(names, &block) if block_given? if names.first.is_a?(Matcher) # Add any states referenced in the matcher. When matchers are used, # states are not allowed to be configured. raise ArgumentError, "Cannot configure states when using matchers (using #{options.inspect})" if options.any? states = add_states(names.first.values) else states = add_states(names) # Update the configuration for the state(s) states.each do |state| if options.include?(:value) state.value = options[:value] self.states.update(state) end state.human_name = options[:human_name] if options.include?(:human_name) state.cache = options[:cache] if options.include?(:cache) state.matcher = options[:if] if options.include?(:if) end end states.length == 1 ? states.first : states end alias_method :other_states, :state # Gets the current value stored in the given object's attribute. # # For example, # # class Vehicle # state_machine :initial => :parked do # ... # end # end # # vehicle = Vehicle.new # => # # Vehicle.state_machine.read(vehicle, :state) # => "parked" # Equivalent to vehicle.state # Vehicle.state_machine.read(vehicle, :event) # => nil # Equivalent to vehicle.state_event def read(object, attribute, ivar = false) attribute = self.attribute(attribute) if ivar object.instance_variable_defined?("@#{attribute}") ? object.instance_variable_get("@#{attribute}") : nil else object.send(attribute) end end # Sets a new value in the given object's attribute. # # For example, # # class Vehicle # state_machine :initial => :parked do # ... # end # end # # vehicle = Vehicle.new # => # # Vehicle.state_machine.write(vehicle, :state, 'idling') # => Equivalent to vehicle.state = 'idling' # Vehicle.state_machine.write(vehicle, :event, 'park') # => Equivalent to vehicle.state_event = 'park' # vehicle.state # => "idling" # vehicle.event # => "park" def write(object, attribute, value, ivar = false) attribute = self.attribute(attribute) ivar ? object.instance_variable_set("@#{attribute}", value) : object.send("#{attribute}=", value) end # Defines one or more events for the machine and the transitions that can # be performed when those events are run. # # This method is also aliased as +on+ for improved compatibility with # using a domain-specific language. # # Configuration options: # * :human_name - The human-readable version of this event's name. # By default, this is either defined by the integration or stringifies the # name and converts underscores to spaces. # # == Instance methods # # The following instance methods are generated when a new event is defined # (the "park" event is used as an example): # * park(..., run_action = true) - Fires the "park" event, # transitioning from the current state to the next valid state. If the # last argument is a boolean, it will control whether the machine's action # gets run. # * park!(..., run_action = true) - Fires the "park" event, # transitioning from the current state to the next valid state. If the # transition fails, then a StateMachine::InvalidTransition error will be # raised. If the last argument is a boolean, it will control whether the # machine's action gets run. # * can_park?(requirements = {}) - Checks whether the "park" event # can be fired given the current state of the object. This will *not* run # validations or callbacks in ORM integrations. It will only determine if # the state machine defines a valid transition for the event. To check # whether an event can fire *and* passes validations, use event attributes # (e.g. state_event) as described in the "Events" documentation of each # ORM integration. # * park_transition(requirements = {}) - Gets the next transition # that would be performed if the "park" event were to be fired now on the # object or nil if no transitions can be performed. Like can_park? # this will also *not* run validations or callbacks. It will only # determine if the state machine defines a valid transition for the event. # # With a namespace of "car", the above names map to the following methods: # * can_park_car? # * park_car_transition # * park_car # * park_car! # # The can_park? and park_transition helpers both take an # optional set of requirements for determining what transitions are available # for the current object. These requirements include: # * :from - One or more states to transition from. If none are # specified, then this will be the object's current state. # * :to - One or more states to transition to. If none are # specified, then this will match any to state. # * :guard - Whether to guard transitions with the if/unless # conditionals defined for each one. Default is true. # # == Defining transitions # # +event+ requires a block which allows you to define the possible # transitions that can happen as a result of that event. For example, # # event :park, :stop do # transition :idling => :parked # end # # event :first_gear do # transition :parked => :first_gear, :if => :seatbelt_on? # transition :parked => same # Allow to loopback if seatbelt is off # end # # See StateMachine::Event#transition for more information on # the possible options that can be passed in. # # *Note* that this block is executed within the context of the actual event # object. As a result, you will not be able to reference any class methods # on the model without referencing the class itself. For example, # # class Vehicle # def self.safe_states # [:parked, :idling, :stalled] # end # # state_machine do # event :park do # transition Vehicle.safe_states => :parked # end # end # end # # == Overriding the event method # # By default, this will define an instance method (with the same name as the # event) that will fire the next possible transition for that. Although the # +before_transition+, +after_transition+, and +around_transition+ hooks # allow you to define behavior that gets executed as a result of the event's # transition, you can also override the event method in order to have a # little more fine-grained control. # # For example: # # class Vehicle # state_machine do # event :park do # ... # end # end # # def park(*) # take_deep_breath # Executes before the transition (and before_transition hooks) even if no transition is possible # if result = super # Runs the transition and all before/after/around hooks # applaud # Executes after the transition (and after_transition hooks) # end # result # end # end # # There are a few important things to note here. First, the method # signature is defined with an unlimited argument list in order to allow # callers to continue passing arguments that are expected by state_machine. # For example, it will still allow calls to +park+ with a single parameter # for skipping the configured action. # # Second, the overridden event method must call +super+ in order to run the # logic for running the next possible transition. In order to remain # consistent with other events, the result of +super+ is returned. # # Third, any behavior defined in this method will *not* get executed if # you're taking advantage of attribute-based event transitions. For example: # # vehicle = Vehicle.new # vehicle.state_event = 'park' # vehicle.save # # In this case, the +park+ event will run the before/after/around transition # hooks and transition the state, but the behavior defined in the overriden # +park+ method will *not* be executed. # # == Defining additional arguments # # Additional arguments can be passed into events and accessed by transition # hooks like so: # # class Vehicle # state_machine do # after_transition :on => :park do |vehicle, transition| # kind = *transition.args # :parallel # ... # end # after_transition :on => :park, :do => :take_deep_breath # # event :park do # ... # end # # def take_deep_breath(transition) # kind = *transition.args # :parallel # ... # end # end # end # # vehicle = Vehicle.new # vehicle.park(:parallel) # # *Remember* that if the last argument is a boolean, it will be used as the # +run_action+ parameter to the event action. Using the +park+ action # example from above, you can might call it like so: # # vehicle.park # => Uses default args and runs machine action # vehicle.park(:parallel) # => Specifies the +kind+ argument and runs the machine action # vehicle.park(:parallel, false) # => Specifies the +kind+ argument and *skips* the machine action # # If you decide to override the +park+ event method *and* define additional # arguments, you can do so as shown below: # # class Vehicle # state_machine do # event :park do # ... # end # end # # def park(kind = :parallel, *args) # take_deep_breath if kind == :parallel # super # end # end # # Note that +super+ is called instead of super(*args). This allow # the entire arguments list to be accessed by transition callbacks through # StateMachine::Transition#args. # # === Using matchers # # The +all+ / +any+ matchers can be used to easily execute blocks for a # group of events. Note, however, that you cannot use these matchers to # set configurations for events. Blocks using these matchers can be # defined at any point in the state machine and will always get applied to # the proper events. # # For example: # # state_machine :initial => :parked do # ... # # event all - [:crash] do # transition :stalled => :parked # end # end # # == Example # # class Vehicle # state_machine do # # The park, stop, and halt events will all share the given transitions # event :park, :stop, :halt do # transition [:idling, :backing_up] => :parked # end # # event :stop do # transition :first_gear => :idling # end # # event :ignite do # transition :parked => :idling # transition :idling => same # Allow ignite while still idling # end # end # end def event(*names, &block) options = names.last.is_a?(Hash) ? names.pop : {} assert_valid_keys(options, :human_name) # Store the context so that it can be used for / matched against any event # that gets added @events.context(names, &block) if block_given? if names.first.is_a?(Matcher) # Add any events referenced in the matcher. When matchers are used, # events are not allowed to be configured. raise ArgumentError, "Cannot configure events when using matchers (using #{options.inspect})" if options.any? events = add_events(names.first.values) else events = add_events(names) # Update the configuration for the event(s) events.each do |event| event.human_name = options[:human_name] if options.include?(:human_name) # Add any states that may have been referenced within the event add_states(event.known_states) end end events.length == 1 ? events.first : events end alias_method :on, :event # Creates a new transition that determines what to change the current state # to when an event fires. # # == Defining transitions # # The options for a new transition uses the Hash syntax to map beginning # states to ending states. For example, # # transition :parked => :idling, :idling => :first_gear, :on => :ignite # # In this case, when the +ignite+ event is fired, this transition will cause # the state to be +idling+ if it's current state is +parked+ or +first_gear+ # if it's current state is +idling+. # # To help define these implicit transitions, a set of helpers are available # for slightly more complex matching: # * all - Matches every state in the machine # * all - [:parked, :idling, ...] - Matches every state except those specified # * any - An alias for +all+ (matches every state in the machine) # * same - Matches the same state being transitioned from # # See StateMachine::MatcherHelpers for more information. # # Examples: # # transition all => nil, :on => :ignite # Transitions to nil regardless of the current state # transition all => :idling, :on => :ignite # Transitions to :idling regardless of the current state # transition all - [:idling, :first_gear] => :idling, :on => :ignite # Transitions every state but :idling and :first_gear to :idling # transition nil => :idling, :on => :ignite # Transitions to :idling from the nil state # transition :parked => :idling, :on => :ignite # Transitions to :idling if :parked # transition [:parked, :stalled] => :idling, :on => :ignite # Transitions to :idling if :parked or :stalled # # transition :parked => same, :on => :park # Loops :parked back to :parked # transition [:parked, :stalled] => same, :on => [:park, :stall] # Loops either :parked or :stalled back to the same state on the park and stall events # transition all - :parked => same, :on => :noop # Loops every state but :parked back to the same state # # # Transitions to :idling if :parked, :first_gear if :idling, or :second_gear if :first_gear # transition :parked => :idling, :idling => :first_gear, :first_gear => :second_gear, :on => :shift_up # # == Verbose transitions # # Transitions can also be defined use an explicit set of configuration # options: # * :from - A state or array of states that can be transitioned from. # If not specified, then the transition can occur for *any* state. # * :to - The state that's being transitioned to. If not specified, # then the transition will simply loop back (i.e. the state will not change). # * :except_from - A state or array of states that *cannot* be # transitioned from. # # These options must be used when defining transitions within the context # of a state. # # Examples: # # transition :to => nil, :on => :park # transition :to => :idling, :on => :ignite # transition :except_from => [:idling, :first_gear], :to => :idling, :on => :ignite # transition :from => nil, :to => :idling, :on => :ignite # transition :from => [:parked, :stalled], :to => :idling, :on => :ignite # # == Conditions # # In addition to the state requirements for each transition, a condition # can also be defined to help determine whether that transition is # available. These options will work on both the normal and verbose syntax. # # Configuration options: # * :if - A method, proc or string to call to determine if the # transition should occur (e.g. :if => :moving?, or :if => lambda {|vehicle| vehicle.speed > 60}). # The condition should return or evaluate to true or false. # * :unless - A method, proc or string to call to determine if the # transition should not occur (e.g. :unless => :stopped?, or :unless => lambda {|vehicle| vehicle.speed <= 60}). # The condition should return or evaluate to true or false. # # Examples: # # transition :parked => :idling, :on => :ignite, :if => :moving? # transition :parked => :idling, :on => :ignite, :unless => :stopped? # transition :idling => :first_gear, :first_gear => :second_gear, :on => :shift_up, :if => :seatbelt_on? # # transition :from => :parked, :to => :idling, :on => ignite, :if => :moving? # transition :from => :parked, :to => :idling, :on => ignite, :unless => :stopped? # # == Order of operations # # Transitions are evaluated in the order in which they're defined. As a # result, if more than one transition applies to a given object, then the # first transition that matches will be performed. def transition(options) raise ArgumentError, 'Must specify :on event' unless options[:on] branches = [] options = options.dup event(*Array(options.delete(:on))) { branches << transition(options) } branches.length == 1 ? branches.first : branches end # Creates a callback that will be invoked *before* a transition is # performed so long as the given requirements match the transition. # # == The callback # # Callbacks must be defined as either an argument, in the :do option, or # as a block. For example, # # class Vehicle # state_machine do # before_transition :set_alarm # before_transition :set_alarm, all => :parked # before_transition all => :parked, :do => :set_alarm # before_transition all => :parked do |vehicle, transition| # vehicle.set_alarm # end # ... # end # end # # Notice that the first three callbacks are the same in terms of how the # methods to invoke are defined. However, using the :do can # provide for a more fluid DSL. # # In addition, multiple callbacks can be defined like so: # # class Vehicle # state_machine do # before_transition :set_alarm, :lock_doors, all => :parked # before_transition all => :parked, :do => [:set_alarm, :lock_doors] # before_transition :set_alarm do |vehicle, transition| # vehicle.lock_doors # end # end # end # # Notice that the different ways of configuring methods can be mixed. # # == State requirements # # Callbacks can require that the machine be transitioning from and to # specific states. These requirements use a Hash syntax to map beginning # states to ending states. For example, # # before_transition :parked => :idling, :idling => :first_gear, :do => :set_alarm # # In this case, the +set_alarm+ callback will only be called if the machine # is transitioning from +parked+ to +idling+ or from +idling+ to +parked+. # # To help define state requirements, a set of helpers are available for # slightly more complex matching: # * all - Matches every state/event in the machine # * all - [:parked, :idling, ...] - Matches every state/event except those specified # * any - An alias for +all+ (matches every state/event in the machine) # * same - Matches the same state being transitioned from # # See StateMachine::MatcherHelpers for more information. # # Examples: # # before_transition :parked => [:idling, :first_gear], :do => ... # Matches from parked to idling or first_gear # before_transition all - [:parked, :idling] => :idling, :do => ... # Matches from every state except parked and idling to idling # before_transition all => :parked, :do => ... # Matches all states to parked # before_transition any => same, :do => ... # Matches every loopback # # == Event requirements # # In addition to state requirements, an event requirement can be defined so # that the callback is only invoked on specific events using the +on+ # option. This can also use the same matcher helpers as the state # requirements. # # Examples: # # before_transition :on => :ignite, :do => ... # Matches only on ignite # before_transition :on => all - :ignite, :do => ... # Matches on every event except ignite # before_transition :parked => :idling, :on => :ignite, :do => ... # Matches from parked to idling on ignite # # == Verbose Requirements # # Requirements can also be defined using verbose options rather than the # implicit Hash syntax and helper methods described above. # # Configuration options: # * :from - One or more states being transitioned from. If none # are specified, then all states will match. # * :to - One or more states being transitioned to. If none are # specified, then all states will match. # * :on - One or more events that fired the transition. If none # are specified, then all events will match. # * :except_from - One or more states *not* being transitioned from # * :except_to - One more states *not* being transitioned to # * :except_on - One or more events that *did not* fire the transition # # Examples: # # before_transition :from => :ignite, :to => :idling, :on => :park, :do => ... # before_transition :except_from => :ignite, :except_to => :idling, :except_on => :park, :do => ... # # == Conditions # # In addition to the state/event requirements, a condition can also be # defined to help determine whether the callback should be invoked. # # Configuration options: # * :if - A method, proc or string to call to determine if the # callback should occur (e.g. :if => :allow_callbacks, or # :if => lambda {|user| user.signup_step > 2}). The method, proc or string # should return or evaluate to a true or false value. # * :unless - A method, proc or string to call to determine if the # callback should not occur (e.g. :unless => :skip_callbacks, or # :unless => lambda {|user| user.signup_step <= 2}). The method, proc or # string should return or evaluate to a true or false value. # # Examples: # # before_transition :parked => :idling, :if => :moving?, :do => ... # before_transition :on => :ignite, :unless => :seatbelt_on?, :do => ... # # == Accessing the transition # # In addition to passing the object being transitioned, the actual # transition describing the context (e.g. event, from, to) can be accessed # as well. This additional argument is only passed if the callback allows # for it. # # For example, # # class Vehicle # # Only specifies one parameter (the object being transitioned) # before_transition all => :parked do |vehicle| # vehicle.set_alarm # end # # # Specifies 2 parameters (object being transitioned and actual transition) # before_transition all => :parked do |vehicle, transition| # vehicle.set_alarm(transition) # end # end # # *Note* that the object in the callback will only be passed in as an # argument if callbacks are configured to *not* be bound to the object # involved. This is the default and may change on a per-integration basis. # # See StateMachine::Transition for more information about the # attributes available on the transition. # # == Usage with delegates # # As noted above, state_machine uses the callback method's argument list # arity to determine whether to include the transition in the method call. # If you're using delegates, such as those defined in ActiveSupport or # Forwardable, the actual arity of the delegated method gets masked. This # means that callbacks which reference delegates will always get passed the # transition as an argument. For example: # # class Vehicle # extend Forwardable # delegate :refresh => :dashboard # # state_machine do # before_transition :refresh # ... # end # # def dashboard # @dashboard ||= Dashboard.new # end # end # # class Dashboard # def refresh(transition) # # ... # end # end # # In the above example, Dashboard#refresh *must* defined a # +transition+ argument. Otherwise, an +ArgumentError+ exception will get # raised. The only way around this is to avoid the use of delegates and # manually define the delegate method so that the correct arity is used. # # == Examples # # Below is an example of a class with one state machine and various types # of +before+ transitions defined for it: # # class Vehicle # state_machine do # # Before all transitions # before_transition :update_dashboard # # # Before specific transition: # before_transition [:first_gear, :idling] => :parked, :on => :park, :do => :take_off_seatbelt # # # With conditional callback: # before_transition all => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on? # # # Using helpers: # before_transition all - :stalled => same, :on => any - :crash, :do => :update_dashboard # ... # end # end # # As can be seen, any number of transitions can be created using various # combinations of configuration options. def before_transition(*args, &block) options = (args.last.is_a?(Hash) ? args.pop : {}) options[:do] = args if args.any? add_callback(:before, options, &block) end # Creates a callback that will be invoked *after* a transition is # performed so long as the given requirements match the transition. # # See +before_transition+ for a description of the possible configurations # for defining callbacks. def after_transition(*args, &block) options = (args.last.is_a?(Hash) ? args.pop : {}) options[:do] = args if args.any? add_callback(:after, options, &block) end # Creates a callback that will be invoked *around* a transition so long as # the given requirements match the transition. # # == The callback # # Around callbacks wrap transitions, executing code both before and after. # These callbacks are defined in the exact same manner as before / after # callbacks with the exception that the transition must be yielded to in # order to finish running it. # # If defining +around+ callbacks using blocks, you must yield within the # transition by directly calling the block (since yielding is not allowed # within blocks). # # For example, # # class Vehicle # state_machine do # around_transition do |block| # Benchmark.measure { block.call } # end # # around_transition do |vehicle, block| # logger.info "vehicle was #{state}..." # block.call # logger.info "...and is now #{state}" # end # # around_transition do |vehicle, transition, block| # logger.info "before #{transition.event}: #{vehicle.state}" # block.call # logger.info "after #{transition.event}: #{vehicle.state}" # end # end # end # # Notice that referencing the block is similar to doing so within an # actual method definition in that it is always the last argument. # # On the other hand, if you're defining +around+ callbacks using method # references, you can yield like normal: # # class Vehicle # state_machine do # around_transition :benchmark # ... # end # # def benchmark # Benchmark.measure { yield } # end # end # # See +before_transition+ for a description of the possible configurations # for defining callbacks. def around_transition(*args, &block) options = (args.last.is_a?(Hash) ? args.pop : {}) options[:do] = args if args.any? add_callback(:around, options, &block) end # Creates a callback that will be invoked *after* a transition failures to # be performed so long as the given requirements match the transition. # # See +before_transition+ for a description of the possible configurations # for defining callbacks. *Note* however that you cannot define the state # requirements in these callbacks. You may only define event requirements. # # = The callback # # Failure callbacks get invoked whenever an event fails to execute. This # can happen when no transition is available, a +before+ callback halts # execution, or the action associated with this machine fails to succeed. # In any of these cases, any failure callback that matches the attempted # transition will be run. # # For example, # # class Vehicle # state_machine do # after_failure do |vehicle, transition| # logger.error "vehicle #{vehicle} failed to transition on #{transition.event}" # end # # after_failure :on => :ignite, :do => :log_ignition_failure # # ... # end # end def after_failure(*args, &block) options = (args.last.is_a?(Hash) ? args.pop : {}) options[:do] = args if args.any? assert_valid_keys(options, :on, :do, :if, :unless) add_callback(:failure, options, &block) end # Generates a list of the possible transition sequences that can be run on # the given object. These paths can reveal all of the possible states and # events that can be encountered in the object's state machine based on the # object's current state. # # Configuration options: # * +from+ - The initial state to start all paths from. By default, this # is the object's current state. # * +to+ - The target state to end all paths on. By default, paths will # end when they loop back to the first transition on the path. # * +deep+ - Whether to allow the target state to be crossed more than once # in a path. By default, paths will immediately stop when the target # state (if specified) is reached. If this is enabled, then paths can # continue even after reaching the target state; they will stop when # reaching the target state a second time. # # *Note* that the object is never modified when the list of paths is # generated. # # == Examples # # class Vehicle # state_machine :initial => :parked do # event :ignite do # transition :parked => :idling # end # # event :shift_up do # transition :idling => :first_gear, :first_gear => :second_gear # end # # event :shift_down do # transition :second_gear => :first_gear, :first_gear => :idling # end # end # end # # vehicle = Vehicle.new # => # # vehicle.state # => "parked" # # vehicle.state_paths # # => [ # # [#, # # #, # # #, # # #, # # #], # # # # [#, # # #, # # #] # # ] # # vehicle.state_paths(:from => :parked, :to => :second_gear) # # => [ # # [#, # # #, # # #] # # ] # # In addition to getting the possible paths that can be accessed, you can # also get summary information about the states / events that can be # accessed at some point along one of the paths. For example: # # # Get the list of states that can be accessed from the current state # vehicle.state_paths.to_states # => [:idling, :first_gear, :second_gear] # # # Get the list of events that can be accessed from the current state # vehicle.state_paths.events # => [:ignite, :shift_up, :shift_down] def paths_for(object, requirements = {}) PathCollection.new(object, self, requirements) end # Marks the given object as invalid with the given message. # # By default, this is a no-op. def invalidate(object, attribute, message, values = []) end # Gets a description of the errors for the given object. This is used to # provide more detailed information when an InvalidTransition exception is # raised. def errors_for(object) '' end # Resets any errors previously added when invalidating the given object. # # By default, this is a no-op. def reset(object) end # Generates the message to use when invalidating the given object after # failing to transition on a specific event def generate_message(name, values = []) message = (@messages[name] || self.class.default_messages[name]) # Check whether there are actually any values to interpolate to avoid # any warnings if message.scan(/%./).any? {|match| match != '%%'} message % values.map {|value| value.last} else message end end # Runs a transaction, rolling back any changes if the yielded block fails. # # This is only applicable to integrations that involve databases. By # default, this will not run any transactions since the changes aren't # taking place within the context of a database. def within_transaction(object) if use_transactions transaction(object) { yield } else yield end end # Draws a directed graph of the machine for visualizing the various events, # states, and their transitions. # # This requires both the Ruby graphviz gem and the graphviz library be # installed on the system. # # Configuration options: # * :name - The name of the file to write to (without the file extension). # Default is "#{owner_class.name}_#{name}" # * :path - The path to write the graph file to. Default is the # current directory ("."). # * :format - The image format to generate the graph in. # Default is "png'. # * :font - The name of the font to draw state names in. # Default is "Arial". # * :orientation - The direction of the graph ("portrait" or # "landscape"). Default is "portrait". # * :human_names - Whether to use human state / event names for # node labels on the graph instead of the internal name. Default is false. def draw(graph_options = {}) name = graph_options.delete(:name) || "#{owner_class.name}_#{self.name}" draw_options = {:human_name => false} draw_options[:human_name] = graph_options.delete(:human_names) if graph_options.include?(:human_names) graph = Graph.new(name, graph_options) # Add nodes / edges states.by_priority.each {|state| state.draw(graph, draw_options)} events.each {|event| event.draw(graph, draw_options)} # Output result graph.output graph end # Determines whether an action hook was defined for firing attribute-based # event transitions when the configured action gets called. def action_hook?(self_only = false) @action_hook_defined || !self_only && owner_class.state_machines.any? {|name, machine| machine.action == action && machine != self && machine.action_hook?(true)} end protected # Runs additional initialization hooks. By default, this is a no-op. def after_initialize end # Looks up other machines that have been defined in the owner class and # are targeting the same attribute as this machine. When accessing # sibling machines, they will be automatically copied for the current # class if they haven't been already. This ensures that any configuration # changes made to the sibling machines only affect this class and not any # base class that may have originally defined the machine. def sibling_machines owner_class.state_machines.inject([]) do |machines, (name, machine)| if machine.attribute == attribute && machine != self machines << (owner_class.state_machine(name) {}) end machines end end # Determines if the machine's attribute needs to be initialized. This # will only be true if the machine's attribute is blank. def initialize_state?(object) value = read(object, :state) (value.nil? || value.respond_to?(:empty?) && value.empty?) && !states[value, :value] end # Adds helper methods for interacting with the state machine, including # for states, events, and transitions def define_helpers define_state_accessor define_state_predicate define_event_helpers define_path_helpers define_action_helpers if define_action_helpers? define_name_helpers end # Defines the initial values for state machine attributes. Static values # are set prior to the original initialize method and dynamic values are # set *after* the initialize method in case it is dependent on it. def define_state_initializer define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def initialize(*) self.class.state_machines.initialize_states(self) { super } end end_eval end # Adds reader/writer methods for accessing the state attribute def define_state_accessor attribute = self.attribute @helper_modules[:instance].class_eval { attr_reader attribute } unless owner_class_ancestor_has_method?(:instance, attribute) @helper_modules[:instance].class_eval { attr_writer attribute } unless owner_class_ancestor_has_method?(:instance, "#{attribute}=") end # Adds predicate method to the owner class for determining the name of the # current state def define_state_predicate call_super = !!owner_class_ancestor_has_method?(:instance, "#{name}?") define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def #{name}?(*args) args.empty? && (#{call_super} || defined?(super)) ? super : self.class.state_machine(#{name.inspect}).states.matches?(self, *args) end end_eval end # Adds helper methods for getting information about this state machine's # events def define_event_helpers # Gets the events that are allowed to fire on the current object define_helper(:instance, attribute(:events)) do |machine, object, *args| machine.events.valid_for(object, *args).map {|event| event.name} end # Gets the next possible transitions that can be run on the current # object define_helper(:instance, attribute(:transitions)) do |machine, object, *args| machine.events.transitions_for(object, *args) end # Fire an arbitrary event for this machine define_helper(:instance, "fire_#{attribute(:event)}") do |machine, object, event, *args| machine.events.fetch(event).fire(object, *args) end # Add helpers for tracking the event / transition to invoke when the # action is called if action event_attribute = attribute(:event) define_helper(:instance, event_attribute) do |machine, object| # Interpret non-blank events as present event = machine.read(object, :event, true) event && !(event.respond_to?(:empty?) && event.empty?) ? event.to_sym : nil end # A roundabout way of writing the attribute is used here so that # integrations can hook into this modification define_helper(:instance, "#{event_attribute}=") do |machine, object, value| machine.write(object, :event, value, true) end event_transition_attribute = attribute(:event_transition) define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 protected; attr_accessor #{event_transition_attribute.inspect} end_eval end end # Adds helper methods for getting information about this state machine's # available transition paths def define_path_helpers # Gets the paths of transitions available to the current object define_helper(:instance, attribute(:paths)) do |machine, object, *args| machine.paths_for(object, *args) end end # Determines whether action helpers should be defined for this machine. # This is only true if there is an action configured and no other machines # have process this same configuration already. def define_action_helpers? action && !owner_class.state_machines.any? {|name, machine| machine.action == action && machine != self} end # Adds helper methods for automatically firing events when an action # is invoked def define_action_helpers if action_hook @action_hook_defined = true define_action_hook end end # Hooks directly into actions by defining the same method in an included # module. As a result, when the action gets invoked, any state events # defined for the object will get run. Method visibility is preserved. def define_action_hook action_hook = self.action_hook action = self.action private_action_hook = owner_class.private_method_defined?(action_hook) # Only define helper if it hasn't define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def #{action_hook}(*) self.class.state_machines.transitions(self, #{action.inspect}).perform { super } end private #{action_hook.inspect} if #{private_action_hook} end_eval end # The method to hook into for triggering transitions when invoked. By # default, this is the action configured for the machine. # # Since the default hook technique relies on module inheritance, the # action must be defined in an ancestor of the owner classs in order for # it to be the action hook. def action_hook action && owner_class_ancestor_has_method?(:instance, action) ? action : nil end # Determines whether there's already a helper method defined within the # given scope. This is true only if one of the owner's ancestors defines # the method and is further along in the ancestor chain than this # machine's helper module. def owner_class_ancestor_has_method?(scope, method) superclasses = owner_class.ancestors[1..-1].select {|ancestor| ancestor.is_a?(Class)} if scope == :class # Use singleton classes current = (class << owner_class; self; end) superclass = superclasses.first else current = owner_class superclass = owner_class.superclass end # Generate the list of modules that *only* occur in the owner class, but # were included *prior* to the helper modules, in addition to the # superclasses ancestors = current.ancestors - superclass.ancestors + superclasses ancestors = ancestors[ancestors.index(@helper_modules[scope])..-1].reverse # Search for for the first ancestor that defined this method ancestors.detect do |ancestor| ancestor = (class << ancestor; self; end) if scope == :class && ancestor.is_a?(Class) ancestor.method_defined?(method) || ancestor.private_method_defined?(method) end end # Adds helper methods for accessing naming information about states and # events on the owner class def define_name_helpers # Gets the humanized version of a state define_helper(:class, "human_#{attribute(:name)}") do |machine, klass, state| machine.states.fetch(state).human_name(klass) end # Gets the humanized version of an event define_helper(:class, "human_#{attribute(:event_name)}") do |machine, klass, event| machine.events.fetch(event).human_name(klass) end # Gets the state name for the current value define_helper(:instance, attribute(:name)) do |machine, object| machine.states.match!(object).name end # Gets the human state name for the current value define_helper(:instance, "human_#{attribute(:name)}") do |machine, object| machine.states.match!(object).human_name(object.class) end end # Defines the with/without scope helpers for this attribute. Both the # singular and plural versions of the attribute are defined for each # scope helper. A custom plural can be specified if it cannot be # automatically determined by either calling +pluralize+ on the attribute # name or adding an "s" to the end of the name. def define_scopes(custom_plural = nil) plural = custom_plural || pluralize(name) [:with, :without].each do |kind| [name, plural].map {|s| s.to_s}.uniq.each do |suffix| method = "#{kind}_#{suffix}" if scope = send("create_#{kind}_scope", method) # Converts state names to their corresponding values so that they # can be looked up properly define_helper(:class, method) do |machine, klass, *states| run_scope(scope, machine, klass, states) end end end end end # Generates the results for the given scope based on one or more states to # filter by def run_scope(scope, machine, klass, states) values = states.flatten.map {|state| machine.states.fetch(state).value} scope.call(klass, values) end # Pluralizes the given word using #pluralize (if available) or simply # adding an "s" to the end of the word def pluralize(word) word = word.to_s if word.respond_to?(:pluralize) word.pluralize else "#{name}s" end end # Creates a scope for finding objects *with* a particular value or values # for the attribute. # # By default, this is a no-op. def create_with_scope(name) end # Creates a scope for finding objects *without* a particular value or # values for the attribute. # # By default, this is a no-op. def create_without_scope(name) end # Always yields def transaction(object) yield end # Gets the initial attribute value defined by the owner class (outside of # the machine's definition). By default, this is always nil. def owner_class_attribute_default nil end # Checks whether the given state matches the attribute default specified # by the owner class def owner_class_attribute_default_matches?(state) state.matches?(owner_class_attribute_default) end # Updates this machine based on the configuration of other machines in the # owner class that share the same target attribute. def add_sibling_machine_configs # Add existing states sibling_machines.each do |machine| machine.states.each {|state| states << state unless states[state.name]} end end # Adds a new transition callback of the given type. def add_callback(type, options, &block) callbacks[type == :around ? :before : type] << callback = Callback.new(type, options, &block) add_states(callback.known_states) callback end # Tracks the given set of states in the list of all known states for # this machine def add_states(new_states) new_states.map do |new_state| # Check for other states that use a different class type for their name. # This typically prevents string / symbol misuse. if new_state && conflict = states.detect {|state| state.name && state.name.class != new_state.class} raise ArgumentError, "#{new_state.inspect} state defined as #{new_state.class}, #{conflict.name.inspect} defined as #{conflict.name.class}; all states must be consistent" end unless state = states[new_state] states << state = State.new(self, new_state) # Copy states over to sibling machines sibling_machines.each {|machine| machine.states << state} end state end end # Tracks the given set of events in the list of all known events for # this machine def add_events(new_events) new_events.map do |new_event| # Check for other states that use a different class type for their name. # This typically prevents string / symbol misuse. if conflict = events.detect {|event| event.name.class != new_event.class} raise ArgumentError, "#{new_event.inspect} event defined as #{new_event.class}, #{conflict.name.inspect} defined as #{conflict.name.class}; all events must be consistent" end unless event = events[new_event] events << event = Event.new(self, new_event) end event end end end end state-machine-1.2.0/lib/state_machine/event.rb0000644000175000017500000002366212305405267020647 0ustar boutilboutilrequire 'state_machine/transition' require 'state_machine/branch' require 'state_machine/assertions' require 'state_machine/matcher_helpers' require 'state_machine/error' module StateMachine # An invalid event was specified class InvalidEvent < Error # The event that was attempted to be run attr_reader :event def initialize(object, event_name) #:nodoc: @event = event_name super(object, "#{event.inspect} is an unknown state machine event") end end # An event defines an action that transitions an attribute from one state to # another. The state that an attribute is transitioned to depends on the # branches configured for the event. class Event include Assertions include MatcherHelpers # The state machine for which this event is defined attr_accessor :machine # The name of the event attr_reader :name # The fully-qualified name of the event, scoped by the machine's namespace attr_reader :qualified_name # The human-readable name for the event attr_writer :human_name # The list of branches that determine what state this event transitions # objects to when fired attr_reader :branches # A list of all of the states known to this event using the configured # branches/transitions as the source attr_reader :known_states # Creates a new event within the context of the given machine # # Configuration options: # * :human_name - The human-readable version of this event's name def initialize(machine, name, options = {}) #:nodoc: assert_valid_keys(options, :human_name) @machine = machine @name = name @qualified_name = machine.namespace ? :"#{name}_#{machine.namespace}" : name @human_name = options[:human_name] || @name.to_s.tr('_', ' ') reset # Output a warning if another event has a conflicting qualified name if conflict = machine.owner_class.state_machines.detect {|other_name, other_machine| other_machine != @machine && other_machine.events[qualified_name, :qualified_name]} name, other_machine = conflict warn "Event #{qualified_name.inspect} for #{machine.name.inspect} is already defined in #{other_machine.name.inspect}" else add_actions end end # Creates a copy of this event in addition to the list of associated # branches to prevent conflicts across events within a class hierarchy. def initialize_copy(orig) #:nodoc: super @branches = @branches.dup @known_states = @known_states.dup end # Transforms the event name into a more human-readable format, such as # "turn on" instead of "turn_on" def human_name(klass = @machine.owner_class) @human_name.is_a?(Proc) ? @human_name.call(self, klass) : @human_name end # Evaluates the given block within the context of this event. This simply # provides a DSL-like syntax for defining transitions. def context(&block) instance_eval(&block) end # Creates a new transition that determines what to change the current state # to when this event fires. # # Since this transition is being defined within an event context, you do # *not* need to specify the :on option for the transition. For # example: # # state_machine do # event :ignite do # transition :parked => :idling, :idling => same, :if => :seatbelt_on? # Transitions to :idling if seatbelt is on # transition all => :parked, :unless => :seatbelt_on? # Transitions to :parked if seatbelt is off # end # end # # See StateMachine::Machine#transition for a description of the possible # configurations for defining transitions. def transition(options) raise ArgumentError, 'Must specify as least one transition requirement' if options.empty? # Only a certain subset of explicit options are allowed for transition # requirements assert_valid_keys(options, :from, :to, :except_from, :except_to, :if, :unless) if (options.keys - [:from, :to, :on, :except_from, :except_to, :except_on, :if, :unless]).empty? branches << branch = Branch.new(options.merge(:on => name)) @known_states |= branch.known_states branch end # Determines whether any transitions can be performed for this event based # on the current state of the given object. # # If the event can't be fired, then this will return false, otherwise true. # # *Note* that this will not take the object context into account. Although # a transition may be possible based on the state machine definition, # object-specific behaviors (like validations) may prevent it from firing. def can_fire?(object, requirements = {}) !transition_for(object, requirements).nil? end # Finds and builds the next transition that can be performed on the given # object. If no transitions can be made, then this will return nil. # # Valid requirement options: # * :from - One or more states being transitioned from. If none # are specified, then this will be the object's current state. # * :to - One or more states being transitioned to. If none are # specified, then this will match any to state. # * :guard - Whether to guard transitions with the if/unless # conditionals defined for each one. Default is true. def transition_for(object, requirements = {}) assert_valid_keys(requirements, :from, :to, :guard) requirements[:from] = machine.states.match!(object).name unless custom_from_state = requirements.include?(:from) branches.each do |branch| if match = branch.match(object, requirements) # Branch allows for the transition to occur from = requirements[:from] to = if match[:to].is_a?(LoopbackMatcher) from else values = requirements.include?(:to) ? [requirements[:to]].flatten : [from] | machine.states.keys(:name) match[:to].filter(values).first end return Transition.new(object, machine, name, from, to, !custom_from_state) end end # No transition matched nil end # Attempts to perform the next available transition on the given object. # If no transitions can be made, then this will return false, otherwise # true. # # Any additional arguments are passed to the StateMachine::Transition#perform # instance method. def fire(object, *args) machine.reset(object) if transition = transition_for(object) transition.perform(*args) else on_failure(object) false end end # Marks the object as invalid and runs any failure callbacks associated with # this event. This should get called anytime this event fails to transition. def on_failure(object) state = machine.states.match!(object) machine.invalidate(object, :state, :invalid_transition, [[:event, human_name(object.class)], [:state, state.human_name(object.class)]]) Transition.new(object, machine, name, state.name, state.name).run_callbacks(:before => false) end # Resets back to the initial state of the event, with no branches / known # states associated. This allows you to redefine an event in situations # where you either are re-using an existing state machine implementation # or are subclassing machines. def reset @branches = [] @known_states = [] end # Draws a representation of this event on the given graph. This will # create 1 or more edges on the graph for each branch (i.e. transition) # configured. # # Configuration options: # * :human_name - Whether to use the event's human name for the # node's label that gets drawn on the graph def draw(graph, options = {}) valid_states = machine.states.by_priority.map {|state| state.name} branches.each do |branch| branch.draw(graph, options[:human_name] ? human_name : name, valid_states) end true end # Generates a nicely formatted description of this event's contents. # # For example, # # event = StateMachine::Event.new(machine, :park) # event.transition all - :idling => :parked, :idling => same # event # => # :parked, :idling => same]> def inspect transitions = branches.map do |branch| branch.state_requirements.map do |state_requirement| "#{state_requirement[:from].description} => #{state_requirement[:to].description}" end * ', ' end "#<#{self.class} name=#{name.inspect} transitions=[#{transitions * ', '}]>" end protected # Add the various instance methods that can transition the object using # the current event def add_actions # Checks whether the event can be fired on the current object machine.define_helper(:instance, "can_#{qualified_name}?") do |machine, object, *args| machine.event(name).can_fire?(object, *args) end # Gets the next transition that would be performed if the event were # fired now machine.define_helper(:instance, "#{qualified_name}_transition") do |machine, object, *args| machine.event(name).transition_for(object, *args) end # Fires the event machine.define_helper(:instance, qualified_name) do |machine, object, *args| machine.event(name).fire(object, *args) end # Fires the event, raising an exception if it fails machine.define_helper(:instance, "#{qualified_name}!") do |machine, object, *args| object.send(qualified_name, *args) || raise(StateMachine::InvalidTransition.new(object, machine, name)) end end end end state-machine-1.2.0/lib/state_machine/integrations/0000755000175000017500000000000012305405267021676 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/integrations/mongo_mapper/0000755000175000017500000000000012305405267024361 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/integrations/mongo_mapper/versions.rb0000644000175000017500000000530512305405267026561 0ustar boutilboutilmodule StateMachine module Integrations #:nodoc: module MongoMapper version '0.5.x - 0.6.x' do def self.active? !defined?(::MongoMapper::Plugins) end def filter_attributes(object, attributes) attributes end def define_state_initializer define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def initialize(*args) attrs, * = args attrs && attrs.stringify_keys.key?('_id') ? super : self.class.state_machines.initialize_states(self, :static => :force) { super } end end_eval end end version '0.5.x - 0.7.x' do def self.active? !defined?(::MongoMapper::Version) || ::MongoMapper::Version =~ /^0\.[5-7]\./ end def define_scope(name, scope) lambda {|model, values| model.all(scope.call(values))} end end version '0.5.x - 0.8.x' do def self.active? !defined?(::MongoMapper::Version) || ::MongoMapper::Version =~ /^0\.[5-8]\./ end def invalidate(object, attribute, message, values = []) object.errors.add(self.attribute(attribute), generate_message(message, values)) end def define_state_accessor owner_class.key(attribute, String) unless owner_class.keys.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 def load_locale end def supports_observers? false end def supports_validations? true end def callback_terminator end def translate(klass, key, value) value.to_s.humanize.downcase end end version '0.7.x - 0.8.3' do def self.active? # Only 0.8.x and up has a Version string available, so Plugins is used # to detect when 0.7.x is active defined?(::MongoMapper::Plugins) && (!defined?(::MongoMapper::Version) || ::MongoMapper::Version =~ /^0\.(7|8\.[0-3])\./) end def define_state_initializer define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def initialize(*args) from_db = args[1] from_db ? super : self.class.state_machines.initialize_states(self, :static => :force) { super } end end_eval end end end end end state-machine-1.2.0/lib/state_machine/integrations/mongo_mapper/locale.rb0000644000175000017500000000034412305405267026146 0ustar boutilboutilfilename = "#{File.dirname(__FILE__)}/../active_model/locale.rb" translations = eval(IO.read(File.expand_path(filename)), binding, filename) translations[:en][:mongo_mapper] = translations[:en].delete(:activemodel) translations state-machine-1.2.0/lib/state_machine/integrations/base.rb0000644000175000017500000000646412305405267023147 0ustar boutilboutilmodule StateMachine module Integrations # Provides a set of base helpers for managing individual integrations module Base module ClassMethods # The default options to use for state machines using this integration attr_reader :defaults # The name of the integration def integration_name @integration_name ||= begin name = self.name.split('::').last name.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') name.gsub!(/([a-z\d])([A-Z])/,'\1_\2') name.downcase! name.to_sym end end # Whether this integration is available for the current library. This # is only true if the ORM that the integration is for is currently # defined. def available? matching_ancestors.any? && Object.const_defined?(matching_ancestors[0].split('::')[0]) end # The list of ancestor names that cause this integration to matched. def matching_ancestors [] end # Whether the integration should be used for the given class. def matches?(klass) matches_ancestors?(klass.ancestors.map {|ancestor| ancestor.name}) end # Whether the integration should be used for the given list of ancestors. def matches_ancestors?(ancestors) (ancestors & matching_ancestors).any? end # Tracks the various version overrides for an integration def versions @versions ||= [] end # Creates a new version override for an integration. When this # integration is activated, each version that is marked as active will # also extend the integration. # # == Example # # module StateMachine # module Integrations # module ORMLibrary # version '0.2.x - 0.3.x' do # def self.active? # ::ORMLibrary::VERSION >= '0.2.0' && ::ORMLibrary::VERSION < '0.4.0' # end # # def invalidate(object, attribute, message, values = []) # # Override here... # end # end # end # end # end # # In the above example, a version override is defined for the ORMLibrary # integration when the version is between 0.2.x and 0.3.x. def version(name, &block) versions << mod = Module.new(&block) mod end # The path to the locale file containing translations for this # integration. This file will only exist for integrations that actually # support i18n. def locale_path path = "#{File.dirname(__FILE__)}/#{integration_name}/locale.rb" path if File.exists?(path) end # Extends the given object with any version overrides that are currently # active def extended(base) versions.each do |version| base.extend(version) if version.active? end end end extend ClassMethods def self.included(base) #:nodoc: base.class_eval { extend ClassMethods } end end end end state-machine-1.2.0/lib/state_machine/integrations/mongoid.rb0000644000175000017500000004222612305405267023665 0ustar boutilboutilmodule 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.name = 'Ford Explorer' # vehicle.ignite # => true # vehicle.reload # => # # # == 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.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.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 # # == Validations # # As mentioned in StateMachine::Machine#state, you can define behaviors, # like validations, that only execute for certain states. One *important* # caveat here is that, due to a constraint in Mongoid's validation # framework, custom validators will not work as expected when defined to run # in multiple states. For example: # # class Vehicle # include Mongoid::Document # # state_machine do # ... # state :first_gear, :second_gear do # validate :speed_is_legal # end # end # end # # In this case, the :speed_is_legal validation will only get run # for the :second_gear state. To avoid this, you can define your # custom validation like so: # # class Vehicle # include Mongoid::Document # # state_machine do # ... # state :first_gear, :second_gear do # validate {|vehicle| vehicle.speed_is_legal} # end # 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.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. # # In addition, if you're using the ignite! version of the event, # then the failure reason (such as the current validation errors) will be # included in the exception that gets raised when the event fails. For # example, assuming there's a validation on a field called +name+ on the class: # # vehicle = Vehicle.new # vehicle.ignite! # => StateMachine::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank) # # == 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 # # scope :with_states, lambda {|*states| where(:state => {'$in' => states})} # # with_states also aliased to with_state # # scope :without_states, lambda {|*states| where(:state => {'$nin' => states})} # # 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. # # Note that states can also be referenced by the string version of their # name: # # Vehicle.with_state('parked') # # == 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. # # == Observers # # In addition to support for Mongoid-like hooks, there is additional support # for Mongoid observers. Because of the way Mongoid observers are designed, # there is less flexibility around the specific transitions that can be # hooked in. However, a large number of hooks *are* supported. For # example, if a transition for a record's +state+ attribute changes the # state from +parked+ to +idling+ via the +ignite+ event, the following # observer methods are supported: # * before/after/after_failure_to-_ignite_from_parked_to_idling # * before/after/after_failure_to-_ignite_from_parked # * before/after/after_failure_to-_ignite_to_idling # * before/after/after_failure_to-_ignite # * before/after/after_failure_to-_transition_state_from_parked_to_idling # * before/after/after_failure_to-_transition_state_from_parked # * before/after/after_failure_to-_transition_state_to_idling # * before/after/after_failure_to-_transition_state # * before/after/after_failure_to-_transition # # The following class shows an example of some of these hooks: # # class VehicleObserver < Mongoid::Observer # def before_save(vehicle) # # log message # end # # # Callback for :ignite event *before* the transition is performed # def before_ignite(vehicle, transition) # # log message # end # # # Callback for :ignite event *after* the transition has been performed # def after_ignite(vehicle, transition) # # put on seatbelt # end # # # Generic transition callback *before* the transition is performed # def after_transition(vehicle, transition) # Audit.log(vehicle, transition) # end # end # # More flexible transition callbacks can be defined directly within the # model as described in StateMachine::Machine#before_transition # and StateMachine::Machine#after_transition. # # To define a single observer for multiple state machines: # # class StateMachineObserver < Mongoid::Observer # observe Vehicle, Switch, Project # # def after_transition(record, transition) # Audit.log(record, transition) # end # end # # === Callback Order # # Callbacks occur in the following order. Callbacks specific to state_machine # are bolded. The remaining callbacks are part of Mongoid. # # * (-) save # * (1) *before_transition* # * (-) valid # * (2) before_validation # * (3) after_validation # * (4) before_save # * (5) before_create # * (6) after_create # * (7) after_save # * (8) *after_transition* # # == Internationalization # # Any error message that is generated from performing invalid transitions # can be localized. The following default translations are used: # # en: # mongoid: # errors: # messages: # invalid: "is invalid" # # %{value} = attribute value, %{state} = Human state name # invalid_event: "cannot transition when %{state}" # # %{value} = attribute value, %{event} = Human event name, %{state} = Human current state name # invalid_transition: "cannot transition via %{event}" # # You can override these for a specific model like so: # # en: # mongoid: # errors: # models: # user: # invalid: "is not valid" # # In addition to the above, you can also provide translations for the # various states / events in each state machine. Using the Vehicle example, # state translations will be looked for using the following keys, where # +model_name+ = "vehicle", +machine_name+ = "state" and +state_name+ = "parked": # * mongoid.state_machines.#{model_name}.#{machine_name}.states.#{state_name} # * mongoid.state_machines.#{model_name}.states.#{state_name} # * mongoid.state_machines.#{machine_name}.states.#{state_name} # * mongoid.state_machines.states.#{state_name} # # Event translations will be looked for using the following keys, where # +model_name+ = "vehicle", +machine_name+ = "state" and +event_name+ = "ignite": # * mongoid.state_machines.#{model_name}.#{machine_name}.events.#{event_name} # * mongoid.state_machines.#{model_name}.events.#{event_name} # * mongoid.state_machines.#{machine_name}.events.#{event_name} # * mongoid.state_machines.events.#{event_name} # # An example translation configuration might look like so: # # es: # mongoid: # state_machines: # states: # parked: 'estacionado' # events: # park: 'estacionarse' module Mongoid include Base include ActiveModel require 'state_machine/integrations/mongoid/versions' # The default options to use for state machines using this integration @defaults = {:action => :save} # Classes that include Mongoid::Document will automatically use the # Mongoid integration. def self.matching_ancestors %w(Mongoid::Document) end def self.extended(base) #:nodoc: require 'mongoid/version' super end protected # Only runs validations on the action if using :save def runs_validations_on_action? action == :save end # Gets the db default for the machine's attribute def owner_class_attribute_default attribute_field && attribute_field.default_val end # Gets the field for this machine's attribute (if it exists) def attribute_field owner_class.fields[attribute.to_s] || owner_class.fields[owner_class.aliased_fields[attribute.to_s]] 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 define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def initialize(*) super do |*args| self.class.state_machines.initialize_states(self, :static => false) yield(*args) if block_given? end end def apply_pre_processed_defaults defaults = {} self.class.state_machines.initialize_states(self, :static => :force, :dynamic => false, :to => defaults) defaults.each do |attr, value| send(:"\#{attr}=", value) unless attributes.include?(attr) end super 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 attribute_field super end # Uses around callbacks to run state events if using the :save hook def define_action_hook if action_hook == :save define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def insert(*) self.class.state_machine(#{name.inspect}).send(:around_save, self) { super.persisted? } self end def update(*) self.class.state_machine(#{name.inspect}).send(:around_save, self) { super } end def upsert(*) self.class.state_machine(#{name.inspect}).send(:around_save, self) { super } end end_eval else super end end # Runs state events around the machine's :save action def around_save(object) object.class.state_machines.transitions(object, action).perform { yield } end # Creates a scope for finding records *with* a particular state or # states for the attribute def create_with_scope(name) define_scope(name, lambda {|values| {attribute => {'$in' => values}}}) end # Creates a scope for finding records *without* a particular state or # states for the attribute def create_without_scope(name) define_scope(name, lambda {|values| {attribute => {'$nin' => values}}}) end # Defines a new scope with the given name def define_scope(name, scope) lambda {|model, values| model.criteria.where(scope.call(values))} end end end end state-machine-1.2.0/lib/state_machine/integrations/mongoid/0000755000175000017500000000000012305405267023332 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/integrations/mongoid/versions.rb0000644000175000017500000000462712305405267025540 0ustar boutilboutilmodule StateMachine module Integrations #:nodoc: module Mongoid version '2.x' do def self.active? ::Mongoid::VERSION =~ /^2\./ end def define_state_initializer define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def initialize(*) @attributes ||= {} self.class.state_machines.initialize_states(self, :static => :force, :dynamic => false) super do |*args| self.class.state_machines.initialize_states(self, :static => false) yield(*args) if block_given? end end end_eval end def owner_class_attribute_default attribute_field && attribute_field.default end def define_action_hook if action_hook == :save define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def insert(*) self.class.state_machine(#{name.inspect}).send(:around_save, self) { super.persisted? } self end def update(*) self.class.state_machine(#{name.inspect}).send(:around_save, self) { super } end end_eval else super end end end version '2.0.x - 2.3.x' do def self.active? ::Mongoid::VERSION =~ /^2\.[0-3]\./ end def attribute_field owner_class.fields[attribute.to_s] end end version '2.0.x - 2.2.x' do def self.active? ::Mongoid::VERSION =~ /^2\.[0-2]\./ end def define_state_initializer define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 # Initializes dynamic states def initialize(*) super do |*args| self.class.state_machines.initialize_states(self, :static => false) yield(*args) if block_given? end end # Initializes static states def apply_default_attributes(*) result = super self.class.state_machines.initialize_states(self, :static => :force, :dynamic => false, :to => result) if new_record? result end end_eval end end end end end state-machine-1.2.0/lib/state_machine/integrations/mongoid/locale.rb0000644000175000017500000000033712305405267025121 0ustar boutilboutilfilename = "#{File.dirname(__FILE__)}/../active_model/locale.rb" translations = eval(IO.read(File.expand_path(filename)), binding, filename) translations[:en][:mongoid] = translations[:en].delete(:activemodel) translations state-machine-1.2.0/lib/state_machine/integrations/mongo_mapper.rb0000644000175000017500000003440012305405267024707 0ustar boutilboutilrequire 'state_machine/integrations/active_model' module StateMachine module Integrations #:nodoc: # Adds support for integrating state machines with MongoMapper models. # # == Examples # # Below is an example of a simple state machine defined within a # MongoMapper model: # # class Vehicle # include MongoMapper::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.name = 'Ford Explorer' # vehicle.ignite # => true # vehicle.reload # => # # # == 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 MongoMapper, 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.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.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 MongoMapper::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 MongoMapper::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 # # == Validations # # As mentioned in StateMachine::Machine#state, you can define behaviors, # like validations, that only execute for certain states. One *important* # caveat here is that, due to a constraint in MongoMapper's validation # framework, custom validators will not work as expected when defined to run # in multiple states. For example: # # class Vehicle # include MongoMapper::Document # # state_machine do # ... # state :first_gear, :second_gear do # validate :speed_is_legal # end # end # end # # In this case, the :speed_is_legal validation will only get run # for the :second_gear state. To avoid this, you can define your # custom validation like so: # # class Vehicle # include MongoMapper::Document # # state_machine do # ... # state :first_gear, :second_gear do # validate {|vehicle| vehicle.speed_is_legal} # end # 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.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. # # In addition, if you're using the ignite! version of the event, # then the failure reason (such as the current validation errors) will be # included in the exception that gets raised when the event fails. For # example, assuming there's a validation on a field called +name+ on the class: # # vehicle = Vehicle.new # vehicle.ignite! # => StateMachine::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank) # # == 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 MongoMapper::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 MongoMapper, they *cannot* be # chained. # # Note that states can also be referenced by the string version of their # name: # # Vehicle.with_state('parked') # # == Callbacks # # All before/after transition callbacks defined for MongoMapper models # behave in the same way that other MongoMapper callbacks behave. The # object involved in the transition is passed in as an argument. # # For example, # # class Vehicle # include MongoMapper::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. # # === Callback Order # # Callbacks occur in the following order. Callbacks specific to state_machine # are bolded. The remaining callbacks are part of MongoMapper. # # * (-) save # * (1) *before_transition* # * (-) valid # * (2) before_validation # * (3) after_validation # * (4) before_save # * (5) before_create # * (6) after_create # * (7) after_save # * (8) *after_transition* # # == Internationalization # # Any error message that is generated from performing invalid transitions # can be localized. The following default translations are used: # # en: # mongo_mapper: # errors: # messages: # invalid: "is invalid" # # %{value} = attribute value, %{state} = Human state name # invalid_event: "cannot transition when %{state}" # # %{value} = attribute value, %{event} = Human event name, %{state} = Human current state name # invalid_transition: "cannot transition via %{event}" # # You can override these for a specific model like so: # # en: # mongo_mapper: # errors: # models: # user: # invalid: "is not valid" # # In addition to the above, you can also provide translations for the # various states / events in each state machine. Using the Vehicle example, # state translations will be looked for using the following keys, where # +model_name+ = "vehicle", +machine_name+ = "state" and +state_name+ = "parked": # * mongo_mapper.state_machines.#{model_name}.#{machine_name}.states.#{state_name} # * mongo_mapper.state_machines.#{model_name}.states.#{state_name} # * mongo_mapper.state_machines.#{machine_name}.states.#{state_name} # * mongo_mapper.state_machines.states.#{state_name} # # Event translations will be looked for using the following keys, where # +model_name+ = "vehicle", +machine_name+ = "state" and +event_name+ = "ignite": # * mongo_mapper.state_machines.#{model_name}.#{machine_name}.events.#{event_name} # * mongo_mapper.state_machines.#{model_name}.events.#{event_name} # * mongo_mapper.state_machines.#{machine_name}.events.#{event_name} # * mongo_mapper.state_machines.events.#{event_name} # # An example translation configuration might look like so: # # es: # mongo_mapper: # state_machines: # states: # parked: 'estacionado' # events: # park: 'estacionarse' module MongoMapper include Base include ActiveModel require 'state_machine/integrations/mongo_mapper/versions' # The default options to use for state machines using this integration @defaults = {:action => :save} # Classes that include MongoMapper::Document will automatically use the # MongoMapper integration. def self.matching_ancestors %w(MongoMapper::Document) end protected # Only runs validations on the action if using :save def runs_validations_on_action? action == :save end # Gets the db default for the machine's attribute def owner_class_attribute_default attribute_key && attribute_key.default_value end # Gets the Mongoid key for this machine's attribute (if it exists) def attribute_key owner_class.keys[attribute.to_s] 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 define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def initialize(*args) self.class.state_machines.initialize_states(self, :static => :force) { super } end end_eval end # Skips defining reader/writer methods since this is done automatically def define_state_accessor owner_class.key(attribute, String) unless attribute_key super end # Uses around callbacks to run state events if using the :save hook def define_action_hook if action_hook == :save define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def save(*) self.class.state_machine(#{name.inspect}).send(:around_save, self) { super } end def save!(*) self.class.state_machine(#{name.inspect}).send(:around_save, self) { super } || raise(::MongoMapper::DocumentNotValid.new(self)) end end_eval else super end end # Runs state events around the machine's :save action def around_save(object) object.class.state_machines.transitions(object, action).perform { yield } end # Creates a scope for finding records *with* a particular state or # states for the attribute def create_with_scope(name) define_scope(name, lambda {|values| {: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) define_scope(name, lambda {|values| {:conditions => {attribute => {'$nin' => values}}}}) end # Defines a new scope with the given name def define_scope(name, scope) lambda {|model, values| model.query.merge(model.query(scope.call(values)))} end end end end state-machine-1.2.0/lib/state_machine/integrations/active_record.rb0000644000175000017500000005164512305405267025047 0ustar boutilboutilrequire 'state_machine/integrations/active_model' module StateMachine module Integrations #:nodoc: # Adds support for integrating state machines with ActiveRecord models. # # == Examples # # Below is an example of a simple state machine defined within an # ActiveRecord model: # # class Vehicle < ActiveRecord::Base # 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.name = 'Ford Explorer' # vehicle.ignite # => true # vehicle.reload # => # # # == 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 ActiveRecord, 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.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.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 < ActiveRecord::Base # 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 < ActiveRecord::Base # 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 # # == Transactions # # In order to ensure that any changes made during transition callbacks # are rolled back during a failed attempt, every transition is wrapped # within a transaction. # # For example, # # class Message < ActiveRecord::Base # end # # Vehicle.state_machine do # before_transition do |vehicle, transition| # Message.create(:content => transition.inspect) # false # end # end # # vehicle = Vehicle.create # => # # vehicle.ignite # => false # Message.count # => 0 # # *Note* that only before callbacks that halt the callback chain and # failed attempts to save the record will result in the transaction being # rolled back. If an after callback halts the chain, the previous result # still applies and the transaction is *not* rolled back. # # To turn off transactions: # # class Vehicle < ActiveRecord::Base # state_machine :initial => :parked, :use_transactions => false do # ... # end # end # # == Validations # # As mentioned in StateMachine::Machine#state, you can define behaviors, # like validations, that only execute for certain states. One *important* # caveat here is that, due to a constraint in ActiveRecord's validation # framework, custom validators will not work as expected when defined to run # in multiple states. For example: # # class Vehicle < ActiveRecord::Base # state_machine do # ... # state :first_gear, :second_gear do # validate :speed_is_legal # end # end # end # # In this case, the :speed_is_legal validation will only get run # for the :second_gear state. To avoid this, you can define your # custom validation like so: # # class Vehicle < ActiveRecord::Base # state_machine do # ... # state :first_gear, :second_gear do # validate {|vehicle| vehicle.speed_is_legal} # end # 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.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. # # In addition, if you're using the ignite! version of the event, # then the failure reason (such as the current validation errors) will be # included in the exception that gets raised when the event fails. For # example, assuming there's a validation on a field called +name+ on the class: # # vehicle = Vehicle.new # vehicle.ignite! # => StateMachine::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank) # # == Scopes # # To assist in filtering models with specific states, a series of named # scopes are defined on the model for finding records with or without a # particular set of states. # # These named scopes are essentially the functional equivalent of the # following definitions: # # class Vehicle < ActiveRecord::Base # named_scope :with_states, lambda {|*states| {:conditions => {:state => states}}} # # with_states also aliased to with_state # # named_scope :without_states, lambda {|*states| {:conditions => ['state NOT IN (?)', states]}} # # 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 ActiveRecord, they can be # chained like so: # # Vehicle.with_state(:parked).all(:order => 'id DESC') # # Note that states can also be referenced by the string version of their # name: # # Vehicle.with_state('parked') # # == Callbacks # # All before/after transition callbacks defined for ActiveRecord models # behave in the same way that other ActiveRecord callbacks behave. The # object involved in the transition is passed in as an argument. # # For example, # # class Vehicle < ActiveRecord::Base # 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. # # === Failure callbacks # # +after_failure+ callbacks allow you to execute behaviors when a transition # is allowed, but fails to save. This could be useful for something like # auditing transition attempts. Since callbacks run within transactions in # ActiveRecord, a save failure will cause any records that get created in # your callback to roll back. You can work around this issue like so: # # class TransitionLog < ActiveRecord::Base # establish_connection Rails.env.to_sym # end # # class Vehicle < ActiveRecord::Base # state_machine do # after_failure do |vehicle, transition| # TransitionLog.create(:vehicle => vehicle, :transition => transition) # end # # ... # end # end # # The +TransitionLog+ model establishes a second connection to the database # that allows new records to be saved without being affected by rollbacks # in the +Vehicle+ model's transaction. # # === Callback Order # # Callbacks occur in the following order. Callbacks specific to state_machine # are bolded. The remaining callbacks are part of ActiveRecord. # # * (-) save # * (-) begin transaction (if enabled) # * (1) *before_transition* # * (-) valid # * (2) before_validation # * (-) validate # * (3) after_validation # * (4) before_save # * (5) before_create # * (-) create # * (6) after_create # * (7) after_save # * (8) *after_transition* # * (-) end transaction (if enabled) # * (9) after_commit # # == Observers # # In addition to support for ActiveRecord-like hooks, there is additional # support for ActiveRecord observers. Because of the way ActiveRecord # observers are designed, there is less flexibility around the specific # transitions that can be hooked in. However, a large number of hooks # *are* supported. For example, if a transition for a record's +state+ # attribute changes the state from +parked+ to +idling+ via the +ignite+ # event, the following observer methods are supported: # * before/after/after_failure_to-_ignite_from_parked_to_idling # * before/after/after_failure_to-_ignite_from_parked # * before/after/after_failure_to-_ignite_to_idling # * before/after/after_failure_to-_ignite # * before/after/after_failure_to-_transition_state_from_parked_to_idling # * before/after/after_failure_to-_transition_state_from_parked # * before/after/after_failure_to-_transition_state_to_idling # * before/after/after_failure_to-_transition_state # * before/after/after_failure_to-_transition # # The following class shows an example of some of these hooks: # # class VehicleObserver < ActiveRecord::Observer # def before_save(vehicle) # # log message # end # # # Callback for :ignite event *before* the transition is performed # def before_ignite(vehicle, transition) # # log message # end # # # Callback for :ignite event *after* the transition has been performed # def after_ignite(vehicle, transition) # # put on seatbelt # end # # # Generic transition callback *before* the transition is performed # def after_transition(vehicle, transition) # Audit.log(vehicle, transition) # end # end # # More flexible transition callbacks can be defined directly within the # model as described in StateMachine::Machine#before_transition # and StateMachine::Machine#after_transition. # # To define a single observer for multiple state machines: # # class StateMachineObserver < ActiveRecord::Observer # observe Vehicle, Switch, Project # # def after_transition(record, transition) # Audit.log(record, transition) # end # end # # == Internationalization # # In Rails 2.2+, any error message that is generated from performing invalid # transitions can be localized. The following default translations are used: # # en: # activerecord: # errors: # messages: # invalid: "is invalid" # # %{value} = attribute value, %{state} = Human state name # invalid_event: "cannot transition when %{state}" # # %{value} = attribute value, %{event} = Human event name, %{state} = Human current state name # invalid_transition: "cannot transition via %{event}" # # Notice that the interpolation syntax is %{key} in Rails 3+. In Rails 2.x, # the appropriate syntax is {{key}}. # # You can override these for a specific model like so: # # en: # activerecord: # errors: # models: # user: # invalid: "is not valid" # # In addition to the above, you can also provide translations for the # various states / events in each state machine. Using the Vehicle example, # state translations will be looked for using the following keys, where # +model_name+ = "vehicle", +machine_name+ = "state" and +state_name+ = "parked": # * activerecord.state_machines.#{model_name}.#{machine_name}.states.#{state_name} # * activerecord.state_machines.#{model_name}.states.#{state_name} # * activerecord.state_machines.#{machine_name}.states.#{state_name} # * activerecord.state_machines.states.#{state_name} # # Event translations will be looked for using the following keys, where # +model_name+ = "vehicle", +machine_name+ = "state" and +event_name+ = "ignite": # * activerecord.state_machines.#{model_name}.#{machine_name}.events.#{event_name} # * activerecord.state_machines.#{model_name}.events.#{event_name} # * activerecord.state_machines.#{machine_name}.events.#{event_name} # * activerecord.state_machines.events.#{event_name} # # An example translation configuration might look like so: # # es: # activerecord: # state_machines: # states: # parked: 'estacionado' # events: # park: 'estacionarse' module ActiveRecord include Base include ActiveModel require 'state_machine/integrations/active_record/versions' # The default options to use for state machines using this integration @defaults = {:action => :save} # Classes that inherit from ActiveRecord::Base will automatically use # the ActiveRecord integration. def self.matching_ancestors %w(ActiveRecord::Base) end def self.extended(base) #:nodoc: require 'active_record/version' super end protected # Only runs validations on the action if using :save def runs_validations_on_action? action == :save end # Gets the db default for the machine's attribute def owner_class_attribute_default if owner_class.connected? && owner_class.table_exists? && column = owner_class.columns_hash[attribute.to_s] column.default 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 define_static_state_initializer define_dynamic_state_initializer end # Initializes static states def define_static_state_initializer # This is the only available hook where the default set of attributes # can be overridden for a new object *prior* to the processing of the # attributes passed into #initialize define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1 def column_defaults(*) #:nodoc: result = super # No need to pass in an object, since the overrides will be forced self.state_machines.initialize_states(nil, :static => :force, :dynamic => false, :to => result) result end end_eval end # Initializes dynamic states def define_dynamic_state_initializer define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def initialize(*) super do |*args| self.class.state_machines.initialize_states(self, :static => false) yield(*args) if block_given? end end end_eval end # Uses around callbacks to run state events if using the :save hook def define_action_hook if action_hook == :save define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def save(*) self.class.state_machine(#{name.inspect}).send(:around_save, self) { super } end def save!(*) self.class.state_machine(#{name.inspect}).send(:around_save, self) { super } || raise(ActiveRecord::RecordInvalid.new(self)) end def changed_for_autosave? super || self.class.state_machines.any? {|name, machine| machine.action == :save && machine.read(self, :event)} end end_eval else super end end # Runs state events around the machine's :save action def around_save(object) transaction(object) do object.class.state_machines.transitions(object, action).perform { yield } end end # Creates a scope for finding records *with* a particular state or # states for the attribute def create_with_scope(name) create_scope(name, lambda {|values| ["#{attribute_column} IN (?)", values]}) end # Creates a scope for finding records *without* a particular state or # states for the attribute def create_without_scope(name) create_scope(name, lambda {|values| ["#{attribute_column} NOT IN (?)", values]}) end # Generates the fully-qualifed column name for this machine's attribute def attribute_column connection = owner_class.connection "#{connection.quote_table_name(owner_class.table_name)}.#{connection.quote_column_name(attribute)}" end # Runs a new database transaction, rolling back any changes by raising # an ActiveRecord::Rollback exception if the yielded block fails # (i.e. returns false). def transaction(object) result = nil object.class.transaction do raise ::ActiveRecord::Rollback unless result = yield end result end # Defines a new named scope with the given name def create_scope(name, scope) lambda {|model, values| model.where(scope.call(values))} end # ActiveModel's use of method_missing / respond_to for attribute methods # breaks both ancestor lookups and defined?(super). Need to special-case # the existence of query attribute methods. def owner_class_ancestor_has_method?(scope, method) scope == :instance && method == "#{attribute}?" ? owner_class : super end end end end state-machine-1.2.0/lib/state_machine/integrations/data_mapper.rb0000644000175000017500000004457112305405267024513 0ustar boutilboutilmodule StateMachine module Integrations #:nodoc: # Adds support for integrating state machines with DataMapper resources. # # == Examples # # Below is an example of a simple state machine defined within a # DataMapper resource: # # class Vehicle # include DataMapper::Resource # # property :id, Serial # property :name, String # property :state, String # # 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 resource to save the changes # made to the state machine's attribute. *Note* that if any other changes # were made to the resource prior to transition, then those changes will # be saved as well. # # For example, # # vehicle = Vehicle.create # => # # vehicle.name = 'Ford Explorer' # vehicle.ignite # => true # vehicle.reload # => # # # == 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 DataMapper, these automated events are run in the following order: # * before validation - If validation feature loaded, run before callbacks and persist new states, then validate # * before save - If validation feature was skipped/not loaded, run before callbacks and persist new states, then save # * after save - Run after callbacks # # For example, # # vehicle = Vehicle.create # => # # vehicle.state_event # => nil # vehicle.state_event = 'invalid' # vehicle.valid? # => false # vehicle.errors # => #["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.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 DataMapper::Resource # ... # # state_machine do # ... # end # protected :state_event # 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 DataMapper::Resource # ... # # state_machine do # # Define private events here # end # protected :state_event= # Prevent access to events in the first machine # # # Allow both machines to share the same state # state_machine :public_state, :attribute => :state do # # Define public events here # end # end # # === Within DataMapper Hooks # # DataMapper protects against the potential for system stack errors resulting # from infinite loops by preventing records from being saved multiple times # within save hooks. You need to be acutely aware of this when interacting # with state_machine within save hooks. There are two things to keep in mind: # # 1. You cannot run a state_machine event during an `after :save/:create` hook. # 2. If you need to run a state_machine event during a `before :save/:create/etc.` # hook, then you have to force the machine's action to be skipped by passing # `false` in as an argument to the event. # # For example: # # class Vehicle # include DataMapper::Resource # ... # # state_machine :initial => :parked do # event :ignite do # transition :parked => :idling # end # end # # # This will allow the event to transition without attempting to save a second time # before :create { ignite(false) } # # # This will never work because DataMapper will refuse to save the # # changes since we're still inside of a transaction # # after :create { ignite } # end # # While the above will work, in reality you should typically just set the # `state_event` attribute in `#initialize` to automatically transition an # object on creation. # # == Transactions # # By default, the use of transactions during an event transition is # turned off to be consistent with DataMapper. This means that if # changes are made to the database during a before callback, but the # transition fails to complete, those changes will *not* be rolled back. # # For example, # # class Message # include DataMapper::Resource # # property :id, Serial # property :content, String # end # # Vehicle.state_machine do # before_transition do |transition| # Message.create(:content => transition.inspect) # throw :halt # end # end # # vehicle = Vehicle.create # => # # vehicle.ignite # => false # Message.all.count # => 1 # # To turn on transactions: # # class Vehicle # include DataMapper::Resource # ... # # state_machine :initial => :parked, :use_transactions => true do # ... # 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.ignite # => false # vehicle.errors.full_messages # => ["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. # # In addition, if you're using the ignite! version of the event, # then the failure reason (such as the current validation errors) will be # included in the exception that gets raised when the event fails. For # example, assuming there's a validation on a field called +name+ on the class: # # vehicle = Vehicle.new # vehicle.ignite! # => StateMachine::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank) # # == Scopes # # To assist in filtering models with specific states, a series of class # methods are defined on the model for finding records with or without a # particular set of states. # # These named scopes are the functional equivalent of the following # definitions: # # class Vehicle # include DataMapper::Resource # # property :id, Serial # property :state, String # # class << self # def with_states(*states) # all(:state => states.flatten) # end # alias_method :with_state, :with_states # # def without_states(*states) # all(:state.not => states.flatten) # end # alias_method :without_state, :without_states # end # end # # *Note*, however, that the states are converted to their stored values # before being passed into the query. # # Because of the way scopes work in DataMapper, they can be chained like # so: # # Vehicle.with_state(:parked).all(:order => [:id.desc]) # # Note that states can also be referenced by the string version of their # name: # # Vehicle.with_state('parked') # # == Callbacks / Observers # # All before/after transition callbacks defined for DataMapper resources # behave in the same way that other DataMapper hooks behave. Rather than # passing in the record as an argument to the callback, the callback is # instead bound to the object and evaluated within its context. # # For example, # # class Vehicle # include DataMapper::Resource # # property :id, Serial # property :state, String # # state_machine :initial => :parked do # before_transition any => :idling do # put_on_seatbelt # end # # before_transition do |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. # # In addition to support for DataMapper-like hooks, there is additional # support for DataMapper observers. See StateMachine::Integrations::DataMapper::Observer # for more information. # # === Failure callbacks # # +after_failure+ callbacks allow you to execute behaviors when a transition # is allowed, but fails to save. This could be useful for something like # auditing transition attempts. Since callbacks run within transactions in # DataMapper, a save failure will cause any records that get created in # your callback to roll back. *Note* that this is only a problem if the # machine is configured to use transactions. If it is, you can work around # this issue like so: # # DataMapper.setup(:default, 'mysql://localhost/app') # DataMapper.setup(:logs, 'mysql://localhost/app') # # class TransitionLog # include DataMapper::Resource # end # # class Vehicle < ActiveRecord::Base # include DataMapper::Resource # # state_machine :use_transactions => true do # after_failure do |transition| # DataMapper.repository(:logs) do # TransitionLog.create(:vehicle => vehicle, :transition => transition) # end # end # # ... # end # end # # The failure callback creates +TransitionLog+ records using a second # connection to the database, allowing them to be saved without being # affected by rollbacks in the +Vehicle+ resource's transaction. # # === Callback Order # # Callbacks occur in the following order. Callbacks specific to state_machine # are bolded. The remaining callbacks are part of ActiveRecord. # # * (-) save # * (-) begin transaction (if enabled) # * (1) *before_transition* # * (2) before :valid? # * (-) valid? # * (3) after :valid? # * (4) before :save # * (-) save # * (5) before :create # * (-) create # * (6) after :create # * (7) after :save # * (8) *after_transition* # * (-) end transaction (if enabled) module DataMapper include Base require 'state_machine/integrations/data_mapper/versions' # The default options to use for state machines using this integration @defaults = {:action => :save, :use_transactions => false} # Classes that include DataMapper::Resource will automatically use the # DataMapper integration. def self.matching_ancestors %w(DataMapper::Resource) end # Loads additional files specific to DataMapper def self.extended(base) #:nodoc: require 'dm-core/version' unless ::DataMapper.const_defined?('VERSION') super end # Adds a validation error to the given object def invalidate(object, attribute, message, values = []) object.errors.add(self.attribute(attribute), generate_message(message, values)) if supports_validations? end # Describes the current validation errors on the given object. If none # are specific, then the default error is interpeted as a "halt". def errors_for(object) if object.errors.empty? 'Transition halted' else errors = [] object.errors.each_pair do |field_name, field_errors| field_errors.each {|error| errors << "#{field_name} #{error}"} end errors * ', ' end end # Resets any errors previously added when invalidating the given object def reset(object) object.errors.clear if supports_validations? end protected # Initializes class-level extensions and defaults for this machine def after_initialize super load_observer_extensions end # Loads extensions to DataMapper's Observers def load_observer_extensions require 'state_machine/integrations/data_mapper/observer' if ::DataMapper.const_defined?('Observer') end # Is validation support currently loaded? def supports_validations? @supports_validations ||= ::DataMapper.const_defined?('Validate') end # Gets the db default for the machine's attribute def owner_class_attribute_default attribute_property && attribute_property.default end # Gets the property for this machine's attribute (if it exists) def attribute_property owner_class.properties.detect {|property| property.name == attribute} end # Pluralizes the name using the built-in inflector def pluralize(word) ::DataMapper::Inflector.pluralize(word.to_s) 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 define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def initialize(*args) self.class.state_machines.initialize_states(self, :static => :force) { super } end end_eval end # Skips defining reader/writer methods since this is done automatically def define_state_accessor owner_class.property(attribute, String) unless attribute_property if supports_validations? name = self.name owner_class.validates_with_block(attribute) do machine = self.class.state_machine(name) machine.states.match(self) ? true : [false, machine.generate_message(:invalid)] end end end # Adds hooks into validation for automatically firing events def define_action_helpers if action_hook == :save define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def save(*) result = self.class.state_machines.transitions(self, :save).perform { super } assert_save_successful(:save, result) result end def save!(*) result = self.class.state_machines.transitions(self, :save).perform { super } assert_save_successful(:save!, result) result end def save_self(*) self.class.state_machines.transitions(self, :save).perform { super } end end_eval define_validation_hook else super end end # Adds hooks into validation for automatically firing events def define_validation_hook if supports_validations? define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def valid?(*) self.class.state_machines.transitions(self, :save, :after => false).perform { super } end end_eval end end # Creates a scope for finding records *with* a particular state or # states for the attribute def create_with_scope(name) lambda {|resource, values| resource.all(attribute => values)} end # Creates a scope for finding records *without* a particular state or # states for the attribute def create_without_scope(name) lambda {|resource, values| resource.all(attribute.to_sym.not => values)} end # Runs a new database transaction, rolling back any changes if the # yielded block fails (i.e. returns false). def transaction(object) object.class.transaction {|t| t.rollback unless yield} end # Creates a new callback in the callback chain, always ensuring that # it's configured to bind to the object as this is the convention for # DataMapper/Extlib callbacks def add_callback(type, options, &block) options[:bind_to_object] = true super end end end end state-machine-1.2.0/lib/state_machine/integrations/sequel.rb0000644000175000017500000004307112305405267023526 0ustar boutilboutilmodule StateMachine module Integrations #:nodoc: # Adds support for integrating state machines with Sequel models. # # == Examples # # Below is an example of a simple state machine defined within a # Sequel model: # # class Vehicle < Sequel::Model # 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 resource to save the changes # made to the state machine's attribute. *Note* that if any other changes # were made to the resource prior to transition, then those changes will # be made as well. # # For example, # # vehicle = Vehicle.create # => #"parked", :name=>nil, :id=>1}> # vehicle.name = 'Ford Explorer' # vehicle.ignite # => true # vehicle.refresh # => #"idling", :name=>"Ford Explorer", :id=>1}> # # == 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 Sequel, 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 # => #"parked", :name=>nil, :id=>1}> # 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 # => #"idling", :name=>nil, :id=>1}> # 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') # => #"idling", :name=>nil, :id=>1}> # 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 < Sequel::Model # set_restricted_columns :state_event # # set_allowed_columns ... # 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 < Sequel::Model # set_restricted_columns :state_event # Prevent access to events in the first machine # # state_machine do # # Define private events here # end # # # Allow both machines to share the same state # state_machine :public_state, :attribute => :state do # # Define public events here # end # end # # == Transactions # # In order to ensure that any changes made during transition callbacks # are rolled back during a failed attempt, every transition is wrapped # within a transaction. # # For example, # # class Message < Sequel::Model # end # # Vehicle.state_machine do # before_transition do |transition| # Message.create(:content => transition.inspect) # false # end # end # # vehicle = Vehicle.create # => #"parked", :name=>nil, :id=>1}> # vehicle.ignite # => false # Message.count # => 0 # # *Note* that only before callbacks that halt the callback chain and # failed attempts to save the record will result in the transaction being # rolled back. If an after callback halts the chain, the previous result # still applies and the transaction is *not* rolled back. # # To turn off transactions: # # class Vehicle < Sequel::Model # state_machine :initial => :parked, :use_transactions => false do # ... # 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') # => #"parked", :name=>nil, :id=>1}> # 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. # # In addition, if you're using the ignite! version of the event, # then the failure reason (such as the current validation errors) will be # included in the exception that gets raised when the event fails. For # example, assuming there's a validation on a field called +name+ on the class: # # vehicle = Vehicle.new # vehicle.ignite! # => StateMachine::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank) # # == Scopes # # To assist in filtering models with specific states, a series of class # methods are defined on the model for finding records with or without a # particular set of states. # # These named scopes are the functional equivalent of the following # definitions: # # class Vehicle < Sequel::Model # class << self # def with_states(*states) # filter(:state => states) # end # alias_method :with_state, :with_states # # def without_states(*states) # filter(~{:state => states}) # end # alias_method :without_state, :without_states # end # end # # *Note*, however, that the states are converted to their stored values # before being passed into the query. # # Because of the way scopes work in Sequel, they can be chained like so: # # Vehicle.with_state(:parked).order(:id.desc) # # Note that states can also be referenced by the string version of their # name: # # Vehicle.with_state('parked') # # == Callbacks # # All before/after transition callbacks defined for Sequel resources # behave in the same way that other Sequel hooks behave. Rather than # passing in the record as an argument to the callback, the callback is # instead bound to the object and evaluated within its context. # # For example, # # class Vehicle < Sequel::Model # state_machine :initial => :parked do # before_transition any => :idling do # put_on_seatbelt # end # # before_transition do |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. # # === Failure callbacks # # +after_failure+ callbacks allow you to execute behaviors when a transition # is allowed, but fails to save. This could be useful for something like # auditing transition attempts. Since callbacks run within transactions in # Sequel, a save failure will cause any records that get created in # your callback to roll back. You can work around this issue like so: # # DB = Sequel.connect('mysql://localhost/app') # DB_LOGS = Sequel.connect('mysql://localhost/app') # # class TransitionLog < Sequel::Model(DB_LOGS[:transition_logs]) # end # # class Vehicle < Sequel::Model(DB[:vehicles]) # state_machine do # after_failure do |transition| # TransitionLog.create(:vehicle => vehicle, :transition => transition) # end # # ... # end # end # # The +TransitionLog+ model uses a second connection to the database that # allows new records to be saved without being affected by rollbacks in the # +Vehicle+ model's transaction. # # === Callback Order # # Callbacks occur in the following order. Callbacks specific to state_machine # are bolded. The remaining callbacks are part of Sequel. # # * (-) save # * (-) begin transaction (if enabled) # * (1) *before_transition* # * (2) before_validation # * (-) validate # * (3) after_validation # * (4) before_save # * (5) before_create # * (-) create # * (6) after_create # * (7) after_save # * (8) *after_transition* # * (-) end transaction (if enabled) # * (9) after_commit module Sequel include Base require 'state_machine/integrations/sequel/versions' # The default options to use for state machines using this integration @defaults = {:action => :save} # Classes that include Sequel::Model will automatically use the Sequel # integration. def self.matching_ancestors %w(Sequel::Model) end # Forces the change in state to be recognized regardless of whether the # state value actually changed def write(object, attribute, value, *args) result = super column = self.attribute.to_sym if (attribute == :state || attribute == :event && value) && owner_class.columns.include?(column) && !object.changed_columns.include?(column) object.changed_columns << column end result end # Adds a validation error to the given object def invalidate(object, attribute, message, values = []) object.errors.add(self.attribute(attribute), generate_message(message, values)) end # Describes the current validation errors on the given object. If none # are specific, then the default error is interpeted as a "halt". def errors_for(object) object.errors.empty? ? 'Transition halted' : object.errors.full_messages * ', ' end # Resets any errors previously added when invalidating the given object def reset(object) object.errors.clear end # Pluralizes the name using the built-in inflector def pluralize(word) load_inflector super end protected # Initializes class-level extensions for this machine def define_helpers load_plugins super end # Loads all of the Sequel plugins necessary to run def load_plugins owner_class.plugin(:hook_class_methods) end # Loads the built-in inflector def load_inflector require 'sequel/extensions/inflector' 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 define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def initialize_set(*) self.class.state_machines.initialize_states(self, :static => :force) { super } end end_eval end # Skips defining reader/writer methods since this is done automatically def define_state_accessor define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def validate(*) super machine = self.class.state_machine(#{name.inspect}) machine.invalidate(self, :state, :invalid) unless machine.states.match(self) end end_eval end # Defines validation hooks if the machine's action is to save the model def define_action_helpers super define_validation_hook if action == :save end # Uses around callbacks to run state events if using the :save hook def define_action_hook if action == :save define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def #{action_hook}(*args) opts = args.last.is_a?(Hash) ? args.last : {} yielded = false result = self.class.state_machine(#{name.inspect}).send(:around_save, self) do yielded = true super end if yielded || result result else #{handle_save_failure} end end end_eval else super end end # Handles how save failures (due to invalid transitions) are raised def handle_save_failure 'raise_hook_failure(:before_transition) if raise_on_failure?(opts)' end # Runs state events around the machine's :save action def around_save(object) result = transaction(object) do object.class.state_machines.transitions(object, action).perform { yield } end result end # Adds hooks into validation for automatically firing events def define_validation_hook define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def around_validation(*) self.class.state_machines.transitions(self, :save, :after => false).perform { super } end end_eval end # Gets the db default for the machine's attribute def owner_class_attribute_default if owner_class.db.table_exists?(owner_class.table_name) && column = owner_class.db_schema[attribute.to_sym] column[:default] end end # Uses the DB literal to match the default against the specified state def owner_class_attribute_default_matches?(state) owner_class.db.literal(state.value) == owner_class_attribute_default end # Creates a scope for finding records *with* a particular state or # states for the attribute def create_with_scope(name) create_scope(name, lambda {|dataset, values| dataset.filter(attribute_column => values)}) end # Creates a scope for finding records *without* a particular state or # states for the attribute def create_without_scope(name) create_scope(name, lambda {|dataset, values| dataset.exclude(attribute_column => values)}) end # Creates a new named scope with the given name def create_scope(name, scope) machine = self owner_class.def_dataset_method(name) do |*states| machine.send(:run_scope, scope, self, states) end false end # Generates the results for the given scope based on one or more states to # filter by def run_scope(scope, dataset, states) super(scope, model_from_dataset(dataset).state_machine(name), dataset, states) end # Determines the model associated with the given dataset def model_from_dataset(dataset) dataset.model end # Generates the fully-qualifed column name for this machine's attribute def attribute_column ::Sequel::SQL::QualifiedIdentifier.new(owner_class.table_name, attribute) end # Runs a new database transaction, rolling back any changes if the # yielded block fails (i.e. returns false). def transaction(object) result = nil object.db.transaction do raise ::Sequel::Error::Rollback unless result = yield end result end # Creates a new callback in the callback chain, always ensuring that # it's configured to bind to the object as this is the convention for # Sequel callbacks def add_callback(type, options, &block) options[:bind_to_object] = true options[:terminator] = @terminator ||= lambda {|result| result == false} super end end end end state-machine-1.2.0/lib/state_machine/integrations/active_record/0000755000175000017500000000000012305405267024507 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/integrations/active_record/versions.rb0000644000175000017500000000753712305405267026720 0ustar boutilboutilmodule StateMachine module Integrations #:nodoc: module ActiveRecord version '2.x - 3.0.x' do def self.active? ::ActiveRecord::VERSION::MAJOR == 2 || ::ActiveRecord::VERSION::MAJOR == 3 && ::ActiveRecord::VERSION::MINOR == 0 end def define_static_state_initializer define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def attributes_from_column_definition(*) result = super self.class.state_machines.initialize_states(self, :static => :force, :dynamic => false, :to => result) result end end_eval end end version '2.x' do def self.active? ::ActiveRecord::VERSION::MAJOR == 2 end def load_locale super if defined?(I18n) end def create_scope(name, scope) if owner_class.respond_to?(:named_scope) name = name.to_sym machine_name = self.name # Since ActiveRecord does not allow direct access to the model # being used within the evaluation of a dynamic named scope, the # scope must be generated manually. It's necessary to have access # to the model so that the state names can be translated to their # associated values and so that inheritance is respected properly. owner_class.named_scope(name) owner_class.scopes[name] = lambda do |model, *states| machine_states = model.state_machine(machine_name).states values = states.flatten.map {|state| machine_states.fetch(state).value} ::ActiveRecord::NamedScope::Scope.new(model, :conditions => scope.call(values)) end end # Prevent the Machine class from wrapping the scope false end def invalidate(object, attribute, message, values = []) if defined?(I18n) super else object.errors.add(self.attribute(attribute), generate_message(message, values)) end end def translate(klass, key, value) if defined?(I18n) super else value ? value.to_s.humanize.downcase : 'nil' end end def supports_observers? true end def supports_validations? true end def supports_mass_assignment_security? true end def i18n_scope(klass) :activerecord end def load_observer_extensions super ::ActiveRecord::Observer.class_eval do include StateMachine::Integrations::ActiveModel::Observer end unless ::ActiveRecord::Observer < StateMachine::Integrations::ActiveModel::Observer end end version '2.0 - 2.2.x' do def self.active? ::ActiveRecord::VERSION::MAJOR == 2 && ::ActiveRecord::VERSION::MINOR < 3 end def default_error_message_options(object, attribute, message) {:default => @messages[message]} end end version '2.0 - 2.3.1' do def self.active? ::ActiveRecord::VERSION::MAJOR == 2 && (::ActiveRecord::VERSION::MINOR < 3 || ::ActiveRecord::VERSION::TINY < 2) end def ancestors_for(klass) klass.self_and_descendents_from_active_record end end version '2.3.2 - 2.3.x' do def self.active? ::ActiveRecord::VERSION::MAJOR == 2 && ::ActiveRecord::VERSION::MINOR == 3 && ::ActiveRecord::VERSION::TINY >= 2 end def ancestors_for(klass) klass.self_and_descendants_from_active_record end end end end end state-machine-1.2.0/lib/state_machine/integrations/active_record/locale.rb0000644000175000017500000000142012305405267026270 0ustar boutilboutilfilename = "#{File.dirname(__FILE__)}/../active_model/locale.rb" translations = eval(IO.read(File.expand_path(filename)), binding, filename) translations[:en][:activerecord] = translations[:en].delete(:activemodel) # Only ActiveRecord 2.3.5+ can pull i18n >= 0.1.3 from system-wide gems (and # therefore possibly have I18n::VERSION available) begin require 'i18n/version' rescue Exception => ex end unless ::ActiveRecord::VERSION::MAJOR == 2 && (::ActiveRecord::VERSION::MINOR < 3 || ::ActiveRecord::VERSION::TINY < 5) # Only i18n 0.4.0+ has the new %{key} syntax if !defined?(I18n::VERSION) || I18n::VERSION < '0.4.0' translations[:en][:activerecord][:errors][:messages].each do |key, message| message.gsub!('%{', '{{') message.gsub!('}', '}}') end end translations state-machine-1.2.0/lib/state_machine/integrations/active_model.rb0000644000175000017500000005516412305405267024671 0ustar boutilboutilmodule StateMachine module Integrations #:nodoc: # Adds support for integrating state machines with ActiveModel classes. # # == Examples # # If using ActiveModel directly within your class, then any one of the # following features need to be included in order for the integration to be # detected: # * ActiveModel::Observing # * ActiveModel::Validations # # Below is an example of a simple state machine defined within an # ActiveModel class: # # class Vehicle # include ActiveModel::Observing # include ActiveModel::Validations # # attr_accessor :state # define_attribute_methods [:state] # # 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, no action will be invoked when a state is transitioned. This # means that if you want to save changes when transitioning, you must # define the action yourself like so: # # class Vehicle # include ActiveModel::Validations # attr_accessor :state # # state_machine :action => :save do # ... # end # # def save # # Save changes # end # end # # == Validations # # As mentioned in StateMachine::Machine#state, you can define behaviors, # like validations, that only execute for certain states. One *important* # caveat here is that, due to a constraint in ActiveModel's validation # framework, custom validators will not work as expected when defined to run # in multiple states. For example: # # class Vehicle # include ActiveModel::Validations # # state_machine do # ... # state :first_gear, :second_gear do # validate :speed_is_legal # end # end # end # # In this case, the :speed_is_legal validation will only get run # for the :second_gear state. To avoid this, you can define your # custom validation like so: # # class Vehicle # include ActiveModel::Validations # # state_machine do # ... # state :first_gear, :second_gear do # validate {|vehicle| vehicle.speed_is_legal} # end # end # end # # == Validation errors # # In order to hook in validation support for your model, the # ActiveModel::Validations feature must be included. If this is included # and an event fails to successfully fire because there are no matching # transitions for the object, a validation error is added to the object's # state attribute to help in determining why it failed. # # For example, # # vehicle = Vehicle.new # vehicle.ignite # => false # vehicle.errors.full_messages # => ["State cannot transition via \"ignite\""] # # In addition, if you're using the ignite! version of the event, # then the failure reason (such as the current validation errors) will be # included in the exception that gets raised when the event fails. For # example, assuming there's a validation on a field called +name+ on the class: # # vehicle = Vehicle.new # vehicle.ignite! # => StateMachine::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank) # # === 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 ActiveModel::MassAssignmentSecurity # attr_accessor :state # # 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 ActiveModel::MassAssignmentSecurity # attr_accessor :state # # 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 # # == Callbacks # # All before/after transition callbacks defined for ActiveModel models # behave in the same way that other ActiveSupport callbacks behave. The # object involved in the transition is passed in as an argument. # # For example, # # class Vehicle # include ActiveModel::Validations # attr_accessor :state # # 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. # # == Observers # # In order to hook in observer support for your application, the # ActiveModel::Observing feature must be included. Because of the way # ActiveModel observers are designed, there is less flexibility around the # specific transitions that can be hooked in. However, a large number of # hooks *are* supported. For example, if a transition for a object's # +state+ attribute changes the state from +parked+ to +idling+ via the # +ignite+ event, the following observer methods are supported: # * before/after/after_failure_to-_ignite_from_parked_to_idling # * before/after/after_failure_to-_ignite_from_parked # * before/after/after_failure_to-_ignite_to_idling # * before/after/after_failure_to-_ignite # * before/after/after_failure_to-_transition_state_from_parked_to_idling # * before/after/after_failure_to-_transition_state_from_parked # * before/after/after_failure_to-_transition_state_to_idling # * before/after/after_failure_to-_transition_state # * before/after/after_failure_to-_transition # # The following class shows an example of some of these hooks: # # class VehicleObserver < ActiveModel::Observer # # Callback for :ignite event *before* the transition is performed # def before_ignite(vehicle, transition) # # log message # end # # # Callback for :ignite event *after* the transition has been performed # def after_ignite(vehicle, transition) # # put on seatbelt # end # # # Generic transition callback *before* the transition is performed # def after_transition(vehicle, transition) # Audit.log(vehicle, transition) # end # # def after_failure_to_transition(vehicle, transition) # Audit.error(vehicle, transition) # end # end # # More flexible transition callbacks can be defined directly within the # model as described in StateMachine::Machine#before_transition # and StateMachine::Machine#after_transition. # # To define a single observer for multiple state machines: # # class StateMachineObserver < ActiveModel::Observer # observe Vehicle, Switch, Project # # def after_transition(object, transition) # Audit.log(object, transition) # end # end # # == Internationalization # # Any error message that is generated from performing invalid transitions # can be localized. The following default translations are used: # # en: # activemodel: # errors: # messages: # invalid: "is invalid" # # %{value} = attribute value, %{state} = Human state name # invalid_event: "cannot transition when %{state}" # # %{value} = attribute value, %{event} = Human event name, %{state} = Human current state name # invalid_transition: "cannot transition via %{event}" # # You can override these for a specific model like so: # # en: # activemodel: # errors: # models: # user: # invalid: "is not valid" # # In addition to the above, you can also provide translations for the # various states / events in each state machine. Using the Vehicle example, # state translations will be looked for using the following keys, where # +model_name+ = "vehicle", +machine_name+ = "state" and +state_name+ = "parked": # * activemodel.state_machines.#{model_name}.#{machine_name}.states.#{state_name} # * activemodel.state_machines.#{model_name}.states.#{state_name} # * activemodel.state_machines.#{machine_name}.states.#{state_name} # * activemodel.state_machines.states.#{state_name} # # Event translations will be looked for using the following keys, where # +model_name+ = "vehicle", +machine_name+ = "state" and +event_name+ = "ignite": # * activemodel.state_machines.#{model_name}.#{machine_name}.events.#{event_name} # * activemodel.state_machines.#{model_name}.events.#{event_name} # * activemodel.state_machines.#{machine_name}.events.#{event_name} # * activemodel.state_machines.events.#{event_name} # # An example translation configuration might look like so: # # es: # activemodel: # state_machines: # states: # parked: 'estacionado' # events: # park: 'estacionarse' # # == Dirty Attribute Tracking # # When using the ActiveModel::Dirty extension, your model will keep track of # any changes that are made to attributes. Depending on your ORM, an object # will only be saved when there are attributes that have changed on the # object. When integrating with state_machine, typically the +state+ field # will be marked as dirty after a transition occurs. In some situations, # however, this isn't the case. # # If you define loopback transitions in your state machine, the value for # the machine's attribute (e.g. state) will not change. Unless you explicitly # indicate so, this means that your object won't persist anything on a # loopback. For example: # # class Vehicle # include ActiveModel::Validations # include ActiveModel::Dirty # attr_accessor :state # # state_machine :initial => :parked do # event :park do # transition :parked => :parked, ... # end # end # end # # If, instead, you'd like your object to always persist regardless of # whether the value actually changed, you can do so by using the # #{attribute}_will_change! helpers or defining a +before_transition+ # callback that actually changes an attribute on the model. For example: # # class Vehicle # ... # state_machine :initial => :parked do # before_transition all => same do |vehicle| # vehicle.state_will_change! # # # Alternative solution, updating timestamp # # vehicle.updated_at = Time.curent # end # end # end # # == Creating new integrations # # If you want to integrate state_machine with an ORM that implements parts # or all of the ActiveModel API, only the machine defaults need to be # specified. Otherwise, the implementation is similar to any other # integration. # # For example, # # module StateMachine::Integrations::MyORM # include StateMachine::Integrations::ActiveModel # # @defaults = {:action = > :persist} # # def self.matches?(klass) # defined?(::MyORM::Base) && klass <= ::MyORM::Base # end # # protected # def runs_validations_on_action? # action == :persist # end # end # # If you wish to implement other features, such as attribute initialization # with protected attributes, named scopes, or database transactions, you # must add these independent of the ActiveModel integration. See the # ActiveRecord implementation for examples of these customizations. module ActiveModel def self.included(base) #:nodoc: base.versions.unshift(*versions) end include Base extend ClassMethods require 'state_machine/integrations/active_model/versions' @defaults = {} # Classes that include ActiveModel::Observing or ActiveModel::Validations # will automatically use the ActiveModel integration. def self.matching_ancestors %w(ActiveModel ActiveModel::Observing ActiveModel::Validations) end # Adds a validation error to the given object def invalidate(object, attribute, message, values = []) if supports_validations? attribute = self.attribute(attribute) options = values.inject({}) do |h, (key, value)| h[key] = value h end default_options = default_error_message_options(object, attribute, message) object.errors.add(attribute, message, options.merge(default_options)) end end # Describes the current validation errors on the given object. If none # are specific, then the default error is interpeted as a "halt". def errors_for(object) object.errors.empty? ? 'Transition halted' : object.errors.full_messages * ', ' end # Resets any errors previously added when invalidating the given object def reset(object) object.errors.clear if supports_validations? end protected # Whether observers are supported in the integration. Only true if # ActiveModel::Observer is available. def supports_observers? defined?(::ActiveModel::Observing) && owner_class <= ::ActiveModel::Observing end # Whether validations are supported in the integration. Only true if # the ActiveModel feature is enabled on the owner class. def supports_validations? defined?(::ActiveModel::Validations) && owner_class <= ::ActiveModel::Validations end # Do validations run when the action configured this machine is # invoked? This is used to determine whether to fire off attribute-based # event transitions when the action is run. def runs_validations_on_action? false end # Gets the terminator to use for callbacks def callback_terminator @terminator ||= lambda {|result| result == false} end # Determines the base scope to use when looking up translations def i18n_scope(klass) klass.i18n_scope end # The default options to use when generating messages for validation # errors def default_error_message_options(object, attribute, message) {:message => @messages[message]} end # Translates the given key / value combo. Translation keys are looked # up in the following order: # * #{i18n_scope}.state_machines.#{model_name}.#{machine_name}.#{plural_key}.#{value} # * #{i18n_scope}.state_machines.#{model_name}.#{plural_key}.#{value} # * #{i18n_scope}.state_machines.#{machine_name}.#{plural_key}.#{value} # * #{i18n_scope}.state_machines.#{plural_key}.#{value} # # If no keys are found, then the humanized value will be the fallback. def translate(klass, key, value) ancestors = ancestors_for(klass) group = key.to_s.pluralize value = value ? value.to_s : 'nil' # Generate all possible translation keys translations = ancestors.map {|ancestor| :"#{ancestor.model_name.to_s.underscore}.#{name}.#{group}.#{value}"} translations.concat(ancestors.map {|ancestor| :"#{ancestor.model_name.to_s.underscore}.#{group}.#{value}"}) translations.concat([:"#{name}.#{group}.#{value}", :"#{group}.#{value}", value.humanize.downcase]) I18n.translate(translations.shift, :default => translations, :scope => [i18n_scope(klass), :state_machines]) end # Build a list of ancestors for the given class to use when # determining which localization key to use for a particular string. def ancestors_for(klass) klass.lookup_ancestors end # Initializes class-level extensions and defaults for this machine def after_initialize super load_locale load_observer_extensions add_default_callbacks end # Loads any locale files needed for translating validation errors def load_locale I18n.load_path.unshift(@integration.locale_path) unless I18n.load_path.include?(@integration.locale_path) end # Loads extensions to ActiveModel's Observers def load_observer_extensions require 'state_machine/integrations/active_model/observer' require 'state_machine/integrations/active_model/observer_update' end # Adds a set of default callbacks that utilize the Observer extensions def add_default_callbacks if supports_observers? callbacks[:before] << Callback.new(:before) {|object, transition| notify(:before, object, transition)} callbacks[:after] << Callback.new(:after) {|object, transition| notify(:after, object, transition)} callbacks[:failure] << Callback.new(:failure) {|object, transition| notify(:after_failure_to, object, transition)} end end # Skips defining reader/writer methods since this is done automatically def define_state_accessor name = self.name owner_class.validates_each(attribute) do |object, attr, value| machine = object.class.state_machine(name) machine.invalidate(object, :state, :invalid) unless machine.states.match(object) end if supports_validations? end # Adds hooks into validation for automatically firing events def define_action_helpers super define_validation_hook if runs_validations_on_action? end # Hooks into validations by defining around callbacks for the # :validation event def define_validation_hook owner_class.set_callback(:validation, :around, self, :prepend => true) end # Runs state events around the object's validation process def around_validation(object) object.class.state_machines.transitions(object, action, :after => false).perform { yield } end # Creates a new callback in the callback chain, always inserting it # before the default Observer callbacks that were created after # initialization. def add_callback(type, options, &block) options[:terminator] = callback_terminator if supports_observers? @callbacks[type == :around ? :before : type].insert(-2, callback = Callback.new(type, options, &block)) add_states(callback.known_states) callback else super end end # Configures new states with the built-in humanize scheme def add_states(new_states) super.each do |new_state| new_state.human_name = lambda {|state, klass| translate(klass, :state, state.name)} end end # Configures new event with the built-in humanize scheme def add_events(new_events) super.each do |new_event| new_event.human_name = lambda {|event, klass| translate(klass, :event, event.name)} end end # Notifies observers on the given object that a callback occurred # involving the given transition. This will attempt to call the # following methods on observers: # * #{type}_#{qualified_event}_from_#{from}_to_#{to} # * #{type}_#{qualified_event}_from_#{from} # * #{type}_#{qualified_event}_to_#{to} # * #{type}_#{qualified_event} # * #{type}_transition_#{machine_name}_from_#{from}_to_#{to} # * #{type}_transition_#{machine_name}_from_#{from} # * #{type}_transition_#{machine_name}_to_#{to} # * #{type}_transition_#{machine_name} # * #{type}_transition # # This will always return true regardless of the results of the # callbacks. def notify(type, object, transition) name = self.name event = transition.qualified_event from = transition.from_name || 'nil' to = transition.to_name || 'nil' # Machine-specific updates ["#{type}_#{event}", "#{type}_transition_#{name}"].each do |event_segment| ["_from_#{from}", nil].each do |from_segment| ["_to_#{to}", nil].each do |to_segment| object.class.changed if object.class.respond_to?(:changed) object.class.notify_observers('update_with_transition', ObserverUpdate.new([event_segment, from_segment, to_segment].join, object, transition)) end end end # Generic updates object.class.changed if object.class.respond_to?(:changed) object.class.notify_observers('update_with_transition', ObserverUpdate.new("#{type}_transition", object, transition)) true end end end end state-machine-1.2.0/lib/state_machine/integrations/active_model/0000755000175000017500000000000012305405267024331 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/integrations/active_model/versions.rb0000644000175000017500000000172112305405267026527 0ustar boutilboutilmodule StateMachine module Integrations #:nodoc: module ActiveModel version '2.x' do def self.active? !defined?(::ActiveModel::VERSION) || ::ActiveModel::VERSION::MAJOR == 2 end def define_validation_hook define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def valid?(*) self.class.state_machines.transitions(self, #{action.inspect}, :after => false).perform { super } end end_eval end end version '3.0.x' do def self.active? defined?(::ActiveModel::VERSION) && ::ActiveModel::VERSION::MAJOR == 3 && ::ActiveModel::VERSION::MINOR == 0 end def define_validation_hook # +around+ callbacks don't have direct access to results until AS 3.1 owner_class.set_callback(:validation, :after, 'value', :prepend => true) super end end end end end state-machine-1.2.0/lib/state_machine/integrations/active_model/observer_update.rb0000644000175000017500000000300112305405267030041 0ustar boutilboutilmodule StateMachine module Integrations #:nodoc: module ActiveModel # Represents the encapsulation of all of the details to be included in an # update to state machine observers. This allows multiple arguments to # get passed to an observer method (instead of just a single +object+) # while still respecting the way in which ActiveModel checks for the # object's list of observers. class ObserverUpdate # The method to invoke on the observer attr_reader :method # The object being transitioned attr_reader :object # The transition being run attr_reader :transition def initialize(method, object, transition) #:nodoc: @method, @object, @transition = method, object, transition end # The arguments to pass into the method def args [object, transition] end # The class of the object being transitioned. Normally the object # getting passed into observer methods is the actual instance of the # ActiveModel class. ActiveModel uses that instance's class to check # for enabled / disabled observers. # # Since state_machine is passing an ObserverUpdate instance into observer # methods, +class+ needs to be overridden so that ActiveModel can still # get access to the enabled / disabled observers. def class object.class end end end end end state-machine-1.2.0/lib/state_machine/integrations/active_model/locale.rb0000644000175000017500000000055212305405267026117 0ustar boutilboutil{:en => { :activemodel => { :errors => { :messages => { :invalid => StateMachine::Machine.default_messages[:invalid], :invalid_event => StateMachine::Machine.default_messages[:invalid_event] % ['%{state}'], :invalid_transition => StateMachine::Machine.default_messages[:invalid_transition] % ['%{event}'] } } } }} state-machine-1.2.0/lib/state_machine/integrations/active_model/observer.rb0000644000175000017500000000212112305405267026501 0ustar boutilboutilmodule StateMachine module Integrations #:nodoc: module ActiveModel # Adds support for invoking callbacks on ActiveModel observers with more # than one argument (e.g. the record *and* the state transition). By # default, ActiveModel only supports passing the record into the # callbacks. # # For example: # # class VehicleObserver < ActiveModel::Observer # # The default behavior: only pass in the record # def after_save(vehicle) # end # # # Custom behavior: allow the transition to be passed in as well # def after_transition(vehicle, transition) # Audit.log(vehicle, transition) # end # end module Observer def update_with_transition(observer_update) method = observer_update.method send(method, *observer_update.args) if respond_to?(method) end end end end end ActiveModel::Observer.class_eval do include StateMachine::Integrations::ActiveModel::Observer end if defined?(ActiveModel::Observer) state-machine-1.2.0/lib/state_machine/integrations/data_mapper/0000755000175000017500000000000012305405267024153 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/integrations/data_mapper/versions.rb0000644000175000017500000000454012305405267026353 0ustar boutilboutilmodule StateMachine module Integrations #:nodoc: module DataMapper version '0.9.x - 0.10.x' do def self.active? ::DataMapper::VERSION =~ /^0\.\d\./ || ::DataMapper::VERSION =~ /^0\.10\./ end def pluralize(word) ::Extlib::Inflection.pluralize(word.to_s) end end version '0.9.x' do def self.active? ::DataMapper::VERSION =~ /^0\.9\./ end def define_action_helpers if action_hook == :save define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def save(*) self.class.state_machines.transitions(self, :save).perform { super } end end_eval define_validation_hook else super end end end version '0.9.4 - 0.9.6' do def self.active? ::DataMapper::VERSION =~ /^0\.9\.[4-6]/ end # 0.9.4 - 0.9.6 fails to run after callbacks when validations are # enabled because of the way dm-validations integrates def define_action_helpers? super if action != :save || !supports_validations? end end version '0.10.x' do def self.active? ::DataMapper::VERSION =~ /^0\.10\./ end def define_action_helpers if action_hook == :save define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def save(*) self.class.state_machines.transitions(self, :save).perform { super } end def save!(*) self.class.state_machines.transitions(self, :save).perform { super } end def save_self(*) self.class.state_machines.transitions(self, :save).perform { super } end end_eval define_validation_hook else super end end end version '1.0.0' do def self.active? ::DataMapper::VERSION == '1.0.0' end def pluralize(word) (defined?(::ActiveSupport::Inflector) ? ::ActiveSupport::Inflector : ::Extlib::Inflection).pluralize(word.to_s) end end end end end state-machine-1.2.0/lib/state_machine/integrations/data_mapper/observer.rb0000644000175000017500000001656112305405267026340 0ustar boutilboutilmodule StateMachine module Integrations #:nodoc: module DataMapper # Adds support for creating before/after/around/failure transition # callbacks within a DataMapper observer. These callbacks behave very # similar to hooks during save/update/destroy/etc., but with the following # modifications: # * Each callback can define a set of transition requirements that must be # met in order for the callback to get invoked. # * An additional transition parameter is available that provides # contextual information about the event (see StateMachine::Transition # for more information) # # To define a single observer for multiple state machines: # # class StateMachineObserver # include DataMapper::Observer # # observe Vehicle, Switch, Project # # after_transition do |transition| # Audit.log(self, transition) # end # end # # == Requirements # # To use this feature of the DataMapper integration, the dm-observer library # must be available. This can be installed either directly or indirectly # through dm-more. When loading DataMapper, be sure to load the dm-observer # library as well like so: # # require 'rubygems' # require 'dm-core' # require 'dm-observer' # # If dm-observer is not available, then this feature will be skipped. module Observer include MatcherHelpers # Creates a callback that will be invoked *before* a transition is # performed, so long as the given configuration options match the # transition. Each part of the transition (event, to state, from state) # must match in order for the callback to get invoked. # # See StateMachine::Machine#before_transition for more # information about the various configuration options available. # # == Examples # # class Vehicle # include DataMapper::Resource # # property :id, Serial # property :state, :String # # state_machine :initial => :parked do # event :ignite do # transition :parked => :idling # end # end # end # # class VehicleObserver # include DataMapper::Observer # # observe Vehicle # # before :save do # # log message # end # # # Target all state machines # before_transition :parked => :idling, :on => :ignite do # # put on seatbelt # end # # # Target a specific state machine # before_transition :state, any => :idling do # # put on seatbelt # end # # # Target all state machines without requirements # before_transition do |transition| # # log message # end # end # # *Note* that in each of the above +before_transition+ callbacks, the # callback is executed within the context of the object (i.e. the # Vehicle instance being transition). This means that +self+ refers # to the vehicle record within each callback block. def before_transition(*args, &block) add_transition_callback(:before_transition, *args, &block) end # Creates a callback that will be invoked *after* a transition is # performed so long as the given configuration options match the # transition. # # See +before_transition+ for a description of the possible configurations # for defining callbacks. def after_transition(*args, &block) add_transition_callback(:after_transition, *args, &block) end # Creates a callback that will be invoked *around* a transition so long # as the given requirements match the transition. # # == Examples # # class Vehicle # include DataMapper::Resource # # property :id, Serial # property :state, :String # # state_machine :initial => :parked do # event :ignite do # transition :parked => :idling # end # end # end # # class VehicleObserver # include DataMapper::Observer # # observe Vehicle # # around_transition do |transition, block| # # track start time # block.call # # track end time # end # end # # See +before_transition+ for a description of the possible configurations # for defining callbacks. def around_transition(*args, &block) add_transition_callback(:around_transition, *args, &block) end # Creates a callback that will be invoked *after* a transition failures to # be performed so long as the given requirements match the transition. # # == Example # # class Vehicle # include DataMapper::Resource # # property :id, Serial # property :state, :String # # state_machine :initial => :parked do # event :ignite do # transition :parked => :idling # end # end # end # # class VehicleObserver # after_transition_failure do |transition| # # log failure # end # # after_transition_failure :on => :ignite do # # log failure # end # end # # See +before_transition+ for a description of the possible configurations # for defining callbacks. *Note* however that you cannot define the state # requirements in these callbacks. You may only define event requirements. def after_transition_failure(*args, &block) add_transition_callback(:after_failure, *args, &block) end private # Adds the transition callback to a specific machine or all of the # state machines for each observed class. def add_transition_callback(type, *args, &block) if args.any? && !args.first.is_a?(Hash) # Specific machine(s) being targeted names = args args = args.last.is_a?(Hash) ? [args.pop] : [] else # Target all state machines names = nil end # Add the transition callback to each class being observed observing.each do |klass| state_machines = if names names.map {|name| klass.state_machines.fetch(name)} else klass.state_machines.values end state_machines.each {|machine| machine.send(type, *args, &block)} end if observing end end end end end DataMapper::Observer::ClassMethods.class_eval do include StateMachine::Integrations::DataMapper::Observer end state-machine-1.2.0/lib/state_machine/integrations/sequel/0000755000175000017500000000000012305405267023174 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/integrations/sequel/versions.rb0000644000175000017500000000560512305405267025377 0ustar boutilboutilmodule StateMachine module Integrations #:nodoc: module Sequel version '2.8.x - 3.23.x' do def self.active? !defined?(::Sequel::MAJOR) || ::Sequel::MAJOR == 2 || ::Sequel::MAJOR == 3 && ::Sequel::MINOR <= 23 end def define_state_initializer define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def initialize(*) super do |*args| self.class.state_machines.initialize_states(self, :static => false) changed_columns.clear yield(*args) if block_given? end end def set(*) self.class.state_machines.initialize_states(self, :static => :force, :dynamic => false) if values.empty? super end end_eval end def define_validation_hook define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def valid?(*args) opts = args.first.is_a?(Hash) ? args.first : {} yielded = false result = self.class.state_machines.transitions(self, :save, :after => false).perform do yielded = true super end if yielded || result result else #{handle_validation_failure} end end end_eval end end version '2.8.x - 3.13.x' do def self.active? !defined?(::Sequel::MAJOR) || ::Sequel::MAJOR == 2 || ::Sequel::MAJOR == 3 && ::Sequel::MINOR <= 13 end def handle_validation_failure 'raise_on_save_failure ? save_failure(:validation) : result' end def handle_save_failure 'save_failure(:save) if raise_on_save_failure' end end version '2.8.x - 2.11.x' do def self.active? !defined?(::Sequel::MAJOR) || ::Sequel::MAJOR == 2 && ::Sequel::MINOR <= 11 end def load_plugins end def load_inflector end def model_from_dataset(dataset) dataset.model_classes[nil] end def define_state_accessor name = self.name owner_class.validates_each(attribute) do |record, attr, value| machine = record.class.state_machine(name) machine.invalidate(record, :state, :invalid) unless machine.states.match(record) end end end version '3.14.x - 3.23.x' do def self.active? defined?(::Sequel::MAJOR) && ::Sequel::MAJOR == 3 && ::Sequel::MINOR >= 14 && ::Sequel::MINOR <= 23 end def handle_validation_failure 'raise_on_failure?(opts) ? raise_hook_failure(:validation) : result' end end end end end state-machine-1.2.0/lib/state_machine/transition.rb0000644000175000017500000003676412305405267021727 0ustar boutilboutilrequire 'state_machine/transition_collection' require 'state_machine/error' module StateMachine # An invalid transition was attempted class InvalidTransition < Error # The machine attempting to be transitioned attr_reader :machine # The current state value for the machine attr_reader :from def initialize(object, machine, event) #:nodoc: @machine = machine @from_state = machine.states.match!(object) @from = machine.read(object, :state) @event = machine.events.fetch(event) errors = machine.errors_for(object) message = "Cannot transition #{machine.name} via :#{self.event} from #{from_name.inspect}" message << " (Reason(s): #{errors})" unless errors.empty? super(object, message) end # The event that triggered the failed transition def event @event.name end # The fully-qualified name of the event that triggered the failed transition def qualified_event @event.qualified_name end # The name for the current state def from_name @from_state.name end # The fully-qualified name for the current state def qualified_from_name @from_state.qualified_name end end # A set of transition failed to run in parallel class InvalidParallelTransition < Error # The set of events that failed the transition(s) attr_reader :events def initialize(object, events) #:nodoc: @events = events super(object, "Cannot run events in parallel: #{events * ', '}") end end # A transition represents a state change for a specific attribute. # # Transitions consist of: # * An event # * A starting state # * An ending state class Transition # The object being transitioned attr_reader :object # The state machine for which this transition is defined attr_reader :machine # The original state value *before* the transition attr_reader :from # The new state value *after* the transition attr_reader :to # The arguments passed in to the event that triggered the transition # (does not include the +run_action+ boolean argument if specified) attr_accessor :args # The result of invoking the action associated with the machine attr_reader :result # Whether the transition is only existing temporarily for the object attr_writer :transient # Determines whether the curreny ruby implementation supports pausing and # resuming transitions def self.pause_supported? !defined?(RUBY_ENGINE) || %w(ruby maglev).include?(RUBY_ENGINE) end # Creates a new, specific transition def initialize(object, machine, event, from_name, to_name, read_state = true) #:nodoc: @object = object @machine = machine @args = [] @transient = false @resume_block = nil @event = machine.events.fetch(event) @from_state = machine.states.fetch(from_name) @from = read_state ? machine.read(object, :state) : @from_state.value @to_state = machine.states.fetch(to_name) @to = @to_state.value reset end # The attribute which this transition's machine is defined for def attribute machine.attribute end # The action that will be run when this transition is performed def action machine.action end # The event that triggered the transition def event @event.name end # The fully-qualified name of the event that triggered the transition def qualified_event @event.qualified_name end # The human-readable name of the event that triggered the transition def human_event @event.human_name(@object.class) end # The state name *before* the transition def from_name @from_state.name end # The fully-qualified state name *before* the transition def qualified_from_name @from_state.qualified_name end # The human-readable state name *before* the transition def human_from_name @from_state.human_name(@object.class) end # The new state name *after* the transition def to_name @to_state.name end # The new fully-qualified state name *after* the transition def qualified_to_name @to_state.qualified_name end # The new human-readable state name *after* the transition def human_to_name @to_state.human_name(@object.class) end # Does this transition represent a loopback (i.e. the from and to state # are the same) # # == Example # # machine = StateMachine.new(Vehicle) # StateMachine::Transition.new(Vehicle.new, machine, :park, :parked, :parked).loopback? # => true # StateMachine::Transition.new(Vehicle.new, machine, :park, :idling, :parked).loopback? # => false def loopback? from_name == to_name end # Is this transition existing for a short period only? If this is set, it # indicates that the transition (or the event backing it) should not be # written to the object if it fails. def transient? @transient end # A hash of all the core attributes defined for this transition with their # names as keys and values of the attributes as values. # # == Example # # machine = StateMachine.new(Vehicle) # transition = StateMachine::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling) # transition.attributes # => {:object => #, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'} def attributes @attributes ||= {:object => object, :attribute => attribute, :event => event, :from => from, :to => to} end # Runs the actual transition and any before/after callbacks associated # with the transition. The action associated with the transition/machine # can be skipped by passing in +false+. # # == Examples # # class Vehicle # state_machine :action => :save do # ... # end # end # # vehicle = Vehicle.new # transition = StateMachine::Transition.new(vehicle, machine, :ignite, :parked, :idling) # transition.perform # => Runs the +save+ action after setting the state attribute # transition.perform(false) # => Only sets the state attribute # transition.perform(Time.now) # => Passes in additional arguments and runs the +save+ action # transition.perform(Time.now, false) # => Passes in additional arguments and only sets the state attribute def perform(*args) run_action = [true, false].include?(args.last) ? args.pop : true self.args = args # Run the transition !!TransitionCollection.new([self], :actions => run_action).perform end # Runs a block within a transaction for the object being transitioned. # By default, transactions are a no-op unless otherwise defined by the # machine's integration. def within_transaction machine.within_transaction(object) do yield end end # Runs the before / after callbacks for this transition. If a block is # provided, then it will be executed between the before and after callbacks. # # Configuration options: # * +before+ - Whether to run before callbacks. # * +after+ - Whether to run after callbacks. If false, then any around # callbacks will be paused until called again with +after+ enabled. # Default is true. # # This will return true if all before callbacks gets executed. After # callbacks will not have an effect on the result. def run_callbacks(options = {}, &block) options = {:before => true, :after => true}.merge(options) @success = false halted = pausable { before(options[:after], &block) } if options[:before] # After callbacks are only run if: # * An around callback didn't halt after yielding # * They're enabled or the run didn't succeed after if !(@before_run && halted) && (options[:after] || !@success) @before_run end # Transitions the current value of the state to that specified by the # transition. Once the state is persisted, it cannot be persisted again # until this transition is reset. # # == Example # # class Vehicle # state_machine do # event :ignite do # transition :parked => :idling # end # end # end # # vehicle = Vehicle.new # transition = StateMachine::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling) # transition.persist # # vehicle.state # => 'idling' def persist unless @persisted machine.write(object, :state, to) @persisted = true end end # Rolls back changes made to the object's state via this transition. This # will revert the state back to the +from+ value. # # == Example # # class Vehicle # state_machine :initial => :parked do # event :ignite do # transition :parked => :idling # end # end # end # # vehicle = Vehicle.new # => # # transition = StateMachine::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling) # # # Persist the new state # vehicle.state # => "parked" # transition.persist # vehicle.state # => "idling" # # # Roll back to the original state # transition.rollback # vehicle.state # => "parked" def rollback reset machine.write(object, :state, from) end # Resets any tracking of which callbacks have already been run and whether # the state has already been persisted def reset @before_run = @persisted = @after_run = false @paused_block = nil end # Determines equality of transitions by testing whether the object, states, # and event involved in the transition are equal def ==(other) other.instance_of?(self.class) && other.object == object && other.machine == machine && other.from_name == from_name && other.to_name == to_name && other.event == event end # Generates a nicely formatted description of this transitions's contents. # # For example, # # transition = StateMachine::Transition.new(object, machine, :ignite, :parked, :idling) # transition # => # def inspect "#<#{self.class} #{%w(attribute event from from_name to to_name).map {|attr| "#{attr}=#{send(attr).inspect}"} * ' '}>" end private # Runs a block that may get paused. If the block doesn't pause, then # execution will continue as normal. If the block gets paused, then it # will take care of switching the execution context when it's resumed. # # This will return true if the given block halts for a reason other than # getting paused. def pausable begin halted = !catch(:halt) { yield; true } rescue Exception => error raise unless @resume_block end if @resume_block @resume_block.call(halted, error) else halted end end # Pauses the current callback execution. This should only occur within # around callbacks when the remainder of the callback will be executed at # a later point in time. def pause raise ArgumentError, 'around_transition callbacks cannot be called in multiple execution contexts in java implementations of Ruby. Use before/after_transitions instead.' unless self.class.pause_supported? unless @resume_block require 'continuation' unless defined?(callcc) callcc do |block| @paused_block = block throw :halt, true end end end # Resumes the execution of a previously paused callback execution. Once # the paused callbacks complete, the current execution will continue. def resume if @paused_block halted, error = callcc do |block| @resume_block = block @paused_block.call end @resume_block = @paused_block = nil raise error if error !halted else true end end # Runs the machine's +before+ callbacks for this transition. Only # callbacks that are configured to match the event, from state, and to # state will be invoked. # # Once the callbacks are run, they cannot be run again until this transition # is reset. def before(complete = true, index = 0, &block) unless @before_run while callback = machine.callbacks[:before][index] index += 1 if callback.type == :around # Around callback: need to handle recursively. Execution only gets # paused if: # * The block fails and the callback doesn't run on failures OR # * The block succeeds, but after callbacks are disabled (in which # case a continuation is stored for later execution) return if catch(:cancel) do callback.call(object, context, self) do before(complete, index, &block) pause if @success && !complete throw :cancel, true unless @success end end else # Normal before callback callback.call(object, context, self) end end @before_run = true end action = {:success => true}.merge(block_given? ? yield : {}) @result, @success = action[:result], action[:success] end # Runs the machine's +after+ callbacks for this transition. Only # callbacks that are configured to match the event, from state, and to # state will be invoked. # # Once the callbacks are run, they cannot be run again until this transition # is reset. # # == Halting # # If any callback throws a :halt exception, it will be caught # and the callback chain will be automatically stopped. However, this # exception will not bubble up to the caller since +after+ callbacks # should never halt the execution of a +perform+. def after unless @after_run # First resume previously paused callbacks if resume catch(:halt) do type = @success ? :after : :failure machine.callbacks[type].each {|callback| callback.call(object, context, self)} end end @after_run = true end end # Gets a hash of the context defining this unique transition (including # event, from state, and to state). # # == Example # # machine = StateMachine.new(Vehicle) # transition = StateMachine::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling) # transition.context # => {:on => :ignite, :from => :parked, :to => :idling} def context @context ||= {:on => event, :from => from_name, :to => to_name} end end end state-machine-1.2.0/lib/state_machine/branch.rb0000644000175000017500000002253712305405267020763 0ustar boutilboutilrequire 'state_machine/matcher' require 'state_machine/eval_helpers' require 'state_machine/assertions' module StateMachine # Represents a set of requirements that must be met in order for a transition # or callback to occur. Branches verify that the event, from state, and to # state of the transition match, in addition to if/unless conditionals for # an object's state. class Branch include Assertions include EvalHelpers # The condition that must be met on an object attr_reader :if_condition # The condition that must *not* be met on an object attr_reader :unless_condition # The requirement for verifying the event being matched attr_reader :event_requirement # One or more requirements for verifying the states being matched. All # requirements contain a mapping of {:from => matcher, :to => matcher}. attr_reader :state_requirements # A list of all of the states known to this branch. This will pull states # from the following options (in the same order): # * +from+ / +except_from+ # * +to+ / +except_to+ attr_reader :known_states # Creates a new branch def initialize(options = {}) #:nodoc: # Build conditionals @if_condition = options.delete(:if) @unless_condition = options.delete(:unless) # Build event requirement @event_requirement = build_matcher(options, :on, :except_on) if (options.keys - [:from, :to, :on, :except_from, :except_to, :except_on]).empty? # Explicit from/to requirements specified @state_requirements = [{:from => build_matcher(options, :from, :except_from), :to => build_matcher(options, :to, :except_to)}] else # Separate out the event requirement options.delete(:on) options.delete(:except_on) # Implicit from/to requirements specified @state_requirements = options.collect do |from, to| from = WhitelistMatcher.new(from) unless from.is_a?(Matcher) to = WhitelistMatcher.new(to) unless to.is_a?(Matcher) {:from => from, :to => to} end end # Track known states. The order that requirements are iterated is based # on the priority in which tracked states should be added. @known_states = [] @state_requirements.each do |state_requirement| [:from, :to].each {|option| @known_states |= state_requirement[option].values} end end # Determines whether the given object / query matches the requirements # configured for this branch. In addition to matching the event, from state, # and to state, this will also check whether the configured :if/:unless # conditions pass on the given object. # # == Examples # # branch = StateMachine::Branch.new(:parked => :idling, :on => :ignite) # # # Successful # branch.matches?(object, :on => :ignite) # => true # branch.matches?(object, :from => nil) # => true # branch.matches?(object, :from => :parked) # => true # branch.matches?(object, :to => :idling) # => true # branch.matches?(object, :from => :parked, :to => :idling) # => true # branch.matches?(object, :on => :ignite, :from => :parked, :to => :idling) # => true # # # Unsuccessful # branch.matches?(object, :on => :park) # => false # branch.matches?(object, :from => :idling) # => false # branch.matches?(object, :to => :first_gear) # => false # branch.matches?(object, :from => :parked, :to => :first_gear) # => false # branch.matches?(object, :on => :park, :from => :parked, :to => :idling) # => false def matches?(object, query = {}) !match(object, query).nil? end # Attempts to match the given object / query against the set of requirements # configured for this branch. In addition to matching the event, from state, # and to state, this will also check whether the configured :if/:unless # conditions pass on the given object. # # If a match is found, then the event/state requirements that the query # passed successfully will be returned. Otherwise, nil is returned if there # was no match. # # Query options: # * :from - One or more states being transitioned from. If none # are specified, then this will always match. # * :to - One or more states being transitioned to. If none are # specified, then this will always match. # * :on - One or more events that fired the transition. If none # are specified, then this will always match. # * :guard - Whether to guard matches with the if/unless # conditionals defined for this branch. Default is true. # # == Examples # # branch = StateMachine::Branch.new(:parked => :idling, :on => :ignite) # # branch.match(object, :on => :ignite) # => {:to => ..., :from => ..., :on => ...} # branch.match(object, :on => :park) # => nil def match(object, query = {}) assert_valid_keys(query, :from, :to, :on, :guard) if (match = match_query(query)) && matches_conditions?(object, query) match end end # Draws a representation of this branch on the given graph. This will draw # an edge between every state this branch matches *from* to either the # configured to state or, if none specified, then a loopback to the from # state. # # For example, if the following from states are configured: # * +idling+ # * +first_gear+ # * +backing_up+ # # ...and the to state is +parked+, then the following edges will be created: # * +idling+ -> +parked+ # * +first_gear+ -> +parked+ # * +backing_up+ -> +parked+ # # Each edge will be labeled with the name of the event that would cause the # transition. def draw(graph, event, valid_states) state_requirements.each do |state_requirement| # From states determined based on the known valid states from_states = state_requirement[:from].filter(valid_states) # If a to state is not specified, then it's a loopback and each from # state maps back to itself if state_requirement[:to].values.empty? loopback = true else to_state = state_requirement[:to].values.first to_state = to_state ? to_state.to_s : 'nil' loopback = false end # Generate an edge between each from and to state from_states.each do |from_state| from_state = from_state ? from_state.to_s : 'nil' graph.add_edges(from_state, loopback ? from_state : to_state, :label => event.to_s) end end true end protected # Builds a matcher strategy to use for the given options. If neither a # whitelist nor a blacklist option is specified, then an AllMatcher is # built. def build_matcher(options, whitelist_option, blacklist_option) assert_exclusive_keys(options, whitelist_option, blacklist_option) if options.include?(whitelist_option) value = options[whitelist_option] value.is_a?(Matcher) ? value : WhitelistMatcher.new(options[whitelist_option]) elsif options.include?(blacklist_option) value = options[blacklist_option] raise ArgumentError, ":#{blacklist_option} option cannot use matchers; use :#{whitelist_option} instead" if value.is_a?(Matcher) BlacklistMatcher.new(value) else AllMatcher.instance end end # Verifies that all configured requirements (event and state) match the # given query. If a match is found, then a hash containing the # event/state requirements that passed will be returned; otherwise, nil. def match_query(query) query ||= {} if match_event(query) && (state_requirement = match_states(query)) state_requirement.merge(:on => event_requirement) end end # Verifies that the event requirement matches the given query def match_event(query) matches_requirement?(query, :on, event_requirement) end # Verifies that the state requirements match the given query. If a # matching requirement is found, then it is returned. def match_states(query) state_requirements.detect do |state_requirement| [:from, :to].all? {|option| matches_requirement?(query, option, state_requirement[option])} end end # Verifies that an option in the given query matches the values required # for that option def matches_requirement?(query, option, requirement) !query.include?(option) || requirement.matches?(query[option], query) end # Verifies that the conditionals for this branch evaluate to true for the # given object def matches_conditions?(object, query) query[:guard] == false || Array(if_condition).all? {|condition| evaluate_method(object, condition)} && !Array(unless_condition).any? {|condition| evaluate_method(object, condition)} end end end state-machine-1.2.0/lib/state_machine/state_collection.rb0000644000175000017500000001005112305405267023045 0ustar boutilboutilrequire 'state_machine/node_collection' module StateMachine # Represents a collection of states in a state machine class StateCollection < NodeCollection def initialize(machine) #:nodoc: super(machine, :index => [:name, :qualified_name, :value]) end # Determines whether the given object is in a specific state. If the # object's current value doesn't match the state, then this will return # false, otherwise true. If the given state is unknown, then an IndexError # will be raised. # # == Examples # # class Vehicle # state_machine :initial => :parked do # other_states :idling # end # end # # states = Vehicle.state_machine.states # vehicle = Vehicle.new # => # # # states.matches?(vehicle, :parked) # => true # states.matches?(vehicle, :idling) # => false # states.matches?(vehicle, :invalid) # => IndexError: :invalid is an invalid key for :name index def matches?(object, name) fetch(name).matches?(machine.read(object, :state)) end # Determines the current state of the given object as configured by this # state machine. This will attempt to find a known state that matches # the value of the attribute on the object. # # == Examples # # class Vehicle # state_machine :initial => :parked do # other_states :idling # end # end # # states = Vehicle.state_machine.states # # vehicle = Vehicle.new # => # # states.match(vehicle) # => # # # vehicle.state = 'idling' # states.match(vehicle) # => # # # vehicle.state = 'invalid' # states.match(vehicle) # => nil def match(object) value = machine.read(object, :state) self[value, :value] || detect {|state| state.matches?(value)} end # Determines the current state of the given object as configured by this # state machine. If no state is found, then an ArgumentError will be # raised. # # == Examples # # class Vehicle # state_machine :initial => :parked do # other_states :idling # end # end # # states = Vehicle.state_machine.states # # vehicle = Vehicle.new # => # # states.match!(vehicle) # => # # # vehicle.state = 'invalid' # states.match!(vehicle) # => ArgumentError: "invalid" is not a known state value def match!(object) match(object) || raise(ArgumentError, "#{machine.read(object, :state).inspect} is not a known #{machine.name} value") end # Gets the order in which states should be displayed based on where they # were first referenced. This will order states in the following priority: # # 1. Initial state # 2. Event transitions (:from, :except_from, :to, :except_to options) # 3. States with behaviors # 4. States referenced via +state+ or +other_states+ # 5. States referenced in callbacks # # This order will determine how the GraphViz visualizations are rendered. def by_priority order = select {|state| state.initial}.map {|state| state.name} machine.events.each {|event| order += event.known_states} order += select {|state| state.methods.any?}.map {|state| state.name} order += keys(:name) - machine.callbacks.values.flatten.map {|callback| callback.known_states}.flatten order += keys(:name) order.uniq! order.map! {|name| self[name]} order end private # Gets the value for the given attribute on the node def value(node, attribute) attribute == :value ? node.value(false) : super end end end state-machine-1.2.0/lib/state_machine/path_collection.rb0000644000175000017500000000557312305405267022676 0ustar boutilboutilrequire 'state_machine/path' module StateMachine # Represents a collection of paths that are generated based on a set of # requirements regarding what states to start and end on class PathCollection < Array include Assertions # The object whose state machine is being walked attr_reader :object # The state machine these path are walking attr_reader :machine # The initial state to start each path from attr_reader :from_name # The target state for each path attr_reader :to_name # Creates a new collection of paths with the given requirements. # # Configuration options: # * :from - The initial state to start from # * :to - The target end state # * :deep - Whether to enable deep searches for the target state. # * :guard - Whether to guard transitions with the if/unless # conditionals defined for each one def initialize(object, machine, options = {}) options = {:deep => false, :from => machine.states.match!(object).name}.merge(options) assert_valid_keys(options, :from, :to, :deep, :guard) @object = object @machine = machine @from_name = machine.states.fetch(options[:from]).name @to_name = options[:to] && machine.states.fetch(options[:to]).name @guard = options[:guard] @deep = options[:deep] initial_paths.each {|path| walk(path)} end # Lists all of the states that can be transitioned from through the paths in # this collection. # # For example, # # paths.from_states # => [:parked, :idling, :first_gear, ...] def from_states map {|path| path.from_states}.flatten.uniq end # Lists all of the states that can be transitioned to through the paths in # this collection. # # For example, # # paths.to_states # => [:idling, :first_gear, :second_gear, ...] def to_states map {|path| path.to_states}.flatten.uniq end # Lists all of the events that can be fired through the paths in this # collection. # # For example, # # paths.events # => [:park, :ignite, :shift_up, ...] def events map {|path| path.events}.flatten.uniq end private # Gets the initial set of paths to walk def initial_paths machine.events.transitions_for(object, :from => from_name, :guard => @guard).map do |transition| path = Path.new(object, machine, :target => to_name, :guard => @guard) path << transition path end end # Walks down the given path. Each new path that matches the configured # requirements will be added to this collection. def walk(path) self << path if path.complete? path.walk {|next_path| walk(next_path)} unless to_name && path.complete? && !@deep end end end state-machine-1.2.0/lib/state_machine/macro_methods.rb0000644000175000017500000005310012305405267022340 0ustar boutilboutilrequire 'state_machine/machine' # A state machine is a model of behavior composed of states, events, and # transitions. This helper adds support for defining this type of # functionality on any Ruby class. module StateMachine module MacroMethods # Creates a new state machine with the given name. The default name, if not # specified, is :state. # # Configuration options: # * :attribute - The name of the attribute to store the state value # in. By default, this is the same as the name of the machine. # * :initial - The initial state of the attribute. This can be a # static state or a lambda block which will be evaluated at runtime # (e.g. lambda {|vehicle| vehicle.speed == 0 ? :parked : :idling}). # Default is nil. # * :initialize - Whether to automatically initialize the attribute # by hooking into #initialize on the owner class. Default is true. # * :action - The instance method to invoke when an object # transitions. Default is nil unless otherwise specified by the # configured integration. # * :namespace - The name to use for namespacing all generated # state / event instance methods (e.g. "heater" would generate # :turn_on_heater and :turn_off_heater for the :turn_on/:turn_off events). # Default is nil. # * :integration - The name of the integration to use for adding # library-specific behavior to the machine. Built-in integrations # include :active_model, :active_record, :data_mapper, :mongo_mapper, and # :sequel. By default, this is determined automatically. # # Configuration options relevant to ORM integrations: # * :plural - The pluralized version of the name. By default, this # will attempt to call +pluralize+ on the name. If this method is not # available, an "s" is appended. This is used for generating scopes. # * :messages - The error messages to use when invalidating # objects due to failed transitions. Messages include: # * :invalid # * :invalid_event # * :invalid_transition # * :use_transactions - Whether transactions should be used when # firing events. Default is true unless otherwise specified by the # configured integration. # # This also expects a block which will be used to actually configure the # states, events and transitions for the state machine. *Note* that this # block will be executed within the context of the state machine. As a # result, you will not be able to access any class methods unless you refer # to them directly (i.e. specifying the class name). # # For examples on the types of state machine configurations and blocks, see # the section below. # # == Examples # # With the default name/attribute and no configuration: # # class Vehicle # state_machine do # event :park do # ... # end # end # end # # The above example will define a state machine named "state" that will # store the value in the +state+ attribute. Every vehicle will start # without an initial state. # # With a custom name / attribute: # # class Vehicle # state_machine :status, :attribute => :status_value do # ... # end # end # # With a static initial state: # # class Vehicle # state_machine :status, :initial => :parked do # ... # end # end # # With a dynamic initial state: # # class Vehicle # state_machine :status, :initial => lambda {|vehicle| vehicle.speed == 0 ? :parked : :idling} do # ... # end # end # # == Class Methods # # The following class methods will be automatically generated by the # state machine based on the *name* of the machine. Any existing methods # will not be overwritten. # * human_state_name(state) - Gets the humanized value for the # given state. This may be generated by internationalization libraries if # supported by the integration. # * human_state_event_name(event) - Gets the humanized value for # the given event. This may be generated by internationalization # libraries if supported by the integration. # # For example, # # class Vehicle # state_machine :state, :initial => :parked do # event :ignite do # transition :parked => :idling # end # # event :shift_up do # transition :idling => :first_gear # end # end # end # # Vehicle.human_state_name(:parked) # => "parked" # Vehicle.human_state_name(:first_gear) # => "first gear" # Vehicle.human_state_event_name(:park) # => "park" # Vehicle.human_state_event_name(:shift_up) # => "shift up" # # == Instance Methods # # The following instance methods will be automatically generated by the # state machine based on the *name* of the machine. Any existing methods # will not be overwritten. # * state - Gets the current value for the attribute # * state=(value) - Sets the current value for the attribute # * state?(name) - Checks the given state name against the current # state. If the name is not a known state, then an ArgumentError is raised. # * state_name - Gets the name of the state for the current value # * human_state_name - Gets the human-readable name of the state # for the current value # * state_events(requirements = {}) - Gets the list of events that # can be fired on the current object's state (uses the *unqualified* event # names) # * state_transitions(requirements = {}) - Gets the list of # transitions that can be made on the current object's state # * state_paths(requirements = {}) - Gets the list of sequences of # transitions that can be run from the current object's state # * fire_state_event(name, *args) - Fires an arbitrary event with # the given argument list. This is essentially the same as calling the # actual event method itself. # # The state_events, state_transitions, and state_paths # helpers all take an optional set of requirements for determining what's # available for the current object. These requirements include: # * :from - One or more states to transition from. If none are # specified, then this will be the object's current state. # * :to - One or more states to transition to. If none are # specified, then this will match any to state. # * :on - One or more events to transition on. If none are # specified, then this will match any event. # * :guard - Whether to guard transitions with the if/unless # conditionals defined for each one. Default is true. # # For example, # # class Vehicle # state_machine :state, :initial => :parked do # event :ignite do # transition :parked => :idling # end # # event :park do # transition :idling => :parked # end # end # end # # vehicle = Vehicle.new # vehicle.state # => "parked" # vehicle.state_name # => :parked # vehicle.human_state_name # => "parked" # vehicle.state?(:parked) # => true # # # Changing state # vehicle.state = 'idling' # vehicle.state # => "idling" # vehicle.state_name # => :idling # vehicle.state?(:parked) # => false # # # Getting current event / transition availability # vehicle.state_events # => [:park] # vehicle.park # => true # vehicle.state_events # => [:ignite] # vehicle.state_events(:from => :idling) # => [:park] # vehicle.state_events(:to => :parked) # => [] # # vehicle.state_transitions # => [#] # vehicle.ignite # => true # vehicle.state_transitions # => [#] # # vehicle.state_transitions(:on => :ignite) # => [] # # # Getting current path availability # vehicle.state_paths # => [ # # [#, # # #] # # ] # vehicle.state_paths(:guard => false) # => # # [#, # # #] # # ] # # # Fire arbitrary events # vehicle.fire_state_event(:park) # => true # # == Attribute initialization # # For most classes, the initial values for state machine attributes are # automatically assigned when a new object is created. However, this # behavior will *not* work if the class defines an +initialize+ method # without properly calling +super+. # # For example, # # class Vehicle # state_machine :state, :initial => :parked do # ... # end # end # # vehicle = Vehicle.new # => # # vehicle.state # => "parked" # # In the above example, no +initialize+ method is defined. As a result, # the default behavior of initializing the state machine attributes is used. # # In the following example, a custom +initialize+ method is defined: # # class Vehicle # state_machine :state, :initial => :parked do # ... # end # # def initialize # end # end # # vehicle = Vehicle.new # => # # vehicle.state # => nil # # Since the +initialize+ method is defined, the state machine attributes # never get initialized. In order to ensure that all initialization hooks # are called, the custom method *must* call +super+ without any arguments # like so: # # class Vehicle # state_machine :state, :initial => :parked do # ... # end # # def initialize(attributes = {}) # ... # super() # end # end # # vehicle = Vehicle.new # => # # vehicle.state # => "parked" # # Because of the way the inclusion of modules works in Ruby, calling # super() will not only call the superclass's +initialize+, but # also +initialize+ on all included modules. This allows the original state # machine hook to get called properly. # # If you want to avoid calling the superclass's constructor, but still want # to initialize the state machine attributes: # # class Vehicle # state_machine :state, :initial => :parked do # ... # end # # def initialize(attributes = {}) # ... # initialize_state_machines # end # end # # vehicle = Vehicle.new # => # # vehicle.state # => "parked" # # You may also need to call the +initialize_state_machines+ helper manually # in cases where you want to change how static / dynamic initial states get # set. For example, the following example forces the initialization of # static states regardless of their current value: # # class Vehicle # state_machine :state, :initial => :parked do # state nil, :idling # ... # end # # def initialize(attributes = {}) # @state = 'idling' # initialize_state_machines(:static => :force) do # ... # end # end # end # # vehicle = Vehicle.new # => # # vehicle.state # => "parked" # # The above example is also noteworthy because it demonstrates how to avoid # initialization issues when +nil+ is a valid state. Without passing in # :static => :force, state_machine would never have initialized # the state because +nil+ (the default attribute value) would have been # interpreted as a valid current state. As a result, state_machine would # have simply skipped initialization. # # == States # # All of the valid states for the machine are automatically tracked based # on the events, transitions, and callbacks defined for the machine. If # there are additional states that are never referenced, these should be # explicitly added using the StateMachine::Machine#state or # StateMachine::Machine#other_states helpers. # # When a new state is defined, a predicate method for that state is # generated on the class. For example, # # class Vehicle # state_machine :initial => :parked do # event :ignite do # transition all => :idling # end # end # end # # ...will generate the following instance methods (assuming they're not # already defined in the class): # * parked? # * idling? # # Each predicate method will return true if it matches the object's # current state. Otherwise, it will return false. # # == Attribute access # # The actual value for a state is stored in the attribute configured for the # state machine. In most cases, this is the same as the name of the state # machine. For example: # # class Vehicle # attr_accessor :state # # state_machine :state, :initial => :parked do # ... # state :parked, :value => 0 # start :idling, :value => 1 # end # end # # vehicle = Vehicle.new # => # # vehicle.state # => 0 # vehicle.parked? # => true # vehicle.state = 1 # vehicle.idling? # => true # # The most important thing to note from the example above is what it means # to read from and write to the state machine's attribute. In particular, # state_machine treats the attribute (+state+ in this case) like a basic # attr_accessor that's been defined on the class. There are no special # behaviors added, such as allowing the attribute to be written to based on # the name of a state in the machine. This is the case for a few reasons: # * Setting the attribute directly is an edge case that is meant to only be # used when you want to skip state_machine altogether. This means that # state_machine shouldn't have any effect on the attribute accessor # methods. If you want to change the state, you should be using one of # the events defined in the state machine. # * Many ORMs provide custom behavior for the attribute reader / writer - it # may even be defined by your own framework / method implementation just # the example above showed. In order to avoid having to worry about the # different ways an attribute can get written, state_machine just makes # sure that the configured value for a state is always used when writing # to the attribute. # # If you were interested in accessing the name of a state (instead of its # actual value through the attribute), you could do the following: # # vehicle.state_name # => :idling # # == Events and Transitions # # Events defined on the machine are the interface to transitioning states # for an object. Events can be fired either directly (through the method # generated for the event) or indirectly (through attributes defined on # the machine). # # For example, # # class Vehicle # include DataMapper::Resource # property :id, Serial # # state_machine :initial => :parked do # event :ignite do # transition :parked => :idling # end # end # # state_machine :alarm_state, :initial => :active do # event :disable do # transition all => :off # end # end # end # # # Fire +ignite+ event directly # vehicle = Vehicle.create # => # # vehicle.ignite # => true # vehicle.state # => "idling" # vehicle.alarm_state # => "active" # # # Fire +disable+ event automatically # vehicle.alarm_state_event = 'disable' # vehicle.save # => true # vehicle.alarm_state # => "off" # # In the above example, the +state+ attribute is transitioned using the # +ignite+ action that's generated from the state machine. On the other # hand, the +alarm_state+ attribute is transitioned using the +alarm_state_event+ # attribute that automatically gets fired when the machine's action (+save+) # is invoked. # # For more information about how to configure an event and its associated # transitions, see StateMachine::Machine#event. # # == Defining callbacks # # Within the +state_machine+ block, you can also define callbacks for # transitions. For more information about defining these callbacks, # see StateMachine::Machine#before_transition, StateMachine::Machine#after_transition, # and StateMachine::Machine#around_transition, and StateMachine::Machine#after_failure. # # == Namespaces # # When a namespace is configured for a state machine, the name provided # will be used in generating the instance methods for interacting with # states/events in the machine. This is particularly useful when a class # has multiple state machines and it would be difficult to differentiate # between the various states / events. # # For example, # # class Vehicle # state_machine :heater_state, :initial => :off, :namespace => 'heater' do # event :turn_on do # transition all => :on # end # # event :turn_off do # transition all => :off # end # end # # state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do # event :turn_on do # transition all => :active # end # # event :turn_off do # transition all => :off # end # end # end # # The above class defines two state machines: +heater_state+ and +alarm_state+. # For the +heater_state+ machine, the following methods are generated since # it's namespaced by "heater": # * can_turn_on_heater? # * turn_on_heater # * ... # * can_turn_off_heater? # * turn_off_heater # * .. # * heater_off? # * heater_on? # # As shown, each method is unique to the state machine so that the states # and events don't conflict. The same goes for the +alarm_state+ machine: # * can_turn_on_alarm? # * turn_on_alarm # * ... # * can_turn_off_alarm? # * turn_off_alarm # * .. # * alarm_active? # * alarm_off? # # == Scopes # # For integrations that support it, a group of default scope filters will # be automatically created for assisting in finding objects that have the # attribute set to one of a given set of states. # # For example, # # Vehicle.with_state(:parked) # => All vehicles where the state is parked # Vehicle.with_states(:parked, :idling) # => All vehicles where the state is either parked or idling # # Vehicle.without_state(:parked) # => All vehicles where the state is *not* parked # Vehicle.without_states(:parked, :idling) # => All vehicles where the state is *not* parked or idling # # *Note* that if class methods already exist with those names (i.e. # :with_state, :with_states, :without_state, or :without_states), then a # scope will not be defined for that name. # # See StateMachine::Machine for more information about using integrations # and the individual integration docs for information about the actual # scopes that are generated. def state_machine(*args, &block) StateMachine::Machine.find_or_create(self, *args, &block) end end end state-machine-1.2.0/lib/state_machine/path.rb0000644000175000017500000001027512305405267020456 0ustar boutilboutilmodule StateMachine # A path represents a sequence of transitions that can be run for a particular # object. Paths can walk to new transitions, revealing all of the possible # branches that can be encountered in the object's state machine. class Path < Array include Assertions # The object whose state machine is being walked attr_reader :object # The state machine this path is walking attr_reader :machine # Creates a new transition path for the given object. Initially this is an # empty path. In order to start walking the path, it must be populated with # an initial transition. # # Configuration options: # * :target - The target state to end the path on # * :guard - Whether to guard transitions with the if/unless # conditionals defined for each one def initialize(object, machine, options = {}) assert_valid_keys(options, :target, :guard) @object = object @machine = machine @target = options[:target] @guard = options[:guard] end def initialize_copy(orig) #:nodoc: super @transitions = nil end # The initial state name for this path def from_name first && first.from_name end # Lists all of the from states that can be reached through this path. # # For example, # # path.to_states # => [:parked, :idling, :first_gear, ...] def from_states map {|transition| transition.from_name}.uniq end # The end state name for this path. If a target state was specified for # the path, then that will be returned if the path is complete. def to_name last && last.to_name end # Lists all of the to states that can be reached through this path. # # For example, # # path.to_states # => [:parked, :idling, :first_gear, ...] def to_states map {|transition| transition.to_name}.uniq end # Lists all of the events that can be fired through this path. # # For example, # # path.events # => [:park, :ignite, :shift_up, ...] def events map {|transition| transition.event}.uniq end # Walks down the next transitions at the end of this path. This will only # walk down paths that are considered valid. def walk transitions.each {|transition| yield dup.push(transition)} end # Determines whether or not this path has completed. A path is considered # complete when one of the following conditions is met: # * The last transition in the path ends on the target state # * There are no more transitions remaining to walk and there is no target # state def complete? !empty? && (@target ? to_name == @target : transitions.empty?) end private # Calculates the number of times the given state has been walked to def times_walked_to(state) select {|transition| transition.to_name == state}.length end # Determines whether the given transition has been recently walked down in # this path. If a target is configured for this path, then this will only # look at transitions walked down since the target was last reached. def recently_walked?(transition) transitions = self if @target && @target != to_name && target_transition = detect {|t| t.to_name == @target} transitions = transitions[index(target_transition) + 1..-1] end transitions.include?(transition) end # Determines whether it's possible to walk to the given transition from # the current path. A transition can be walked to if: # * It has not been recently walked and # * If a target is specified, it has not been walked to twice yet def can_walk_to?(transition) !recently_walked?(transition) && (!@target || times_walked_to(@target) < 2) end # Get the next set of transitions that can be walked to starting from the # end of this path def transitions @transitions ||= empty? ? [] : machine.events.transitions_for(object, :from => to_name, :guard => @guard).select {|transition| can_walk_to?(transition)} end end end state-machine-1.2.0/lib/state_machine/matcher.rb0000644000175000017500000000715012305405267021143 0ustar boutilboutilrequire 'singleton' module StateMachine # Provides a general strategy pattern for determining whether a match is found # for a value. The algorithm that actually determines the match depends on # the matcher in use. class Matcher # The list of values against which queries are matched attr_reader :values # Creates a new matcher for querying against the given set of values def initialize(values = []) @values = values.is_a?(Array) ? values : [values] end # Generates a subset of values that exists in both the set of values being # filtered and the values configured for the matcher def filter(values) self.values & values end end # Matches any given value. Since there is no configuration for this type of # matcher, it must be used as a singleton. class AllMatcher < Matcher include Singleton # Generates a blacklist matcher based on the given set of values # # == Examples # # matcher = StateMachine::AllMatcher.instance - [:parked, :idling] # matcher.matches?(:parked) # => false # matcher.matches?(:first_gear) # => true def -(blacklist) BlacklistMatcher.new(blacklist) end # Always returns true def matches?(value, context = {}) true end # Always returns the given set of values def filter(values) values end # A human-readable description of this matcher. Always "all". def description 'all' end end # Matches a specific set of values class WhitelistMatcher < Matcher # Checks whether the given value exists within the whitelist configured # for this matcher. # # == Examples # # matcher = StateMachine::WhitelistMatcher.new([:parked, :idling]) # matcher.matches?(:parked) # => true # matcher.matches?(:first_gear) # => false def matches?(value, context = {}) values.include?(value) end # A human-readable description of this matcher def description values.length == 1 ? values.first.inspect : values.inspect end end # Matches everything but a specific set of values class BlacklistMatcher < Matcher # Checks whether the given value exists outside the blacklist configured # for this matcher. # # == Examples # # matcher = StateMachine::BlacklistMatcher.new([:parked, :idling]) # matcher.matches?(:parked) # => false # matcher.matches?(:first_gear) # => true def matches?(value, context = {}) !values.include?(value) end # Finds all values that are *not* within the blacklist configured for this # matcher def filter(values) values - self.values end # A human-readable description of this matcher def description "all - #{values.length == 1 ? values.first.inspect : values.inspect}" end end # Matches a loopback of two values within a context. Since there is no # configuration for this type of matcher, it must be used as a singleton. class LoopbackMatcher < Matcher include Singleton # Checks whether the given value matches what the value originally was. # This value should be defined in the context. # # == Examples # # matcher = StateMachine::LoopbackMatcher.instance # matcher.matches?(:parked, :from => :parked) # => true # matcher.matches?(:parked, :from => :idling) # => false def matches?(value, context) context[:from] == value end # A human-readable description of this matcher. Always "same". def description 'same' end end end state-machine-1.2.0/lib/state_machine/extensions.rb0000644000175000017500000001265112305405267021721 0ustar boutilboutilrequire 'state_machine/machine_collection' module StateMachine module ClassMethods def self.extended(base) #:nodoc: base.class_eval do @state_machines = MachineCollection.new end end # Gets the current list of state machines defined for this class. This # class-level attribute acts like an inheritable attribute. The attribute # is available to each subclass, each having a copy of its superclass's # attribute. # # The hash of state machines maps :attribute => +machine+, e.g. # # Vehicle.state_machines # => {:state => #} def state_machines @state_machines ||= superclass.state_machines.dup end end module InstanceMethods # Runs one or more events in parallel. All events will run through the # following steps: # * Before callbacks # * Persist state # * Invoke action # * After callbacks # # For example, if two events (for state machines A and B) are run in # parallel, the order in which steps are run is: # * A - Before transition callbacks # * B - Before transition callbacks # * A - Persist new state # * B - Persist new state # * A - Invoke action # * B - Invoke action (only if different than A's action) # * A - After transition callbacks # * B - After transition callbacks # # *Note* that multiple events on the same state machine / attribute cannot # be run in parallel. If this is attempted, an ArgumentError will be # raised. # # == Halting callbacks # # When running multiple events in parallel, special consideration should # be taken with regard to how halting within callbacks affects the flow. # # For *before* callbacks, any :halt error that's thrown will # immediately cancel the perform for all transitions. As a result, it's # possible for one event's transition to affect the continuation of # another. # # On the other hand, any :halt error that's thrown within an # *after* callback with only affect that event's transition. Other # transitions will continue to run their own callbacks. # # == Example # # class Vehicle # state_machine :initial => :parked do # event :ignite do # transition :parked => :idling # end # # event :park do # transition :idling => :parked # end # end # # state_machine :alarm_state, :namespace => 'alarm', :initial => :on do # event :enable do # transition all => :active # end # # event :disable do # transition all => :off # end # end # end # # vehicle = Vehicle.new # => # # vehicle.state # => "parked" # vehicle.alarm_state # => "active" # # vehicle.fire_events(:ignite, :disable_alarm) # => true # vehicle.state # => "idling" # vehicle.alarm_state # => "off" # # # If any event fails, the entire event chain fails # vehicle.fire_events(:ignite, :enable_alarm) # => false # vehicle.state # => "idling" # vehicle.alarm_state # => "off" # # # Exception raised on invalid event # vehicle.fire_events(:park, :invalid) # => StateMachine::InvalidEvent: :invalid is an unknown event # vehicle.state # => "idling" # vehicle.alarm_state # => "off" def fire_events(*events) self.class.state_machines.fire_events(self, *events) end # Run one or more events in parallel. If any event fails to run, then # a StateMachine::InvalidTransition exception will be raised. # # See StateMachine::InstanceMethods#fire_events for more information. # # == Example # # class Vehicle # state_machine :initial => :parked do # event :ignite do # transition :parked => :idling # end # # event :park do # transition :idling => :parked # end # end # # state_machine :alarm_state, :namespace => 'alarm', :initial => :active do # event :enable do # transition all => :active # end # # event :disable do # transition all => :off # end # end # end # # vehicle = Vehicle.new # => # # vehicle.fire_events(:ignite, :disable_alarm) # => true # # vehicle.fire_events!(:ignite, :disable_alarm) # => StateMachine::InvalidTranstion: Cannot run events in parallel: ignite, disable_alarm def fire_events!(*events) run_action = [true, false].include?(events.last) ? events.pop : true fire_events(*(events + [run_action])) || raise(StateMachine::InvalidParallelTransition.new(self, events)) end protected def initialize_state_machines(options = {}, &block) #:nodoc: self.class.state_machines.initialize_states(self, options, &block) end end end state-machine-1.2.0/lib/state_machine/state.rb0000644000175000017500000002637012305405267020645 0ustar boutilboutilrequire 'state_machine/assertions' require 'state_machine/state_context' module StateMachine # A state defines a value that an attribute can be in after being transitioned # 0 or more times. States can represent a value of any type in Ruby, though # the most common (and default) type is String. # # In addition to defining the machine's value, a state can also define a # behavioral context for an object when that object is in the state. See # StateMachine::Machine#state for more information about how state-driven # behavior can be utilized. class State include Assertions # The state machine for which this state is defined attr_accessor :machine # The unique identifier for the state used in event and callback definitions attr_reader :name # The fully-qualified identifier for the state, scoped by the machine's # namespace attr_reader :qualified_name # The human-readable name for the state attr_writer :human_name # The value that is written to a machine's attribute when an object # transitions into this state attr_writer :value # Whether this state's value should be cached after being evaluated attr_accessor :cache # Whether or not this state is the initial state to use for new objects attr_accessor :initial alias_method :initial?, :initial # A custom lambda block for determining whether a given value matches this # state attr_accessor :matcher # Tracks all of the methods that have been defined for the machine's owner # class when objects are in this state. # # Maps :method_name => UnboundMethod attr_reader :methods # Creates a new state within the context of the given machine. # # Configuration options: # * :initial - Whether this state is the beginning state for the # machine. Default is false. # * :value - The value to store when an object transitions to this # state. Default is the name (stringified). # * :cache - If a dynamic value (via a lambda block) is being used, # then setting this to true will cache the evaluated result # * :if - Determines whether a value matches this state # (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}). # By default, the configured value is matched. # * :human_name - The human-readable version of this state's name def initialize(machine, name, options = {}) #:nodoc: assert_valid_keys(options, :initial, :value, :cache, :if, :human_name) @machine = machine @name = name @qualified_name = name && machine.namespace ? :"#{machine.namespace}_#{name}" : name @human_name = options[:human_name] || (@name ? @name.to_s.tr('_', ' ') : 'nil') @value = options.include?(:value) ? options[:value] : name && name.to_s @cache = options[:cache] @matcher = options[:if] @methods = {} @initial = options[:initial] == true if name conflicting_machines = machine.owner_class.state_machines.select {|other_name, other_machine| other_machine != machine && other_machine.states[qualified_name, :qualified_name]} # Output a warning if another machine has a conflicting qualified name # for a different attribute if conflict = conflicting_machines.detect {|other_name, other_machine| other_machine.attribute != machine.attribute} name, other_machine = conflict warn "State #{qualified_name.inspect} for #{machine.name.inspect} is already defined in #{other_machine.name.inspect}" elsif conflicting_machines.empty? # Only bother adding predicates when another machine for the same # attribute hasn't already done so add_predicate end end end # Creates a copy of this state in addition to the list of associated # methods to prevent conflicts across different states. def initialize_copy(orig) #:nodoc: super @methods = methods.dup end # Determines whether there are any states that can be transitioned to from # this state. If there are none, then this state is considered *final*. # Any objects in a final state will remain so forever given the current # machine's definition. def final? !machine.events.any? do |event| event.branches.any? do |branch| branch.state_requirements.any? do |requirement| requirement[:from].matches?(name) && !requirement[:to].matches?(name, :from => name) end end end end # Transforms the state name into a more human-readable format, such as # "first gear" instead of "first_gear" def human_name(klass = @machine.owner_class) @human_name.is_a?(Proc) ? @human_name.call(self, klass) : @human_name end # Generates a human-readable description of this state's name / value: # # For example, # # State.new(machine, :parked).description # => "parked" # State.new(machine, :parked, :value => :parked).description # => "parked" # State.new(machine, :parked, :value => nil).description # => "parked (nil)" # State.new(machine, :parked, :value => 1).description # => "parked (1)" # State.new(machine, :parked, :value => lambda {Time.now}).description # => "parked (*) # # Configuration options: # * :human_name - Whether to use this state's human name in the # description or just the internal name def description(options = {}) label = options[:human_name] ? human_name : name description = label ? label.to_s : label.inspect description << " (#{@value.is_a?(Proc) ? '*' : @value.inspect})" unless name.to_s == @value.to_s description end # The value that represents this state. This will optionally evaluate the # original block if it's a lambda block. Otherwise, the static value is # returned. # # For example, # # State.new(machine, :parked, :value => 1).value # => 1 # State.new(machine, :parked, :value => lambda {Time.now}).value # => Tue Jan 01 00:00:00 UTC 2008 # State.new(machine, :parked, :value => lambda {Time.now}).value(false) # => def value(eval = true) if @value.is_a?(Proc) && eval if cache_value? @value = @value.call machine.states.update(self) @value else @value.call end else @value end end # Determines whether this state matches the given value. If no matcher is # configured, then this will check whether the values are equivalent. # Otherwise, the matcher will determine the result. # # For example, # # # Without a matcher # state = State.new(machine, :parked, :value => 1) # state.matches?(1) # => true # state.matches?(2) # => false # # # With a matcher # state = State.new(machine, :parked, :value => lambda {Time.now}, :if => lambda {|value| !value.nil?}) # state.matches?(nil) # => false # state.matches?(Time.now) # => true def matches?(other_value) matcher ? matcher.call(other_value) : other_value == value end # Defines a context for the state which will be enabled on instances of # the owner class when the machine is in this state. # # This can be called multiple times. Each time a new context is created, # a new module will be included in the owner class. def context(&block) machine_name = machine.name # Evaluate the method definitions context = StateContext.new(self) context.class_eval(&block) context.instance_methods.each do |method| methods[method.to_sym] = context.instance_method(method) # Calls the method defined by the current state of the machine context.class_eval <<-end_eval, __FILE__, __LINE__ + 1 remove_method :#{method} def #{method}(*args, &block) self.class.state_machine(#{machine_name.inspect}).states.fetch(#{name.inspect}).call(self, #{method.inspect}, lambda {super(*args, &block)}, *args, &block) end end_eval end # Include the context so that it can be bound to the owner class (the # context is considered an ancestor, so it's allowed to be bound) machine.owner_class.class_eval { include context } context end # Calls a method defined in this state's context on the given object. All # arguments and any block will be passed into the method defined. # # If the method has never been defined for this state, then a NoMethodError # will be raised. def call(object, method, method_missing = nil, *args, &block) if machine.states.matches?(object, name) && context_method = methods[method.to_sym] # Method is defined by the state: proxy it through context_method.bind(object).call(*args, &block) else # Dispatch to the superclass since the object either isn't in this state # or this state doesn't handle the method method_missing.call if method_missing end end # Draws a representation of this state on the given machine. This will # create a new node on the graph with the following properties: # * +label+ - The human-friendly description of the state. # * +width+ - The width of the node. Always 1. # * +height+ - The height of the node. Always 1. # * +shape+ - The actual shape of the node. If the state is a final # state, then "doublecircle", otherwise "ellipse". # # Configuration options: # * :human_name - Whether to use the state's human name for the # node's label that gets drawn on the graph def draw(graph, options = {}) node = graph.add_nodes(name ? name.to_s : 'nil', :label => description(options), :width => '1', :height => '1', :shape => final? ? 'doublecircle' : 'ellipse' ) # Add open arrow for initial state graph.add_edges(graph.add_nodes('starting_state', :shape => 'point'), node) if initial? true end # Generates a nicely formatted description of this state's contents. # # For example, # # state = StateMachine::State.new(machine, :parked, :value => 1, :initial => true) # state # => # def inspect attributes = [[:name, name], [:value, @value], [:initial, initial?], [:context, methods.keys]] "#<#{self.class} #{attributes.map {|attr, value| "#{attr}=#{value.inspect}"} * ' '}>" end private # Should the value be cached after it's evaluated for the first time? def cache_value? @cache end # Adds a predicate method to the owner class so long as a name has # actually been configured for the state def add_predicate # Checks whether the current value matches this state machine.define_helper(:instance, "#{qualified_name}?") do |machine, object| machine.states.matches?(object, name) end end end end state-machine-1.2.0/lib/state_machine/core.rb0000644000175000017500000000077612305405267020457 0ustar boutilboutilmodule StateMachine # Graphing extensions aren't required, so they're loaded when referenced autoload :Graph, 'state_machine/graph' end # Load all of the core implementation required to use state_machine. This # includes: # * StateMachine::MacroMethods which adds the state_machine DSL to your class # * A set of initializers for setting state_machine defaults based on the current # running environment (such as within Rails) require 'state_machine/macro_methods' require 'state_machine/initializers' state-machine-1.2.0/lib/state_machine/helper_module.rb0000644000175000017500000000105012305405267022335 0ustar boutilboutilmodule StateMachine # Represents a type of module that defines instance / class methods for a # state machine class HelperModule < Module #:nodoc: def initialize(machine, kind) @machine = machine @kind = kind end # Provides a human-readable description of the module def to_s owner_class = @machine.owner_class owner_class_name = owner_class.name && !owner_class.name.empty? ? owner_class.name : owner_class.to_s "#{owner_class_name} #{@machine.name.inspect} #{@kind} helpers" end end end state-machine-1.2.0/lib/state_machine/core_ext/0000755000175000017500000000000012305405267021000 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/core_ext/class/0000755000175000017500000000000012305405267022105 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/core_ext/class/state_machine.rb0000644000175000017500000000014412305405267025235 0ustar boutilboutilrequire 'state_machine/macro_methods' Class.class_eval do include StateMachine::MacroMethods end state-machine-1.2.0/lib/state_machine/callback.rb0000644000175000017500000002047612305405267021262 0ustar boutilboutilrequire 'state_machine/branch' require 'state_machine/eval_helpers' module StateMachine # Callbacks represent hooks into objects that allow logic to be triggered # before, after, or around a specific set of transitions. class Callback include EvalHelpers class << self # Determines whether to automatically bind the callback to the object # being transitioned. This only applies to callbacks that are defined as # lambda blocks (or Procs). Some integrations, such as DataMapper, handle # callbacks by executing them bound to the object involved, while other # integrations, such as ActiveRecord, pass the object as an argument to # the callback. This can be configured on an application-wide basis by # setting this configuration to +true+ or +false+. The default value # is +false+. # # *Note* that the DataMapper and Sequel integrations automatically # configure this value on a per-callback basis, so it does not have to # be enabled application-wide. # # == Examples # # When not bound to the object: # # class Vehicle # state_machine do # before_transition do |vehicle| # vehicle.set_alarm # end # end # # def set_alarm # ... # end # end # # When bound to the object: # # StateMachine::Callback.bind_to_object = true # # class Vehicle # state_machine do # before_transition do # self.set_alarm # end # end # # def set_alarm # ... # end # end attr_accessor :bind_to_object # The application-wide terminator to use for callbacks when not # explicitly defined. Terminators determine whether to cancel a # callback chain based on the return value of the callback. # # See StateMachine::Callback#terminator for more information. attr_accessor :terminator end # The type of callback chain this callback is for. This can be one of the # following: # * +before+ # * +after+ # * +around+ # * +failure+ attr_accessor :type # An optional block for determining whether to cancel the callback chain # based on the return value of the callback. By default, the callback # chain never cancels based on the return value (i.e. there is no implicit # terminator). Certain integrations, such as ActiveRecord and Sequel, # change this default value. # # == Examples # # Canceling the callback chain without a terminator: # # class Vehicle # state_machine do # before_transition do |vehicle| # throw :halt # end # end # end # # Canceling the callback chain with a terminator value of +false+: # # class Vehicle # state_machine do # before_transition do |vehicle| # false # end # end # end attr_reader :terminator # The branch that determines whether or not this callback can be invoked # based on the context of the transition. The event, from state, and # to state must all match in order for the branch to pass. # # See StateMachine::Branch for more information. attr_reader :branch # Creates a new callback that can get called based on the configured # options. # # In addition to the possible configuration options for branches, the # following options can be configured: # * :bind_to_object - Whether to bind the callback to the object involved. # If set to false, the object will be passed as a parameter instead. # Default is integration-specific or set to the application default. # * :terminator - A block/proc that determines what callback # results should cause the callback chain to halt (if not using the # default throw :halt technique). # # More information about how those options affect the behavior of the # callback can be found in their attribute definitions. def initialize(type, *args, &block) @type = type raise ArgumentError, 'Type must be :before, :after, :around, or :failure' unless [:before, :after, :around, :failure].include?(type) options = args.last.is_a?(Hash) ? args.pop : {} @methods = args @methods.concat(Array(options.delete(:do))) @methods << block if block_given? raise ArgumentError, 'Method(s) for callback must be specified' unless @methods.any? options = {:bind_to_object => self.class.bind_to_object, :terminator => self.class.terminator}.merge(options) # Proxy lambda blocks so that they're bound to the object bind_to_object = options.delete(:bind_to_object) @methods.map! do |method| bind_to_object && method.is_a?(Proc) ? bound_method(method) : method end @terminator = options.delete(:terminator) @branch = Branch.new(options) end # Gets a list of the states known to this callback by looking at the # branch's known states def known_states branch.known_states end # Runs the callback as long as the transition context matches the branch # requirements configured for this callback. If a block is provided, it # will be called when the last method has run. # # If a terminator has been configured and it matches the result from the # evaluated method, then the callback chain should be halted. def call(object, context = {}, *args, &block) if @branch.matches?(object, context) run_methods(object, context, 0, *args, &block) true else false end end private # Runs all of the methods configured for this callback. # # When running +around+ callbacks, this will evaluate each method and # yield when the last method has yielded. The callback will only halt if # one of the methods does not yield. # # For all other types of callbacks, this will evaluate each method in # order. The callback will only halt if the resulting value from the # method passes the terminator. def run_methods(object, context = {}, index = 0, *args, &block) if type == :around if current_method = @methods[index] yielded = false evaluate_method(object, current_method, *args) do yielded = true run_methods(object, context, index + 1, *args, &block) end throw :halt unless yielded else yield if block_given? end else @methods.each do |method| result = evaluate_method(object, method, *args) throw :halt if @terminator && @terminator.call(result) end end end # Generates a method that can be bound to the object being transitioned # when the callback is invoked def bound_method(block) type = self.type arity = block.arity arity += 1 if arity >= 0 # Make sure the object gets passed arity += 1 if arity == 1 && type == :around # Make sure the block gets passed method = if RUBY_VERSION >= '1.9' lambda do |object, *args| object.instance_exec(*args, &block) end else # Generate a thread-safe unbound method that can be used on any object. # This is a workaround for not having Ruby 1.9's instance_exec unbound_method = Object.class_eval do time = Time.now method_name = "__bind_#{time.to_i}_#{time.usec}" define_method(method_name, &block) method = instance_method(method_name) remove_method(method_name) method end # Proxy calls to the method so that the method can be bound *and* # the arguments are adjusted lambda do |object, *args| unbound_method.bind(object).call(*args) end end # Proxy arity to the original block (class << method; self; end).class_eval do define_method(:arity) { arity } end method end end end state-machine-1.2.0/lib/state_machine/state_context.rb0000644000175000017500000001165212305405267022406 0ustar boutilboutilrequire 'state_machine/assertions' require 'state_machine/eval_helpers' module StateMachine # Represents a module which will get evaluated within the context of a state. # # Class-level methods are proxied to the owner class, injecting a custom # :if condition along with method. This assumes that the method has # support for a set of configuration options, including :if. This # condition will check that the object's state matches this context's state. # # Instance-level methods are used to define state-driven behavior on the # state's owner class. # # == Examples # # class Vehicle # class << self # attr_accessor :validations # # def validate(options, &block) # validations << options # end # end # # self.validations = [] # attr_accessor :state, :simulate # # def moving? # self.class.validations.all? {|validation| validation[:if].call(self)} # end # end # # In the above class, a simple set of validation behaviors have been defined. # Each validation consists of a configuration like so: # # Vehicle.validate :unless => :simulate # Vehicle.validate :if => lambda {|vehicle| ...} # # In order to scope validations to a particular state context, the class-level # +validate+ method can be invoked like so: # # machine = StateMachine::Machine.new(Vehicle) # context = StateMachine::StateContext.new(machine.state(:first_gear)) # context.validate(:unless => :simulate) # # vehicle = Vehicle.new # => # # vehicle.moving? # => false # # vehicle.state = 'first_gear' # vehicle.moving? # => true # # vehicle.simulate = true # vehicle.moving? # => false class StateContext < Module include Assertions include EvalHelpers # The state machine for which this context's state is defined attr_reader :machine # The state that must be present in an object for this context to be active attr_reader :state # Creates a new context for the given state def initialize(state) @state = state @machine = state.machine state_name = state.name machine_name = machine.name @condition = lambda {|object| object.class.state_machine(machine_name).states.matches?(object, state_name)} end # Creates a new transition that determines what to change the current state # to when an event fires from this state. # # Since this transition is being defined within a state context, you do # *not* need to specify the :from option for the transition. For # example: # # state_machine do # state :parked do # transition :to => :idling, :on => [:ignite, :shift_up] # Transitions to :idling # transition :from => [:idling, :parked], :on => :park, :unless => :seatbelt_on? # Transitions to :parked if seatbelt is off # end # end # # See StateMachine::Machine#transition for a description of the possible # configurations for defining transitions. def transition(options) assert_valid_keys(options, :from, :to, :on, :if, :unless) raise ArgumentError, 'Must specify :on event' unless options[:on] raise ArgumentError, 'Must specify either :to or :from state' unless !options[:to] ^ !options[:from] machine.transition(options.merge(options[:to] ? {:from => state.name} : {:to => state.name})) end # Hooks in condition-merging to methods that don't exist in this module def method_missing(*args, &block) # Get the configuration if args.last.is_a?(Hash) options = args.last else args << options = {} end # Get any existing condition that may need to be merged if_condition = options.delete(:if) unless_condition = options.delete(:unless) # Provide scope access to configuration in case the block is evaluated # within the object instance proxy = self proxy_condition = @condition # Replace the configuration condition with the one configured for this # proxy, merging together any existing conditions options[:if] = lambda do |*condition_args| # Block may be executed within the context of the actual object, so # it'll either be the first argument or the executing context object = condition_args.first || self proxy.evaluate_method(object, proxy_condition) && Array(if_condition).all? {|condition| proxy.evaluate_method(object, condition)} && !Array(unless_condition).any? {|condition| proxy.evaluate_method(object, condition)} end # Evaluate the method on the owner class with the condition proxied # through machine.owner_class.send(*args, &block) end end end state-machine-1.2.0/lib/state_machine/initializers.rb0000644000175000017500000000025512305405267022225 0ustar boutilboutil# Load each application initializer Dir["#{File.dirname(__FILE__)}/initializers/*.rb"].sort.each do |path| require "state_machine/initializers/#{File.basename(path)}" end state-machine-1.2.0/lib/state_machine/eval_helpers.rb0000644000175000017500000000655012305405267022174 0ustar boutilboutilmodule StateMachine # Provides a set of helper methods for evaluating methods within the context # of an object. module EvalHelpers # Evaluates one of several different types of methods within the context # of the given object. Methods can be one of the following types: # * Symbol # * Method / Proc # * String # # == Examples # # Below are examples of the various ways that a method can be evaluated # on an object: # # class Person # def initialize(name) # @name = name # end # # def name # @name # end # end # # class PersonCallback # def self.run(person) # person.name # end # end # # person = Person.new('John Smith') # # evaluate_method(person, :name) # => "John Smith" # evaluate_method(person, PersonCallback.method(:run)) # => "John Smith" # evaluate_method(person, Proc.new {|person| person.name}) # => "John Smith" # evaluate_method(person, lambda {|person| person.name}) # => "John Smith" # evaluate_method(person, '@name') # => "John Smith" # # == Additional arguments # # Additional arguments can be passed to the methods being evaluated. If # the method defines additional arguments other than the object context, # then all arguments are required. # # For example, # # person = Person.new('John Smith') # # evaluate_method(person, lambda {|person| person.name}, 21) # => "John Smith" # evaluate_method(person, lambda {|person, age| "#{person.name} is #{age}"}, 21) # => "John Smith is 21" # evaluate_method(person, lambda {|person, age| "#{person.name} is #{age}"}, 21, 'male') # => ArgumentError: wrong number of arguments (3 for 2) def evaluate_method(object, method, *args, &block) case method when Symbol klass = (class << object; self; end) args = [] if (klass.method_defined?(method) || klass.private_method_defined?(method)) && object.method(method).arity == 0 object.send(method, *args, &block) when Proc, Method args.unshift(object) arity = method.arity # Procs don't support blocks in < Ruby 1.9, so it's tacked on as an # argument for consistency across versions of Ruby if block_given? && Proc === method && arity != 0 if [1, 2].include?(arity) # Force the block to be either the only argument or the 2nd one # after the object (may mean additional arguments get discarded) args = args[0, arity - 1] + [block] else # Tack the block to the end of the args args << block end else # These method types are only called with 0, 1, or n arguments args = args[0, arity] if [0, 1].include?(arity) end method.is_a?(Proc) ? method.call(*args) : method.call(*args, &block) when String eval(method, object.instance_eval {binding}, &block) else raise ArgumentError, 'Methods must be a symbol denoting the method to call, a block to be invoked, or a string to be evaluated' end end end end state-machine-1.2.0/lib/state_machine/machine_collection.rb0000644000175000017500000000652512305405267023344 0ustar boutilboutilrequire 'state_machine/assertions' module StateMachine # Represents a collection of state machines for a class class MachineCollection < Hash include Assertions # Initializes the state of each machine in the given object. This can allow # states to be initialized in two groups: static and dynamic. For example: # # machines.initialize_states(object) do # # After static state initialization, before dynamic state initialization # end # # If no block is provided, then all states will still be initialized. # # Valid configuration options: # * :static - Whether to initialize static states. If set to # :force, the state will be initialized regardless of its current value. # Default is :force. # * :dynamic - Whether to initialize dynamic states. If set to # :force, the state will be initialized regardless of its current value. # Default is true. # * :to - A hash to write the initialized state to instead of # writing to the object. Default is to write directly to the object. def initialize_states(object, options = {}) assert_valid_keys(options, :static, :dynamic, :to) options = {:static => true, :dynamic => true}.merge(options) each_value do |machine| machine.initialize_state(object, :force => options[:static] == :force, :to => options[:to]) unless machine.dynamic_initial_state? end if options[:static] result = yield if block_given? each_value do |machine| machine.initialize_state(object, :force => options[:dynamic] == :force, :to => options[:to]) if machine.dynamic_initial_state? end if options[:dynamic] result end # Runs one or more events in parallel on the given object. See # StateMachine::InstanceMethods#fire_events for more information. def fire_events(object, *events) run_action = [true, false].include?(events.last) ? events.pop : true # Generate the transitions to run for each event transitions = events.collect do |event_name| # Find the actual event being run event = nil detect {|name, machine| event = machine.events[event_name, :qualified_name]} raise(InvalidEvent.new(object, event_name)) unless event # Get the transition that will be performed for the event unless transition = event.transition_for(object) event.on_failure(object) end transition end.compact # Run the events in parallel only if valid transitions were found for # all of them if events.length == transitions.length TransitionCollection.new(transitions, :actions => run_action).perform else false end end # Builds the collection of transitions for all event attributes defined on # the given object. This will only include events whose machine actions # match the one specified. # # These should only be fired as a result of the action being run. def transitions(object, action, options = {}) transitions = map do |name, machine| machine.events.attribute_transition_for(object, true) if machine.action == action end AttributeTransitionCollection.new(transitions.compact, options) end end end state-machine-1.2.0/lib/state_machine/graph.rb0000644000175000017500000000575612305405267020633 0ustar boutilboutilbegin require 'rubygems' gem 'ruby-graphviz', '>=0.9.17' require 'graphviz' rescue LoadError => ex $stderr.puts "Cannot draw the machine (#{ex.message}). `gem install ruby-graphviz` >= v0.9.17 and try again." raise end require 'state_machine/assertions' module StateMachine # Provides a set of higher-order features on top of the raw GraphViz graphs class Graph < GraphViz include Assertions # The name of the font to draw state names in attr_reader :font # The graph's full filename attr_reader :file_path # The image format to generate the graph in attr_reader :file_format # Creates a new graph with the given name. # # Configuration options: # * :path - The path to write the graph file to. Default is the # current directory ("."). # * :format - The image format to generate the graph in. # Default is "png'. # * :font - The name of the font to draw state names in. # Default is "Arial". # * :orientation - The direction of the graph ("portrait" or # "landscape"). Default is "portrait". def initialize(name, options = {}) options = {:path => '.', :format => 'png', :font => 'Arial', :orientation => 'portrait'}.merge(options) assert_valid_keys(options, :path, :format, :font, :orientation) @font = options[:font] @file_path = File.join(options[:path], "#{name}.#{options[:format]}") @file_format = options[:format] super('G', :rankdir => options[:orientation] == 'landscape' ? 'LR' : 'TB') end # Generates the actual image file based on the nodes / edges added to the # graph. The path to the file is based on the configuration options for # this graph. def output super(@file_format => @file_path) end # Adds a new node to the graph. The font for the node will be automatically # set based on the graph configuration. The generated node will be returned. # # For example, # # graph = StateMachine::Graph.new('test') # graph.add_nodes('parked', :label => 'Parked', :width => '1', :height => '1', :shape => 'ellipse') def add_nodes(*args) node = v0_api? ? add_node(*args) : super node.fontname = @font node end # Adds a new edge to the graph. The font for the edge will be automatically # set based on the graph configuration. The generated edge will be returned. # # For example, # # graph = StateMachine::Graph.new('test') # graph.add_edges('parked', 'idling', :label => 'ignite') def add_edges(*args) edge = v0_api? ? add_edge(*args) : super edge.fontname = @font edge end private # Determines whether the old v0 api is in use def v0_api? version[0] == '0' || version[0] == '1' && version[1] == '0' && version[2] <= '2' end # The ruby-graphviz version data def version Constants::RGV_VERSION.split('.') end end end state-machine-1.2.0/lib/state_machine/yard.rb0000644000175000017500000000024612305405267020456 0ustar boutilboutilmodule StateMachine # YARD plugin for automated documentation module YARD end end require 'state_machine/yard/handlers' require 'state_machine/yard/templates' state-machine-1.2.0/lib/state_machine/matcher_helpers.rb0000644000175000017500000000273012305405267022664 0ustar boutilboutilmodule StateMachine # Provides a set of helper methods for generating matchers module MatcherHelpers # Represents a state that matches all known states in a machine. # # == Examples # # class Vehicle # state_machine do # before_transition any => :parked, :do => lambda {...} # before_transition all - :parked => all - :idling, :do => lambda {} # # event :park # transition all => :parked # end # # event :crash # transition all - :parked => :stalled # end # end # end # # In the above example, +all+ will match the following states since they # are known: # * +parked+ # * +stalled+ # * +idling+ def all AllMatcher.instance end alias_method :any, :all # Represents a state that matches the original +from+ state. This is useful # for defining transitions which are loopbacks. # # == Examples # # class Vehicle # state_machine do # event :ignite # transition [:idling, :first_gear] => same # end # end # end # # In the above example, +same+ will match whichever the from state is. In # the case of the +ignite+ event, it is essential the same as the following: # # transition :parked => :parked, :first_gear => :first_gear def same LoopbackMatcher.instance end end end state-machine-1.2.0/lib/state_machine/core_ext.rb0000644000175000017500000000016312305405267021325 0ustar boutilboutil# Loads all of the extensions to be made to Ruby core classes require 'state_machine/core_ext/class/state_machine' state-machine-1.2.0/lib/state_machine/assertions.rb0000644000175000017500000000324212305405267021710 0ustar boutilboutilmodule StateMachine # Provides a set of helper methods for making assertions about the content # of various objects module Assertions # Validates that the given hash *only* includes the specified valid keys. # If any invalid keys are found, an ArgumentError will be raised. # # == Examples # # options = {:name => 'John Smith', :age => 30} # # assert_valid_keys(options, :name) # => ArgumentError: Invalid key(s): age # assert_valid_keys(options, 'name', 'age') # => ArgumentError: Invalid key(s): age, name # assert_valid_keys(options, :name, :age) # => nil def assert_valid_keys(hash, *valid_keys) invalid_keys = hash.keys - valid_keys raise ArgumentError, "Invalid key(s): #{invalid_keys.join(', ')}" unless invalid_keys.empty? end # Validates that the given hash only includes at *most* one of a set of # exclusive keys. If more than one key is found, an ArgumentError will be # raised. # # == Examples # # options = {:only => :on, :except => :off} # assert_exclusive_keys(options, :only) # => nil # assert_exclusive_keys(options, :except) # => nil # assert_exclusive_keys(options, :only, :except) # => ArgumentError: Conflicting keys: only, except # assert_exclusive_keys(options, :only, :except, :with) # => ArgumentError: Conflicting keys: only, except def assert_exclusive_keys(hash, *exclusive_keys) conflicting_keys = exclusive_keys & hash.keys raise ArgumentError, "Conflicting keys: #{conflicting_keys.join(', ')}" unless conflicting_keys.length <= 1 end end end state-machine-1.2.0/lib/state_machine/transition_collection.rb0000644000175000017500000002012412305405267024121 0ustar boutilboutilmodule StateMachine # Represents a collection of transitions in a state machine class TransitionCollection < Array include Assertions # Whether to skip running the action for each transition's machine attr_reader :skip_actions # Whether to skip running the after callbacks attr_reader :skip_after # Whether transitions should wrapped around a transaction block attr_reader :use_transaction # Creates a new collection of transitions that can be run in parallel. Each # transition *must* be for a different attribute. # # Configuration options: # * :actions - Whether to run the action configured for each transition # * :after - Whether to run after callbacks # * :transaction - Whether to wrap transitions within a transaction def initialize(transitions = [], options = {}) super(transitions) # Determine the validity of the transitions as a whole @valid = all? reject! {|transition| !transition} attributes = map {|transition| transition.attribute}.uniq raise ArgumentError, 'Cannot perform multiple transitions in parallel for the same state machine attribute' if attributes.length != length assert_valid_keys(options, :actions, :after, :transaction) options = {:actions => true, :after => true, :transaction => true}.merge(options) @skip_actions = !options[:actions] @skip_after = !options[:after] @use_transaction = options[:transaction] end # Runs each of the collection's transitions in parallel. # # All transitions will run through the following steps: # 1. Before callbacks # 2. Persist state # 3. Invoke action # 4. After callbacks (if configured) # 5. Rollback (if action is unsuccessful) # # If a block is passed to this method, that block will be called instead # of invoking each transition's action. def perform(&block) reset if valid? if use_event_attributes? && !block_given? each do |transition| transition.transient = true transition.machine.write(object, :event_transition, transition) end run_actions else within_transaction do catch(:halt) { run_callbacks(&block) } rollback unless success? end end end if actions.length == 1 && results.include?(actions.first) results[actions.first] else success? end end protected attr_reader :results #:nodoc: private # Is this a valid set of transitions? If the collection was creating with # any +false+ values for transitions, then the the collection will be # marked as invalid. def valid? @valid end # Did each transition perform successfully? This will only be true if the # following requirements are met: # * No +before+ callbacks halt # * All actions run successfully (always true if skipping actions) def success? @success end # Gets the object being transitioned def object first.object end # Gets the list of actions to run. If configured to skip actions, then # this will return an empty collection. def actions empty? ? [nil] : map {|transition| transition.action}.uniq end # Determines whether an event attribute be used to trigger the transitions # in this collection or whether the transitions be run directly *outside* # of the action. def use_event_attributes? !skip_actions && !skip_after && actions.all? && actions.length == 1 && first.machine.action_hook? end # Resets any information tracked from previous attempts to perform the # collection def reset @results = {} @success = false end # Runs each transition's callbacks recursively. Once all before callbacks # have been executed, the transitions will then be persisted and the # configured actions will be run. # # If any transition fails to run its callbacks, :halt will be thrown. def run_callbacks(index = 0, &block) if transition = self[index] throw :halt unless transition.run_callbacks(:after => !skip_after) do run_callbacks(index + 1, &block) {:result => results[transition.action], :success => success?} end else persist run_actions(&block) end end # Transitions the current value of the object's states to those specified by # each transition def persist each {|transition| transition.persist} end # Runs the actions for each transition. If a block is given method, then it # will be called instead of invoking each transition's action. # # The results of the actions will be used to determine #success?. def run_actions catch_exceptions do @success = if block_given? result = yield actions.each {|action| results[action] = result} !!result else actions.compact.each {|action| !skip_actions && results[action] = object.send(action)} results.values.all? end end end # Rolls back changes made to the object's states via each transition def rollback each {|transition| transition.rollback} end # Wraps the given block with a rescue handler so that any exceptions that # occur will automatically result in the transition rolling back any changes # that were made to the object involved. def catch_exceptions begin yield rescue Exception rollback raise end end # Runs a block within a transaction for the object being transitioned. If # transactions are disabled, then this is a no-op. def within_transaction if use_transaction && !empty? first.within_transaction do yield success? end else yield end end end # Represents a collection of transitions that were generated from attribute- # based events class AttributeTransitionCollection < TransitionCollection def initialize(transitions = [], options = {}) #:nodoc: super(transitions, {:transaction => false, :actions => false}.merge(options)) end private # Hooks into running transition callbacks so that event / event transition # attributes can be properly updated def run_callbacks(index = 0) if index == 0 # Clears any traces of the event attribute to prevent it from being # evaluated multiple times if actions are nested each do |transition| transition.machine.write(object, :event, nil) transition.machine.write(object, :event_transition, nil) end # Rollback only if exceptions occur during before callbacks begin super rescue Exception rollback unless @before_run raise end # Persists transitions on the object if partial transition was successful. # This allows us to reference them later to complete the transition with # after callbacks. each {|transition| transition.machine.write(object, :event_transition, transition)} if skip_after && success? else super end end # Tracks that before callbacks have now completed def persist @before_run = true super end # Resets callback tracking def reset super @before_run = false end # Resets the event attribute so it can be re-evaluated if attempted again def rollback super each {|transition| transition.machine.write(object, :event, transition.event) unless transition.transient?} end end end state-machine-1.2.0/lib/state_machine/event_collection.rb0000644000175000017500000001340712305405267023056 0ustar boutilboutilmodule StateMachine # Represents a collection of events in a state machine class EventCollection < NodeCollection def initialize(machine) #:nodoc: super(machine, :index => [:name, :qualified_name]) end # Gets the list of events that can be fired on the given object. # # Valid requirement options: # * :from - One or more states being transitioned from. If none # are specified, then this will be the object's current state. # * :to - One or more states being transitioned to. If none are # specified, then this will match any to state. # * :on - One or more events that fire the transition. If none # are specified, then this will match any event. # * :guard - Whether to guard transitions with the if/unless # conditionals defined for each one. Default is true. # # == Examples # # class Vehicle # state_machine :initial => :parked do # event :park do # transition :idling => :parked # end # # event :ignite do # transition :parked => :idling # end # end # end # # events = Vehicle.state_machine(:state).events # # vehicle = Vehicle.new # => # # events.valid_for(vehicle) # => [# :idling]>] # # vehicle.state = 'idling' # events.valid_for(vehicle) # => [# :parked]>] def valid_for(object, requirements = {}) match(requirements).select {|event| event.can_fire?(object, requirements)} end # Gets the list of transitions that can be run on the given object. # # Valid requirement options: # * :from - One or more states being transitioned from. If none # are specified, then this will be the object's current state. # * :to - One or more states being transitioned to. If none are # specified, then this will match any to state. # * :on - One or more events that fire the transition. If none # are specified, then this will match any event. # * :guard - Whether to guard transitions with the if/unless # conditionals defined for each one. Default is true. # # == Examples # # class Vehicle # state_machine :initial => :parked do # event :park do # transition :idling => :parked # end # # event :ignite do # transition :parked => :idling # end # end # end # # events = Vehicle.state_machine.events # # vehicle = Vehicle.new # => # # events.transitions_for(vehicle) # => [#] # # vehicle.state = 'idling' # events.transitions_for(vehicle) # => [#] # # # Search for explicit transitions regardless of the current state # events.transitions_for(vehicle, :from => :parked) # => [#] def transitions_for(object, requirements = {}) match(requirements).map {|event| event.transition_for(object, requirements)}.compact end # Gets the transition that should be performed for the event stored in the # given object's event attribute. This also takes an additional parameter # for automatically invalidating the object if the event or transition are # invalid. By default, this is turned off. # # *Note* that if a transition has already been generated for the event, then # that transition will be used. # # == Examples # # class Vehicle < ActiveRecord::Base # state_machine :initial => :parked do # event :ignite do # transition :parked => :idling # end # end # end # # vehicle = Vehicle.new # => # # events = Vehicle.state_machine.events # # vehicle.state_event = nil # events.attribute_transition_for(vehicle) # => nil # Event isn't defined # # vehicle.state_event = 'invalid' # events.attribute_transition_for(vehicle) # => false # Event is invalid # # vehicle.state_event = 'ignite' # events.attribute_transition_for(vehicle) # => # def attribute_transition_for(object, invalidate = false) return unless machine.action result = machine.read(object, :event_transition) || if event_name = machine.read(object, :event) if event = self[event_name.to_sym, :name] event.transition_for(object) || begin # No valid transition: invalidate machine.invalidate(object, :event, :invalid_event, [[:state, machine.states.match!(object).human_name(object.class)]]) if invalidate false end else # Event is unknown: invalidate machine.invalidate(object, :event, :invalid) if invalidate false end end result end private def match(requirements) #:nodoc: requirements && requirements[:on] ? [fetch(requirements.delete(:on))] : self end end end state-machine-1.2.0/lib/state_machine/integrations.rb0000644000175000017500000001222412305405267022224 0ustar boutilboutil# Load each available integration require 'state_machine/integrations/base' Dir["#{File.dirname(__FILE__)}/integrations/*.rb"].sort.each do |path| require "state_machine/integrations/#{File.basename(path)}" end require 'state_machine/error' module StateMachine # An invalid integration was specified class IntegrationNotFound < Error def initialize(name) super(nil, "#{name.inspect} is an invalid integration") end end # Integrations allow state machines to take advantage of features within the # context of a particular library. This is currently most useful with # database libraries. For example, the various database integrations allow # state machines to hook into features like: # * Saving # * Transactions # * Observers # * Scopes # * Callbacks # * Validation errors # # This type of integration allows the user to work with state machines in a # fashion similar to other object models in their application. # # The integration interface is loosely defined by various unimplemented # methods in the StateMachine::Machine class. See that class or the various # built-in integrations for more information about how to define additional # integrations. module Integrations # Attempts to find an integration that matches the given class. This will # look through all of the built-in integrations under the StateMachine::Integrations # namespace and find one that successfully matches the class. # # == Examples # # class Vehicle # end # # class ActiveModelVehicle # include ActiveModel::Observing # include ActiveModel::Validations # end # # class ActiveRecordVehicle < ActiveRecord::Base # end # # class DataMapperVehicle # include DataMapper::Resource # end # # class MongoidVehicle # include Mongoid::Document # end # # class MongoMapperVehicle # include MongoMapper::Document # end # # class SequelVehicle < Sequel::Model # end # # StateMachine::Integrations.match(Vehicle) # => nil # StateMachine::Integrations.match(ActiveModelVehicle) # => StateMachine::Integrations::ActiveModel # StateMachine::Integrations.match(ActiveRecordVehicle) # => StateMachine::Integrations::ActiveRecord # StateMachine::Integrations.match(DataMapperVehicle) # => StateMachine::Integrations::DataMapper # StateMachine::Integrations.match(MongoidVehicle) # => StateMachine::Integrations::Mongoid # StateMachine::Integrations.match(MongoMapperVehicle) # => StateMachine::Integrations::MongoMapper # StateMachine::Integrations.match(SequelVehicle) # => StateMachine::Integrations::Sequel def self.match(klass) all.detect {|integration| integration.matches?(klass)} end # Attempts to find an integration that matches the given list of ancestors. # This will look through all of the built-in integrations under the StateMachine::Integrations # namespace and find one that successfully matches one of the ancestors. # # == Examples # # StateMachine::Integrations.match([]) # => nil # StateMachine::Integrations.match(['ActiveRecord::Base') # => StateMachine::Integrations::ActiveModel def self.match_ancestors(ancestors) all.detect {|integration| integration.matches_ancestors?(ancestors)} end # Finds an integration with the given name. If the integration cannot be # found, then a NameError exception will be raised. # # == Examples # # StateMachine::Integrations.find_by_name(:active_record) # => StateMachine::Integrations::ActiveRecord # StateMachine::Integrations.find_by_name(:active_model) # => StateMachine::Integrations::ActiveModel # StateMachine::Integrations.find_by_name(:data_mapper) # => StateMachine::Integrations::DataMapper # StateMachine::Integrations.find_by_name(:mongoid) # => StateMachine::Integrations::Mongoid # StateMachine::Integrations.find_by_name(:mongo_mapper) # => StateMachine::Integrations::MongoMapper # StateMachine::Integrations.find_by_name(:sequel) # => StateMachine::Integrations::Sequel # StateMachine::Integrations.find_by_name(:invalid) # => StateMachine::IntegrationNotFound: :invalid is an invalid integration def self.find_by_name(name) all.detect {|integration| integration.integration_name == name} || raise(IntegrationNotFound.new(name)) end # Gets a list of all of the available integrations for use. This will # always list the ActiveModel integration last. # # == Example # # StateMachine::Integrations.all # # => [StateMachine::Integrations::ActiveRecord, StateMachine::Integrations::DataMapper # # StateMachine::Integrations::Mongoid, StateMachine::Integrations::MongoMapper, # # StateMachine::Integrations::Sequel, StateMachine::Integrations::ActiveModel] def self.all constants = self.constants.map {|c| c.to_s}.select {|c| c != 'ActiveModel'}.sort << 'ActiveModel' constants.map {|c| const_get(c)} end end end state-machine-1.2.0/lib/state_machine/error.rb0000644000175000017500000000043412305405267020647 0ustar boutilboutilmodule StateMachine # An error occurred during a state machine invocation class Error < StandardError # The object that failed attr_reader :object def initialize(object, message = nil) #:nodoc: @object = object super(message) end end end state-machine-1.2.0/lib/state_machine/version.rb0000644000175000017500000000005412305405267021201 0ustar boutilboutilmodule StateMachine VERSION = '1.2.0' end state-machine-1.2.0/lib/state_machine/initializers/0000755000175000017500000000000012305405267021676 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/initializers/rails.rb0000644000175000017500000000134112305405267023334 0ustar boutilboutilif defined?(Rails) # Track all of the applicable locales to load locale_paths = [] StateMachine::Integrations.all.each do |integration| locale_paths << integration.locale_path if integration.available? && integration.locale_path end if defined?(Rails::Engine) # Rails 3.x class StateMachine::RailsEngine < Rails::Engine rake_tasks do load 'tasks/state_machine.rb' end end if Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR == 0 StateMachine::RailsEngine.paths.config.locales = locale_paths else StateMachine::RailsEngine.paths['config/locales'] = locale_paths end elsif defined?(I18n) # Rails 2.x I18n.load_path.unshift(*locale_paths) end end state-machine-1.2.0/lib/state_machine/initializers/merb.rb0000644000175000017500000000015612305405267023152 0ustar boutilboutilMerb::Plugins.add_rakefiles("#{File.dirname(__FILE__)}/../../tasks/state_machine") if defined?(Merb::Plugins) state-machine-1.2.0/lib/state_machine/yard/0000755000175000017500000000000012305405267020127 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/yard/templates/0000755000175000017500000000000012305405267022125 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/yard/templates/default/0000755000175000017500000000000012305405267023551 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/yard/templates/default/class/0000755000175000017500000000000012305405267024656 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/yard/templates/default/class/html/0000755000175000017500000000000012305405267025622 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/yard/templates/default/class/html/state_machines.erb0000644000175000017500000000060512305405267031304 0ustar boutilboutil

          State Machines

          This class contains <%= state_machines.count %> state machine(s). <% state_machines.each do |name, machine| %>

          <%= h(machine[:name]) %>

          <%= h(machine[:description]) %>

          <% if machine[:image] %> State machine diagram for <%= h(machine[:name]) %> <% end %> <% end %> state-machine-1.2.0/lib/state_machine/yard/templates/default/class/html/setup.rb0000644000175000017500000000160512305405267027311 0ustar boutilboutilrequire 'tempfile' # Define where state machine descriptions will be rendered def init super sections.place(:state_machine_details).before(:children) end # Renders state machine details in the main content of the class's documentation def state_machine_details erb(:state_machines) if state_machines end # Gets a list of state machines prased for this class def state_machines @state_machines ||= begin if state_machines = object['state_machines'] state_machines.each do |name, machine| serializer.serialize(state_machine_image_path(machine), machine[:image]) if machine[:image] end end end end # Generates the image path for the given machine's visualization def state_machine_image_path(machine) base_path = File.dirname(serializer.serialized_path(object)) image_name = "#{object.name}_#{machine[:name]}" "#{File.join(base_path, image_name)}.png" end state-machine-1.2.0/lib/state_machine/yard/templates.rb0000644000175000017500000000020012305405267022442 0ustar boutilboutilrequire 'yard' YARD::Templates::Engine.register_template_path File.expand_path(File.join(File.dirname(__FILE__), 'templates')) state-machine-1.2.0/lib/state_machine/yard/handlers/0000755000175000017500000000000012305405267021727 5ustar boutilboutilstate-machine-1.2.0/lib/state_machine/yard/handlers/machine.rb0000644000175000017500000004044412305405267023666 0ustar boutilboutilrequire 'tempfile' module StateMachine module YARD module Handlers # Handles and processes #state_machine class Machine < Base handles method_call(:state_machine) namespace_only # The generated state machine attr_reader :machine def process # Cross-file storage for state machines globals.state_machines ||= Hash.new {|h, k| h[k] = {}} namespace['state_machines'] ||= {} # Create new machine klass = inherited_machine ? Class.new(inherited_machine.owner_class) : Class.new { extend StateMachine::MacroMethods } @machine = klass.state_machine(name, options) {} # Track the state machine globals.state_machines[namespace.name][name] = machine namespace['state_machines'][name] = {:name => name, :description => statement.docstring} # Parse the block parse_block(statement.last.last, :owner => machine) # Draw the machine for reference in the template file = Tempfile.new(['state_machine', '.png']) begin if machine.draw(:name => File.basename(file.path, '.png'), :path => File.dirname(file.path), :orientation => 'landscape') namespace['state_machines'][name][:image] = file.read end ensure # Clean up tempfile file.close file.unlink end # Define auto-generated methods define_macro_methods define_state_methods define_event_methods end protected # Extracts the machine name's def name @name ||= begin ast = statement.parameters.first if ast && [:symbol_literal, :string_literal].include?(ast.type) extract_node_name(ast) else :state end end end # Extracts the machine options. Note that this will only extract a # subset of the options supported. def options @options ||= begin options = {} ast = statement.parameters(false).last if !inherited_machine && ast && ![:symbol_literal, :string_literal].include?(ast.type) ast.children.each do |assoc| # Only extract important options key = extract_node_name(assoc[0]) next unless [:initial, :attribute, :namespace, :action].include?(key) value = extract_node_name(assoc[1]) options[key] = value end end options end end # Gets the machine that was inherited from a superclass. This also # ensures each ancestor has been loaded prior to looking up their definitions. def inherited_machine @inherited_machine ||= begin namespace.inheritance_tree.each do |ancestor| begin ensure_loaded!(ancestor) rescue ::YARD::Handlers::NamespaceMissingError # Ignore: just means that we can't access an ancestor end end # Find the first ancestor that has the machine loaded_superclasses.detect do |superclass| if superclass != namespace machine = globals.state_machines[superclass.name][name] break machine if machine end end end end # Gets members of this class's superclasses have already been loaded # by YARD def loaded_superclasses namespace.inheritance_tree.select {|ancestor| ancestor.is_a?(::YARD::CodeObjects::ClassObject)} end # Gets a list of all attributes for the current class, including those # that are inherited def instance_attributes attributes = {} loaded_superclasses.each {|superclass| attributes.merge!(superclass.instance_attributes)} attributes end # Gets the type of ORM integration being used based on the list of # ancestors (including mixins) def integration @integration ||= Integrations.match_ancestors(namespace.inheritance_tree(true).map {|ancestor| ancestor.path}) end # Gets the class type being used to define states. Default is "Symbol". def state_type @state_type ||= machine.states.any? ? machine.states.map {|state| state.name}.compact.first.class.to_s : 'Symbol' end # Gets the class type being used to define events. Default is "Symbol". def event_type @event_type ||= machine.events.any? ? machine.events.first.name.class.to_s : 'Symbol' end # Defines auto-generated macro methods for the given machine def define_macro_methods return if inherited_machine # Human state name lookup register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:name)}", :class)) m.docstring = [ "Gets the humanized name for the given state.", "@param [#{state_type}] state The state to look up", "@return [String] The human state name" ] m.parameters = ["state"] # Human event name lookup register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:event_name)}", :class)) m.docstring = [ "Gets the humanized name for the given event.", "@param [#{event_type}] event The event to look up", "@return [String] The human event name" ] m.parameters = ["event"] # Only register attributes when the accessor isn't explicitly defined # by the class / superclass *and* isn't defined by inference from the # ORM being used unless integration || instance_attributes.include?(machine.attribute.to_sym) attribute = machine.attribute namespace.attributes[:instance][attribute] = {} # Machine attribute getter register(m = ::YARD::CodeObjects::MethodObject.new(namespace, attribute)) namespace.attributes[:instance][attribute][:read] = m m.docstring = [ "Gets the current attribute value for the machine", "@return The attribute value" ] # Machine attribute setter register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{attribute}=")) namespace.attributes[:instance][attribute][:write] = m m.docstring = [ "Sets the current value for the machine", "@param new_#{attribute} The new value to set" ] m.parameters = ["new_#{attribute}"] end if integration && integration.defaults[:action] && !options.include?(:action) || options[:action] attribute = "#{machine.name}_event" namespace.attributes[:instance][attribute] = {} # Machine event attribute getter register(m = ::YARD::CodeObjects::MethodObject.new(namespace, attribute)) namespace.attributes[:instance][attribute][:read] = m m.docstring = [ "Gets the current event attribute value for the machine", "@return The event attribute value" ] # Machine event attribute setter register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{attribute}=")) namespace.attributes[:instance][attribute][:write] = m m.docstring = [ "Sets the current value for the machine", "@param new_#{attribute} The new value to set" ] m.parameters = ["new_#{attribute}"] end # Presence query register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{machine.name}?")) m.docstring = [ "Checks the given state name against the current state.", "@param [#{state_type}] state_name The name of the state to check", "@return [Boolean] True if they are the same state, otherwise false", "@raise [IndexError] If the state name is invalid" ] m.parameters = ["state_name"] # Internal state name register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:name))) m.docstring = [ "Gets the internal name of the state for the current value.", "@return [#{state_type}] The internal name of the state" ] # Human state name register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:name)}")) m.docstring = [ "Gets the human-readable name of the state for the current value.", "@return [String] The human-readable state name" ] # Available events register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:events))) m.docstring = [ "Gets the list of events that can be fired on the current #{machine.name} (uses the *unqualified* event names)", "@param [Hash] requirements The transition requirements to test against", "@option requirements [#{state_type}] :from (the current state) One or more initial states", "@option requirements [#{state_type}] :to One or more target states", "@option requirements [#{event_type}] :on One or more events that fire the transition", "@option requirements [Boolean] :guard Whether to guard transitions with conditionals", "@return [Array<#{event_type}>] The list of event names" ] m.parameters = [["requirements", "{}"]] # Available transitions register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:transitions))) m.docstring = [ "Gets the list of transitions that can be made for the current #{machine.name}", "@param [Hash] requirements The transition requirements to test against", "@option requirements [#{state_type}] :from (the current state) One or more initial states", "@option requirements [#{state_type}] :to One or more target states", "@option requirements [#{event_type}] :on One or more events that fire the transition", "@option requirements [Boolean] :guard Whether to guard transitions with conditionals", "@return [Array] The available transitions" ] m.parameters = [["requirements", "{}"]] # Available transition paths register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:paths))) m.docstring = [ "Gets the list of sequences of transitions that can be run for the current #{machine.name}", "@param [Hash] requirements The transition requirements to test against", "@option requirements [#{state_type}] :from (the current state) The initial state", "@option requirements [#{state_type}] :to The target state", "@option requirements [Boolean] :deep Whether to enable deep searches for the target state", "@option requirements [Boolean] :guard Whether to guard transitions with conditionals", "@return [StateMachine::PathCollection] The collection of paths" ] m.parameters = [["requirements", "{}"]] # Generic event fire register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "fire_#{machine.attribute(:event)}")) m.docstring = [ "Fires an arbitrary #{machine.name} event with the given argument list", "@param [#{event_type}] event The name of the event to fire", "@param args Optional arguments to include in the transition", "@return [Boolean] +true+ if the event succeeds, otherwise +false+" ] m.parameters = ["event", "*args"] end # Defines auto-generated event methods for the given machine def define_event_methods machine.events.each do |event| next if inherited_machine && inherited_machine.events[event.name] # Event query register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "can_#{event.qualified_name}?")) m.docstring = [ "Checks whether #{event.name.inspect} can be fired.", "@param [Hash] requirements The transition requirements to test against", "@option requirements [#{state_type}] :from (the current state) One or more initial states", "@option requirements [#{state_type}] :to One or more target states", "@option requirements [Boolean] :guard Whether to guard transitions with conditionals", "@return [Boolean] +true+ if #{event.name.inspect} can be fired, otherwise +false+" ] m.parameters = [["requirements", "{}"]] # Event transition register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{event.qualified_name}_transition")) m.docstring = [ "Gets the next transition that would be performed if #{event.name.inspect} were to be fired.", "@param [Hash] requirements The transition requirements to test against", "@option requirements [#{state_type}] :from (the current state) One or more initial states", "@option requirements [#{state_type}] :to One or more target states", "@option requirements [Boolean] :guard Whether to guard transitions with conditionals", "@return [StateMachine::Transition] The transition that would be performed or +nil+" ] m.parameters = [["requirements", "{}"]] # Fire event register(m = ::YARD::CodeObjects::MethodObject.new(namespace, event.qualified_name)) m.docstring = [ "Fires the #{event.name.inspect} event.", "@param [Array] args Optional arguments to include in transition callbacks", "@return [Boolean] +true+ if the transition succeeds, otherwise +false+" ] m.parameters = ["*args"] # Fire event (raises exception) register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{event.qualified_name}!")) m.docstring = [ "Fires the #{event.name.inspect} event, raising an exception if it fails.", "@param [Array] args Optional arguments to include in transition callbacks", "@return [Boolean] +true+ if the transition succeeds", "@raise [StateMachine::InvalidTransition] If the transition fails" ] m.parameters = ["*args"] end end # Defines auto-generated state methods for the given machine def define_state_methods machine.states.each do |state| next if inherited_machine && inherited_machine.states[state.name] || !state.name # State query register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{state.qualified_name}?")) m.docstring = [ "Checks whether #{state.name.inspect} is the current state.", "@return [Boolean] +true+ if this is the current state, otherwise +false+" ] end end end end end end state-machine-1.2.0/lib/state_machine/yard/handlers/event.rb0000644000175000017500000000117712305405267023403 0ustar boutilboutilmodule StateMachine module YARD module Handlers # Handles and processes #event class Event < Base handles method_call(:event) def process if owner.is_a?(StateMachine::Machine) handler = self statement = self.statement names = extract_node_names(statement.parameters(false)) names.each do |name| owner.event(name) do # Parse the block handler.parse_block(statement.last.last, :owner => self) end end end end end end end end state-machine-1.2.0/lib/state_machine/yard/handlers/transition.rb0000644000175000017500000000261012305405267024445 0ustar boutilboutilmodule StateMachine module YARD module Handlers # Handles and processes #transition class Transition < Base handles method_call(:transition) def process if [StateMachine::Machine, StateMachine::Event, StateMachine::State].include?(owner.class) options = {} # Extract requirements ast = statement.parameters.first ast.children.each do |assoc| # Skip conditionals next if %w(if unless).include?(assoc[0].jump(:ident).source) options[extract_requirement(assoc[0])] = extract_requirement(assoc[1]) end owner.transition(options) end end private # Extracts the statement requirement from the given node def extract_requirement(ast) case ast.type when :symbol_literal, :string_literal, :array extract_node_names(ast, false) when :binary AllMatcher.instance - extract_node_names(ast.children.last) when :var_ref, :vcall case ast.source when 'nil' nil when 'same' LoopbackMatcher.instance else AllMatcher.instance end end end end end end end state-machine-1.2.0/lib/state_machine/yard/handlers/base.rb0000644000175000017500000000201012305405267023157 0ustar boutilboutilmodule StateMachine module YARD module Handlers # Handles and processes nodes class Base < ::YARD::Handlers::Ruby::Base private # Extracts the value from the node as either a string or symbol def extract_node_name(ast) case ast.type when :symbol_literal ast.jump(:ident).source.to_sym when :string_literal ast.jump(:tstring_content).source else nil end end # Extracts the values from the node as either strings or symbols. # If the node isn't an array, it'll be converted to an array. def extract_node_names(ast, convert_to_array = true) if [nil, :array].include?(ast.type) ast.children.map {|child| extract_node_name(child)} else node_name = extract_node_name(ast) convert_to_array ? [node_name] : node_name end end end end end end state-machine-1.2.0/lib/state_machine/yard/handlers/state.rb0000644000175000017500000000117712305405267023402 0ustar boutilboutilmodule StateMachine module YARD module Handlers # Handles and processes #state class State < Base handles method_call(:state) def process if owner.is_a?(StateMachine::Machine) handler = self statement = self.statement names = extract_node_names(statement.parameters(false)) names.each do |name| owner.state(name) do # Parse the block handler.parse_block(statement.last.last, :owner => self) end end end end end end end end state-machine-1.2.0/lib/state_machine/yard/handlers.rb0000644000175000017500000000047112305405267022256 0ustar boutilboutilmodule StateMachine module YARD # YARD custom handlers for integrating the state_machine DSL with the # YARD documentation system module Handlers end end end Dir["#{File.dirname(__FILE__)}/handlers/*.rb"].sort.each do |path| require "state_machine/yard/handlers/#{File.basename(path)}" end state-machine-1.2.0/.gitignore0000644000175000017500000000010712305405267015604 0ustar boutilboutil*.gem .bundle/ .rbx/ .yardoc/ coverage/ /doc/ test/*.log /Gemfile.lock state-machine-1.2.0/Appraisals0000644000175000017500000003122012305405267015636 0ustar boutilboutilappraise "default" do end # GraphViz appraise "graphviz-0.9.17" do gem "ruby-graphviz", "0.9.17" end appraise "graphviz-0.9.21" do gem "ruby-graphviz", "0.9.21" end appraise "graphviz-1.0.0" do gem "ruby-graphviz", "1.0.0" end appraise "graphviz-1.0.3" do gem "ruby-graphviz", "1.0.3" end appraise "graphviz-1.0.8" do gem "ruby-graphviz", "1.0.8" end # ActiveRecord integrations if RUBY_VERSION < "1.9.1" appraise "active_record-2.0.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "activerecord", "2.0.0" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform => :jruby end appraise "active_record-2.0.5" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "activerecord", "2.0.5" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform => :jruby end appraise "active_record-2.1.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "activerecord", "2.1.0" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform => :jruby end appraise "active_record-2.1.2" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "activerecord", "2.1.2" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform => :jruby end appraise "active_record-2.2.3" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "activerecord", "2.2.3" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform => :jruby end end if RUBY_VERSION < "1.9.2" appraise "active_record-2.3.5" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "activerecord", "2.3.5" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform => :jruby end end if RUBY_VERSION < "2.0.0" appraise "active_record-2.3.12" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "activerecord", "2.3.12" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform => :jruby end end if RUBY_VERSION > "1.8.6" && RUBY_PLATFORM != "java" appraise "active_record-3.0.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "activerecord", "3.0.0" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform => :jruby end end if RUBY_VERSION > "1.8.6" appraise "active_record-3.0.5" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "activerecord", "3.0.5" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform => :jruby end end if RUBY_VERSION > "1.8.6" && RUBY_VERSION != "1.9.1" && RUBY_VERSION < "2.0.0" appraise "active_record-3.1.1" do gem "sqlite3", "1.3.6", :platform => [:ruby, :mswin, :mingw] gem "activerecord", "3.1.1" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform => :jruby end appraise "active_record-3.2.12" do gem "sqlite3", "1.3.6", :platform => [:ruby, :mswin, :mingw] gem "activerecord", "3.2.12" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform => :jruby end end if RUBY_VERSION > "1.8.6" && RUBY_VERSION != "1.9.1" appraise "active_record-3.2.13.rc1" do gem "sqlite3", "1.3.6", :platform => [:ruby, :mswin, :mingw] gem "activerecord", "3.2.13.rc1" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform => :jruby end end if RUBY_VERSION > "1.9.2" appraise "active_record-4.0.0" do gem "sqlite3", "1.3.6" gem "activerecord", "4.0.0.beta1", :git => "git://github.com/rails/rails.git", :ref => "92d6dac" gem "activerecord-deprecated_finders", "0.0.3" gem "protected_attributes", "1.0.0" gem "rails-observers", "0.1.1" end end # ActiveModel integrations if RUBY_VERSION > "1.8.6" && RUBY_VERSION < "2.0.0" appraise "active_model-3.0.0" do gem "activemodel", "3.0.0" end appraise "active_model-3.0.5" do gem "activemodel", "3.0.5" end appraise "active_model-3.1.1" do gem "activemodel", "3.1.1" end appraise "active_model-3.2.12" do gem "activemodel", "3.2.12" end end if RUBY_VERSION > "1.8.6" appraise "active_model-3.2.13.rc1" do gem "activemodel", "3.2.13.rc1" end end if RUBY_VERSION > "1.9.2" appraise "active_model-4.0.0" do gem "activemodel", "4.0.0.beta", :git => "git://github.com/rails/rails.git", :ref => "4e286bf" gem "rails-observers", "0.1.1" gem "protected_attributes", "1.0.0" end end # MongoMapper integrations if RUBY_VERSION > "1.8.6" && RUBY_VERSION < "1.9.1" appraise "mongo_mapper-0.5.5" do gem "activesupport", "2.3.11" gem "mongo_mapper", "0.5.5" end end if RUBY_VERSION > "1.8.6" && RUBY_VERSION < "2.0.0" appraise "mongo_mapper-0.5.8" do gem "activesupport", "2.3.11" gem "mongo_mapper", "0.5.8" end appraise "mongo_mapper-0.6.0" do gem "activesupport", "2.3.11" gem "mongo_mapper", "0.6.0" end appraise "mongo_mapper-0.6.10" do gem "activesupport", "2.3.11" gem "mongo_mapper", "0.6.10" end appraise "mongo_mapper-0.7.0" do gem "activesupport", "2.3.11" gem "mongo_mapper", "0.7.0" end end if RUBY_VERSION > "1.8.6" && RUBY_VERSION < "2.0.0" && (!defined?(RUBY_ENGINE) || RUBY_ENGINE != 'rbx') appraise "mongo_mapper-0.7.5" do gem "activesupport", "2.3.11" gem "mongo_mapper", "0.7.5" end appraise "mongo_mapper-0.8.0" do gem "activesupport", "2.3.11" gem "mongo", "1.0.1" gem "plucky", "0.3.0" gem "mongo_mapper", "0.8.0" end appraise "mongo_mapper-0.8.3" do gem "activesupport", "2.3.11" gem "mongo", "1.0.1" gem "plucky", "0.3.3" gem "mongo_mapper", "0.8.3" end end if RUBY_VERSION > "1.8.6" && RUBY_VERSION < "2.0.0" appraise "mongo_mapper-0.8.4" do gem "activesupport", "2.3.11" gem "mongo_mapper", "0.8.4" end appraise "mongo_mapper-0.8.6" do gem "activesupport", "2.3.11" gem "mongo_mapper", "0.8.6" end end # MongoMapper 0.9.0+ breaks on Ruby 1.9.1 if RUBY_VERSION > "1.8.6" && RUBY_VERSION != "1.9.1" && RUBY_VERSION < "2.0.0" appraise "mongo_mapper-0.9.0" do gem "mongo_mapper", "0.9.0" end end if RUBY_VERSION > "1.8.6" && RUBY_VERSION != "1.9.1" appraise "mongo_mapper-0.10.0" do gem "activemodel", "3.2.13.rc1" gem "mongo_mapper", "0.10.0" end appraise "mongo_mapper-0.11.2" do gem "activemodel", "3.2.13.rc1" gem "mongo", "~> 1.7.0" gem "mongo_mapper", "0.11.2" end appraise "mongo_mapper-0.12.0" do gem "activemodel", "3.2.13.rc1" gem "mongo", "~> 1.7.0" gem "mongo_mapper", "0.12.0" end end # Mongoid integrations if RUBY_VERSION > "1.8.6" && RUBY_VERSION < "2.0.0" appraise "mongoid-2.0.0" do gem "activemodel", "~> 3.1.0" gem "mongo", "~> 1.7.0" gem "mongoid", "2.0.0" end appraise "mongoid-2.1.4" do gem "activemodel", "~> 3.1.0" gem "mongo", "~> 1.7.0" gem "mongoid", "2.1.4" end appraise "mongoid-2.2.4" do gem "activemodel", "~> 3.1.0" gem "mongo", "~> 1.7.0" gem "mongoid", "2.2.4" end appraise "mongoid-2.3.3" do gem "activemodel", "~> 3.1.0" gem "mongo", "~> 1.7.0" gem "mongoid", "2.3.3" end end if RUBY_VERSION > "1.8.6" appraise "mongoid-2.4.0" do gem "activemodel", "3.2.13.rc1" gem "mongo", "~> 1.7.0" gem "mongoid", "2.4.0" end appraise "mongoid-2.4.10" do gem "activemodel", "3.2.13.rc1" gem "mongo", "~> 1.7.0" gem "mongoid", "2.4.10" end appraise "mongoid-2.5.2" do gem "activemodel", "3.2.13.rc1" gem "mongo", "~> 1.7.0" gem "mongoid", "2.5.2" end appraise "mongoid-2.6.0" do gem "activemodel", "3.2.13.rc1" gem "mongo", "~> 1.7.0" gem "mongoid", "2.6.0" end end if RUBY_VERSION > "1.9.2" appraise "mongoid-3.0.0" do gem "activemodel", "3.2.13.rc1" gem "mongoid", "3.0.0" end appraise "mongoid-3.0.22" do gem "activemodel", "3.2.13.rc1" gem "mongoid", "3.0.22" end appraise "mongoid-3.1.0" do gem "activemodel", "3.2.13.rc1" gem "mongoid", "3.1.0" end end # Sequel integrations if RUBY_VERSION < "1.9.2" appraise "sequel-2.8.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform => :jruby gem "sequel", "2.8.0" end appraise "sequel-2.11.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform => :jruby gem "sequel", "2.11.0" end appraise "sequel-2.12.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform => :jruby gem "sequel", "2.12.0" end appraise "sequel-3.0.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform => :jruby gem "sequel", "3.0.0" end appraise "sequel-3.4.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform => :jruby gem "sequel", "3.4.0" end end appraise "sequel-3.10.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform => :jruby gem "sequel", "3.10.0" end appraise "sequel-3.13.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform => :jruby gem "sequel", "3.13.0" end appraise "sequel-3.14.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform => :jruby gem "sequel", "3.14.0" end appraise "sequel-3.23.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform => :jruby gem "sequel", "3.23.0" end appraise "sequel-3.24.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform => :jruby gem "sequel", "3.24.0" end appraise "sequel-3.29.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform => :jruby gem "sequel", "3.29.0" end appraise "sequel-3.34.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform => :jruby gem "sequel", "3.34.0" end if RUBY_VERSION > "1.8.6" appraise "sequel-3.35.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform => :jruby gem "sequel", "3.35.0" end appraise "sequel-3.44.0" do gem "sqlite3-ruby", "1.3.1", :platform => [:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform => :jruby gem "sequel", "3.44.0" end end # DataMapper if RUBY_VERSION < "1.9.1" && RUBY_PLATFORM != 'java' && (!defined?(RUBY_ENGINE) || RUBY_ENGINE != 'rbx') appraise "data_mapper-0.9.4" do gem "dm-core", "0.9.4" gem "dm-migrations", "0.9.4" gem "dm-validations", "0.9.4" gem "dm-observer", "0.9.4" gem "data_objects", "0.9.4" gem "do_sqlite3", "0.9.4" end appraise "data_mapper-0.9.7" do gem "extlib", "0.9.8" gem "dm-core", "0.9.7" gem "dm-migrations", "0.9.7" gem "dm-validations", "0.9.7" gem "dm-observer", "0.9.7" gem "data_objects", "0.9.7" gem "do_sqlite3", "0.9.7" end appraise "data_mapper-0.9.11" do gem "extlib", "0.9.11" gem "dm-core", "0.9.11" gem "dm-migrations", "0.9.11" gem "dm-validations", "0.9.11" gem "dm-observer", "0.9.11" gem "data_objects", "0.9.11" gem "do_sqlite3", "0.9.11" end appraise "data_mapper-0.10.2" do gem "extlib", "0.9.16" gem "dm-core", "0.10.2" gem "dm-migrations", "0.10.2" gem "dm-validations", "0.10.2" gem "dm-observer", "0.10.2" gem "data_objects", "0.10.2" gem "do_sqlite3", "0.10.2" end end appraise "data_mapper-1.0.0" do gem "dm-core", "1.0.0" gem "dm-migrations", "1.0.0" gem "dm-validations", "1.0.0" gem "dm-observer", "1.0.0" gem "dm-transactions", "1.0.0" gem "dm-sqlite-adapter", "1.0.0" end appraise "data_mapper-1.0.1" do gem "dm-core", "1.0.1" gem "dm-migrations", "1.0.1" gem "dm-validations", "1.0.1" gem "dm-observer", "1.0.1" gem "dm-transactions", "1.0.1" gem "dm-sqlite-adapter", "1.0.1" end appraise "data_mapper-1.0.2" do gem "dm-core", "1.0.2" gem "dm-migrations", "1.0.2" gem "dm-validations", "1.0.2" gem "dm-observer", "1.0.2" gem "dm-transactions", "1.0.2" gem "dm-sqlite-adapter", "1.0.2" end if RUBY_VERSION > "1.8.6" appraise "data_mapper-1.1.0" do gem "dm-core", "1.1.0" gem "dm-migrations", "1.1.0" gem "dm-validations", "1.1.0" gem "dm-observer", "1.1.0" gem "dm-transactions", "1.1.0" gem "dm-sqlite-adapter", "1.1.0" end appraise "data_mapper-1.2.0" do gem "dm-core", "1.2.0" gem "dm-migrations", "1.2.0" gem "dm-validations", "1.2.0" gem "dm-observer", "1.2.0" gem "dm-transactions", "1.2.0" gem "dm-sqlite-adapter", "1.2.0" end end state-machine-1.2.0/gemfiles/0000755000175000017500000000000012305405267015411 5ustar boutilboutilstate-machine-1.2.0/gemfiles/sequel-3.29.0.gemfile.lock0000644000175000017500000000111412305405267021714 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2) multi_json (1.6.1) rake (10.0.3) sequel (3.29.0) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) jdbc-sqlite3 (= 3.7.2) rake sequel (= 3.29.0) simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/active_record-2.3.5.gemfile0000644000175000017500000000037612305405267022225 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "activerecord", "2.3.5" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform=>:jruby gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-3.13.0.gemfile.lock0000644000175000017500000000111412305405267021705 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2) multi_json (1.6.1) rake (10.0.3) sequel (3.13.0) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) jdbc-sqlite3 (= 3.7.2) rake sequel (= 3.13.0) simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/data_mapper-1.2.0.gemfile0000644000175000017500000000041412305405267021653 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "dm-core", "1.2.0" gem "dm-migrations", "1.2.0" gem "dm-validations", "1.2.0" gem "dm-observer", "1.2.0" gem "dm-transactions", "1.2.0" gem "dm-sqlite-adapter", "1.2.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-2.12.0.gemfile0000644000175000017500000000034512305405267020761 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform=>:jruby gem "sequel", "2.12.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/data_mapper-0.9.7.gemfile0000644000175000017500000000043012305405267021666 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "extlib", "0.9.8" gem "dm-core", "0.9.7" gem "dm-migrations", "0.9.7" gem "dm-validations", "0.9.7" gem "dm-observer", "0.9.7" gem "data_objects", "0.9.7" gem "do_sqlite3", "0.9.7" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-3.2.13.rc1.gemfile0000644000175000017500000000037612305405267022770 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3", "1.3.6", :platform=>[:ruby, :mswin, :mingw] gem "activerecord", "3.2.13.rc1" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform=>:jruby gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-2.3.12.gemfile0000644000175000017500000000037712305405267022304 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "activerecord", "2.3.12" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform=>:jruby gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-2.8.0.gemfile.lock0000644000175000017500000000111212305405267021626 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2) multi_json (1.6.1) rake (10.0.3) sequel (2.8.0) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) jdbc-sqlite3 (= 3.7.2) rake sequel (= 2.8.0) simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/active_record-3.0.5.gemfile0000644000175000017500000000037612305405267022223 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "activerecord", "3.0.5" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform=>:jruby gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongoid-2.4.0.gemfile0000644000175000017500000000025612305405267021041 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.2.13.rc1" gem "mongo", "~> 1.7.0" gem "mongoid", "2.4.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/graphviz-0.9.21.gemfile0000644000175000017500000000017512305405267021325 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "ruby-graphviz", "0.9.21" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-2.2.3.gemfile0000644000175000017500000000037612305405267022222 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "activerecord", "2.2.3" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform=>:jruby gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-3.24.0.gemfile0000644000175000017500000000034512305405267020765 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform=>:jruby gem "sequel", "3.24.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-3.1.1.gemfile0000644000175000017500000000037112305405267022213 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3", "1.3.6", :platform=>[:ruby, :mswin, :mingw] gem "activerecord", "3.1.1" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform=>:jruby gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongoid-3.0.22.gemfile0000644000175000017500000000022712305405267021120 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.2.13.rc1" gem "mongoid", "3.0.22" gemspec :path=>"../"state-machine-1.2.0/gemfiles/graphviz-1.0.8.gemfile.lock0000644000175000017500000000076412305405267022175 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake multi_json (1.6.1) rake (10.0.3) ruby-graphviz (1.0.8) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) rake ruby-graphviz (= 1.0.8) simplecov state_machine! state-machine-1.2.0/gemfiles/mongoid-2.3.3.gemfile0000644000175000017500000000025412305405267021041 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "~> 3.1.0" gem "mongo", "~> 1.7.0" gem "mongoid", "2.3.3" gemspec :path=>"../"state-machine-1.2.0/gemfiles/data_mapper-1.0.0.gemfile0000644000175000017500000000041412305405267021651 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "dm-core", "1.0.0" gem "dm-migrations", "1.0.0" gem "dm-validations", "1.0.0" gem "dm-observer", "1.0.0" gem "dm-transactions", "1.0.0" gem "dm-sqlite-adapter", "1.0.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-4.0.0.gemfile.lock0000644000175000017500000000363412305405267023146 0ustar boutilboutilGIT remote: git://github.com/rails/rails.git revision: 92d6dacc348a4e42d314c813a650ac578976d4c4 ref: 92d6dac specs: actionpack (4.0.0.beta1) activesupport (= 4.0.0.beta1) builder (~> 3.1.0) erubis (~> 2.7.0) rack (~> 1.5.2) rack-test (~> 0.6.2) activemodel (4.0.0.beta1) activesupport (= 4.0.0.beta1) builder (~> 3.1.0) activerecord (4.0.0.beta1) activemodel (= 4.0.0.beta1) activerecord-deprecated_finders (~> 0.0.3) activesupport (= 4.0.0.beta1) arel (~> 4.0.0.beta2) activesupport (4.0.0.beta1) i18n (~> 0.6, >= 0.6.4) minitest (~> 4.2) multi_json (~> 1.3) thread_safe (~> 0.1) tzinfo (~> 0.3.37) railties (4.0.0.beta1) actionpack (= 4.0.0.beta1) activesupport (= 4.0.0.beta1) rake (>= 0.8.7) thor (>= 0.17.0, < 2.0) PATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activerecord-deprecated_finders (0.0.3) appraisal (0.4.1) bundler rake arel (4.0.0.beta2) atomic (1.0.2) atomic (1.0.2-java) builder (3.1.4) erubis (2.7.0) i18n (0.6.4) minitest (4.7.0) multi_json (1.6.1) protected_attributes (1.0.0) activemodel (>= 4.0.0.beta, < 5.0) rack (1.5.2) rack-test (0.6.2) rack (>= 1.0) rails-observers (0.1.1) railties (~> 4.0.0.beta) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3 (1.3.6) thor (0.18.0) thread_safe (0.1.0) atomic tzinfo (0.3.37) PLATFORMS java ruby DEPENDENCIES activerecord (= 4.0.0.beta1)! activerecord-deprecated_finders (= 0.0.3) appraisal (~> 0.4.0) protected_attributes (= 1.0.0) rails-observers (= 0.1.1) rake simplecov sqlite3 (= 1.3.6) state_machine! state-machine-1.2.0/gemfiles/data_mapper-0.9.7.gemfile.lock0000644000175000017500000000264212305405267022624 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: addressable (2.3.2) appraisal (0.4.1) bundler rake data_objects (0.9.7) addressable (~> 2.0) extlib (~> 0.9.8) hoe (>= 1.8.2) diff-lcs (1.1.3) dm-core (0.9.7) addressable (~> 2.0) data_objects (~> 0.9.7) extlib (~> 0.9.8) rspec (>= 1.1.3) dm-migrations (0.9.7) dm-core (~> 0.9.7) hoe (>= 1.8.2) dm-observer (0.9.7) dm-core (= 0.9.7) hoe (>= 1.8.2) dm-validations (0.9.7) dm-core (= 0.9.7) hoe (>= 1.8.2) do_sqlite3 (0.9.7) data_objects (= 0.9.7) hoe (>= 1.8.2) extlib (0.9.8) hoe (3.5.0) rake (>= 0.8, < 11.0) multi_json (1.6.1) rake (10.0.3) rspec (2.12.0) rspec-core (~> 2.12.0) rspec-expectations (~> 2.12.0) rspec-mocks (~> 2.12.0) rspec-core (2.12.2) rspec-expectations (2.12.1) diff-lcs (~> 1.1.3) rspec-mocks (2.12.2) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS ruby DEPENDENCIES appraisal (~> 0.4.0) data_objects (= 0.9.7) dm-core (= 0.9.7) dm-migrations (= 0.9.7) dm-observer (= 0.9.7) dm-validations (= 0.9.7) do_sqlite3 (= 0.9.7) extlib (= 0.9.8) rake simplecov state_machine! state-machine-1.2.0/gemfiles/data_mapper-1.0.0.gemfile.lock0000644000175000017500000000260012305405267022577 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: addressable (2.3.2) appraisal (0.4.1) bundler rake data_objects (0.10.12) addressable (~> 2.1) dm-core (1.0.0) addressable (~> 2.1) extlib (~> 0.9.15) dm-do-adapter (1.0.0) data_objects (~> 0.10.1) dm-core (~> 1.0.0) dm-migrations (1.0.0) dm-core (~> 1.0.0) dm-observer (1.0.0) dm-core (~> 1.0.0) dm-sqlite-adapter (1.0.0) dm-do-adapter (~> 1.0.0) do_sqlite3 (~> 0.10.2) dm-transactions (1.0.0) dm-core (~> 1.0.0) dm-validations (1.0.0) dm-core (~> 1.0.0) do_jdbc (0.10.12-java) data_objects (= 0.10.12) do_sqlite3 (0.10.12) data_objects (= 0.10.12) do_sqlite3 (0.10.12-java) data_objects (= 0.10.12) do_jdbc (= 0.10.12) jdbc-sqlite3 (>= 3.5.8) extlib (0.9.16) jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) dm-core (= 1.0.0) dm-migrations (= 1.0.0) dm-observer (= 1.0.0) dm-sqlite-adapter (= 1.0.0) dm-transactions (= 1.0.0) dm-validations (= 1.0.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.5.8.gemfile0000644000175000017500000000023112305405267022070 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activesupport", "2.3.11" gem "mongo_mapper", "0.5.8" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongo_mapper-0.8.3.gemfile.lock0000644000175000017500000000152312305405267023022 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activesupport (2.3.11) appraisal (0.4.0) bundler rake bson (1.0.1) jnunemaker-validatable (1.8.4) activesupport (>= 2.3.4) mongo (1.0.1) bson (= 1.0.1) mongo_mapper (0.8.3) activesupport (>= 2.3.4) jnunemaker-validatable (~> 1.8.4) plucky (~> 0.3.3) multi_json (1.0.4) plucky (0.3.3) mongo (~> 1.0.1) rake (0.9.2.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES activesupport (= 2.3.11) appraisal (~> 0.4.0) mongo (= 1.0.1) mongo_mapper (= 0.8.3) plucky (= 0.3.3) rake simplecov state_machine! state-machine-1.2.0/gemfiles/sequel-3.24.0.gemfile.lock0000644000175000017500000000111412305405267021707 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2) multi_json (1.6.1) rake (10.0.3) sequel (3.24.0) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) jdbc-sqlite3 (= 3.7.2) rake sequel (= 3.24.0) simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/mongoid-2.4.10.gemfile0000644000175000017500000000025712305405267021123 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.2.13.rc1" gem "mongo", "~> 1.7.0" gem "mongoid", "2.4.10" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_model-3.0.0.gemfile0000644000175000017500000000017212305405267022032 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.0.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongoid-3.0.0.gemfile.lock0000644000175000017500000000155612305405267021771 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.13.rc1) activesupport (= 3.2.13.rc1) builder (~> 3.0.0) activesupport (3.2.13.rc1) i18n (= 0.6.1) multi_json (~> 1.0) appraisal (0.4.1) bundler rake builder (3.0.4) i18n (0.6.1) mongoid (3.0.0) activemodel (~> 3.1) moped (~> 1.1.1) origin (~> 1.0.3) tzinfo (~> 0.3.22) moped (1.1.6) multi_json (1.6.1) origin (1.0.11) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.2.13.rc1) appraisal (~> 0.4.0) mongoid (= 3.0.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/sequel-3.34.0.gemfile0000644000175000017500000000034512305405267020766 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform=>:jruby gem "sequel", "3.34.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/data_mapper-0.9.4.gemfile0000644000175000017500000000040212305405267021662 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "dm-core", "0.9.4" gem "dm-migrations", "0.9.4" gem "dm-validations", "0.9.4" gem "dm-observer", "0.9.4" gem "data_objects", "0.9.4" gem "do_sqlite3", "0.9.4" gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-3.0.0.gemfile0000644000175000017500000000034412305405267020676 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform=>:jruby gem "sequel", "3.0.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongo_mapper-0.6.0.gemfile.lock0000644000175000017500000000127012305405267023014 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activesupport (2.3.11) appraisal (0.4.0) bundler rake jnunemaker-validatable (1.8.1) mongo (0.16) mongo_mapper (0.6.0) activesupport (>= 2.3) jnunemaker-validatable (= 1.8.1) mongo (= 0.16) multi_json (1.0.4) rake (0.9.2.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES activesupport (= 2.3.11) appraisal (~> 0.4.0) mongo_mapper (= 0.6.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.8.0.gemfile.lock0000644000175000017500000000152312305405267023017 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activesupport (2.3.11) appraisal (0.4.0) bundler rake bson (1.0.1) jnunemaker-validatable (1.8.4) activesupport (>= 2.3.4) mongo (1.0.1) bson (= 1.0.1) mongo_mapper (0.8.0) activesupport (>= 2.3.4) jnunemaker-validatable (~> 1.8.4) plucky (~> 0.3.0) multi_json (1.0.4) plucky (0.3.0) mongo (~> 1.0.1) rake (0.9.2.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES activesupport (= 2.3.11) appraisal (~> 0.4.0) mongo (= 1.0.1) mongo_mapper (= 0.8.0) plucky (= 0.3.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/data_mapper-1.0.1.gemfile.lock0000644000175000017500000000260012305405267022600 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: addressable (2.3.2) appraisal (0.4.1) bundler rake data_objects (0.10.12) addressable (~> 2.1) dm-core (1.0.1) addressable (~> 2.2) extlib (~> 0.9.15) dm-do-adapter (1.0.1) data_objects (~> 0.10.2) dm-core (~> 1.0.1) dm-migrations (1.0.1) dm-core (~> 1.0.1) dm-observer (1.0.1) dm-core (~> 1.0.1) dm-sqlite-adapter (1.0.1) dm-do-adapter (~> 1.0.1) do_sqlite3 (~> 0.10.2) dm-transactions (1.0.1) dm-core (~> 1.0.1) dm-validations (1.0.1) dm-core (~> 1.0.1) do_jdbc (0.10.12-java) data_objects (= 0.10.12) do_sqlite3 (0.10.12) data_objects (= 0.10.12) do_sqlite3 (0.10.12-java) data_objects (= 0.10.12) do_jdbc (= 0.10.12) jdbc-sqlite3 (>= 3.5.8) extlib (0.9.16) jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) dm-core (= 1.0.1) dm-migrations (= 1.0.1) dm-observer (= 1.0.1) dm-sqlite-adapter (= 1.0.1) dm-transactions (= 1.0.1) dm-validations (= 1.0.1) rake simplecov state_machine! state-machine-1.2.0/gemfiles/sequel-3.10.0.gemfile0000644000175000017500000000034512305405267020760 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform=>:jruby gem "sequel", "3.10.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-3.14.0.gemfile.lock0000644000175000017500000000111412305405267021706 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2) multi_json (1.6.1) rake (10.0.3) sequel (3.14.0) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) jdbc-sqlite3 (= 3.7.2) rake sequel (= 3.14.0) simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/mongoid-2.4.0.gemfile.lock0000644000175000017500000000162012305405267021764 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.13.rc1) activesupport (= 3.2.13.rc1) builder (~> 3.0.0) activesupport (3.2.13.rc1) i18n (= 0.6.1) multi_json (~> 1.0) appraisal (0.4.1) bundler rake bson (1.7.1) bson (1.7.1-java) builder (3.0.4) i18n (0.6.1) mongo (1.7.1) bson (~> 1.7.1) mongoid (2.4.0) activemodel (~> 3.1) mongo (~> 1.3) tzinfo (~> 0.3.22) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.2.13.rc1) appraisal (~> 0.4.0) mongo (~> 1.7.0) mongoid (= 2.4.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.10.0.gemfile0000644000175000017500000000023412305405267022137 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.2.13.rc1" gem "mongo_mapper", "0.10.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_model-3.2.13.rc1.gemfile0000644000175000017500000000017712305405267022611 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.2.13.rc1" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_model-4.0.0.gemfile.lock0000644000175000017500000000324012305405267022761 0ustar boutilboutilGIT remote: git://github.com/rails/rails.git revision: 4e286bf1c62e24017867fdd04bda1129a645bec6 ref: 4e286bf specs: actionpack (4.0.0.beta) activesupport (= 4.0.0.beta) builder (~> 3.1.0) erubis (~> 2.7.0) rack (~> 1.5.2) rack-test (~> 0.6.2) activemodel (4.0.0.beta) activesupport (= 4.0.0.beta) builder (~> 3.1.0) activesupport (4.0.0.beta) i18n (~> 0.6) minitest (~> 4.2) multi_json (~> 1.3) thread_safe (~> 0.1) tzinfo (~> 0.3.33) railties (4.0.0.beta) actionpack (= 4.0.0.beta) activesupport (= 4.0.0.beta) rake (>= 0.8.7) rdoc (~> 3.4) thor (>= 0.17.0, < 2.0) PATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake atomic (1.0.1) atomic (1.0.1-java) builder (3.1.4) erubis (2.7.0) i18n (0.6.1) json (1.7.7) json (1.7.7-java) minitest (4.6.1) multi_json (1.6.1) protected_attributes (1.0.0) activemodel (>= 4.0.0.beta, < 5.0) rack (1.5.2) rack-test (0.6.2) rack (>= 1.0) rails-observers (0.1.1) railties (~> 4.0.0.beta) rake (10.0.3) rdoc (3.12.2) json (~> 1.4) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) thor (0.17.0) thread_safe (0.1.0) atomic tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activemodel (= 4.0.0.beta)! appraisal (~> 0.4.0) protected_attributes (= 1.0.0) rails-observers (= 0.1.1) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongoid-2.6.0.gemfile.lock0000644000175000017500000000162012305405267021766 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.13.rc1) activesupport (= 3.2.13.rc1) builder (~> 3.0.0) activesupport (3.2.13.rc1) i18n (= 0.6.1) multi_json (~> 1.0) appraisal (0.4.1) bundler rake bson (1.7.1) bson (1.7.1-java) builder (3.0.4) i18n (0.6.1) mongo (1.7.1) bson (~> 1.7.1) mongoid (2.6.0) activemodel (~> 3.1) mongo (~> 1.7) tzinfo (~> 0.3.22) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.2.13.rc1) appraisal (~> 0.4.0) mongo (~> 1.7.0) mongoid (= 2.6.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongoid-2.3.3.gemfile.lock0000644000175000017500000000157712305405267022001 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.1.11) activesupport (= 3.1.11) builder (~> 3.0.0) i18n (~> 0.6) activesupport (3.1.11) multi_json (~> 1.0) appraisal (0.4.1) bundler rake bson (1.7.1) bson (1.7.1-java) builder (3.0.4) i18n (0.6.1) mongo (1.7.1) bson (~> 1.7.1) mongoid (2.3.3) activemodel (~> 3.1) mongo (~> 1.3) tzinfo (~> 0.3.22) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activemodel (~> 3.1.0) appraisal (~> 0.4.0) mongo (~> 1.7.0) mongoid (= 2.3.3) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongoid-2.0.0.gemfile.lock0000644000175000017500000000167212305405267021767 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.1.11) activesupport (= 3.1.11) builder (~> 3.0.0) i18n (~> 0.6) activesupport (3.1.11) multi_json (~> 1.0) appraisal (0.4.1) bundler rake bson (1.7.1) bson (1.7.1-java) builder (3.0.4) i18n (0.6.1) mongo (1.7.1) bson (~> 1.7.1) mongoid (2.0.0) activemodel (~> 3.0) mongo (~> 1.2) tzinfo (~> 0.3.22) will_paginate (~> 3.0.pre) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) tzinfo (0.3.35) will_paginate (3.0.4) PLATFORMS java ruby DEPENDENCIES activemodel (~> 3.1.0) appraisal (~> 0.4.0) mongo (~> 1.7.0) mongoid (= 2.0.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/active_record-2.2.3.gemfile.lock0000644000175000017500000000150012305405267023137 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activerecord (2.2.3) activesupport (= 2.2.3) activerecord-jdbc-adapter (1.2.7) activerecord-jdbcsqlite3-adapter (1.2.7) activerecord-jdbc-adapter (~> 1.2.7) jdbc-sqlite3 (~> 3.7.2) activesupport (2.2.3) appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES activerecord (= 2.2.3) activerecord-jdbcsqlite3-adapter (= 1.2.7) appraisal (~> 0.4.0) rake simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/data_mapper-0.9.4.gemfile.lock0000644000175000017500000000274012305405267022620 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: addressable (2.3.2) appraisal (0.4.1) bundler rake data_objects (0.9.4) addressable (>= 1.0.3) extlib (= 0.9.4) hoe (>= 1.7.0) diff-lcs (1.1.3) dm-core (0.9.4) addressable (>= 1.0.4) data_objects (= 0.9.4) extlib (= 0.9.4) rspec (>= 1.1.3) dm-migrations (0.9.4) dm-core (= 0.9.4) hoe (>= 1.7.0) dm-observer (0.9.4) dm-core (= 0.9.4) hoe (>= 1.7.0) dm-validations (0.9.4) dm-core (= 0.9.4) hoe (>= 1.7.0) do_sqlite3 (0.9.4) data_objects (= 0.9.4) hoe (>= 1.7.0) english (0.6.3) language extlib (0.9.4) english (>= 0.2.0) hoe (3.5.0) rake (>= 0.8, < 11.0) language (0.6.0) multi_json (1.6.1) rake (10.0.3) rspec (2.12.0) rspec-core (~> 2.12.0) rspec-expectations (~> 2.12.0) rspec-mocks (~> 2.12.0) rspec-core (2.12.2) rspec-expectations (2.12.1) diff-lcs (~> 1.1.3) rspec-mocks (2.12.2) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS ruby DEPENDENCIES appraisal (~> 0.4.0) data_objects (= 0.9.4) dm-core (= 0.9.4) dm-migrations (= 0.9.4) dm-observer (= 0.9.4) dm-validations (= 0.9.4) do_sqlite3 (= 0.9.4) rake simplecov state_machine! state-machine-1.2.0/gemfiles/active_model-4.0.0.gemfile0000644000175000017500000000037512305405267022040 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "4.0.0.beta", :git=>"git://github.com/rails/rails.git", :ref=>"4e286bf" gem "rails-observers", "0.1.1" gem "protected_attributes", "1.0.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-3.44.0.gemfile.lock0000644000175000017500000000111412305405267021711 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2) multi_json (1.6.1) rake (10.0.3) sequel (3.44.0) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) jdbc-sqlite3 (= 3.7.2) rake sequel (= 3.44.0) simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/graphviz-1.0.8.gemfile0000644000175000017500000000017412305405267021241 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "ruby-graphviz", "1.0.8" gemspec :path=>"../"state-machine-1.2.0/gemfiles/default.gemfile.lock0000644000175000017500000000070312305405267021316 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.0) bundler rake multi_json (1.0.4) rake (0.9.2.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.11.1.gemfile.lock0000644000175000017500000000153312305405267023073 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.3) activesupport (= 3.2.3) builder (~> 3.0.0) activesupport (3.2.3) i18n (~> 0.6) multi_json (~> 1.0) appraisal (0.4.1) bundler rake bson (1.6.2) builder (3.0.0) i18n (0.6.0) mongo (1.6.2) bson (~> 1.6.2) mongo_mapper (0.11.1) activemodel (~> 3.0) activesupport (~> 3.0) plucky (~> 0.4.0) multi_json (1.3.5) plucky (0.4.4) mongo (~> 1.5) rake (0.9.2.2) simplecov (0.6.4) multi_json (~> 1.0) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS ruby DEPENDENCIES appraisal (~> 0.4.0) mongo_mapper (= 0.11.1) rake simplecov state_machine! state-machine-1.2.0/gemfiles/active_model-3.0.5.gemfile.lock0000644000175000017500000000117212305405267022767 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.0.5) activesupport (= 3.0.5) builder (~> 2.1.2) i18n (~> 0.4) activesupport (3.0.5) appraisal (0.4.1) bundler rake builder (2.1.2) i18n (0.6.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.0.5) appraisal (~> 0.4.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.12.0.gemfile0000644000175000017500000000026412305405267022144 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.2.13.rc1" gem "mongo", "~> 1.7.0" gem "mongo_mapper", "0.12.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongoid-3.1.0.gemfile0000644000175000017500000000022612305405267021034 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.2.13.rc1" gem "mongoid", "3.1.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-2.3.5.gemfile.lock0000644000175000017500000000150012305405267023142 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activerecord (2.3.5) activesupport (= 2.3.5) activerecord-jdbc-adapter (1.2.8) activerecord-jdbcsqlite3-adapter (1.2.7) activerecord-jdbc-adapter (~> 1.2.7) jdbc-sqlite3 (~> 3.7.2) activesupport (2.3.5) appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES activerecord (= 2.3.5) activerecord-jdbcsqlite3-adapter (= 1.2.7) appraisal (~> 0.4.0) rake simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/active_record-3.0.5.gemfile.lock0000644000175000017500000000207112305405267023144 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.0.5) activesupport (= 3.0.5) builder (~> 2.1.2) i18n (~> 0.4) activerecord (3.0.5) activemodel (= 3.0.5) activesupport (= 3.0.5) arel (~> 2.0.2) tzinfo (~> 0.3.23) activerecord-jdbc-adapter (1.2.7) activerecord-jdbcsqlite3-adapter (1.2.7) activerecord-jdbc-adapter (~> 1.2.7) jdbc-sqlite3 (~> 3.7.2) activesupport (3.0.5) appraisal (0.4.1) bundler rake arel (2.0.10) builder (2.1.2) i18n (0.6.1) jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activerecord (= 3.0.5) activerecord-jdbcsqlite3-adapter (= 1.2.7) appraisal (~> 0.4.0) rake simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/sequel-3.44.0.gemfile0000644000175000017500000000034512305405267020767 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform=>:jruby gem "sequel", "3.44.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-2.11.0.gemfile.lock0000644000175000017500000000111412305405267021702 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2) multi_json (1.6.1) rake (10.0.3) sequel (2.11.0) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) jdbc-sqlite3 (= 3.7.2) rake sequel (= 2.11.0) simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/active_record-2.1.2.gemfile.lock0000644000175000017500000000150012305405267023135 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activerecord (2.1.2) activesupport (= 2.1.2) activerecord-jdbc-adapter (1.2.7) activerecord-jdbcsqlite3-adapter (1.2.7) activerecord-jdbc-adapter (~> 1.2.7) jdbc-sqlite3 (~> 3.7.2) activesupport (2.1.2) appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES activerecord (= 2.1.2) activerecord-jdbcsqlite3-adapter (= 1.2.7) appraisal (~> 0.4.0) rake simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/sequel-2.12.0.gemfile.lock0000644000175000017500000000111412305405267021703 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2) multi_json (1.6.1) rake (10.0.3) sequel (2.12.0) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) jdbc-sqlite3 (= 3.7.2) rake sequel (= 2.12.0) simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/mongoid-2.5.2.gemfile.lock0000644000175000017500000000162012305405267021767 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.13.rc1) activesupport (= 3.2.13.rc1) builder (~> 3.0.0) activesupport (3.2.13.rc1) i18n (= 0.6.1) multi_json (~> 1.0) appraisal (0.4.1) bundler rake bson (1.7.1) bson (1.7.1-java) builder (3.0.4) i18n (0.6.1) mongo (1.7.1) bson (~> 1.7.1) mongoid (2.5.2) activemodel (~> 3.1) mongo (~> 1.7) tzinfo (~> 0.3.22) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.2.13.rc1) appraisal (~> 0.4.0) mongo (~> 1.7.0) mongoid (= 2.5.2) rake simplecov state_machine! state-machine-1.2.0/gemfiles/default.gemfile0000644000175000017500000000014012305405267020362 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gemspec :path=>"../"state-machine-1.2.0/gemfiles/data_mapper-0.9.11.gemfile0000644000175000017500000000043712305405267021750 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "extlib", "0.9.11" gem "dm-core", "0.9.11" gem "dm-migrations", "0.9.11" gem "dm-validations", "0.9.11" gem "dm-observer", "0.9.11" gem "data_objects", "0.9.11" gem "do_sqlite3", "0.9.11" gemspec :path=>"../"state-machine-1.2.0/gemfiles/graphviz-1.0.3.gemfile0000644000175000017500000000017412305405267021234 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "ruby-graphviz", "1.0.3" gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-3.14.0.gemfile0000644000175000017500000000034512305405267020764 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform=>:jruby gem "sequel", "3.14.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-2.8.0.gemfile0000644000175000017500000000034412305405267020705 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform=>:jruby gem "sequel", "2.8.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongo_mapper-0.8.4.gemfile0000644000175000017500000000023112305405267022067 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activesupport", "2.3.11" gem "mongo_mapper", "0.8.4" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongo_mapper-0.5.5.gemfile.lock0000644000175000017500000000127412305405267023024 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activesupport (2.3.11) appraisal (0.4.0) bundler rake jnunemaker-validatable (1.7.4) mongo (0.15.1) mongo_mapper (0.5.5) activesupport (>= 2.3) jnunemaker-validatable (= 1.7.4) mongo (= 0.15.1) multi_json (1.0.4) rake (0.9.2.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES activesupport (= 2.3.11) appraisal (~> 0.4.0) mongo_mapper (= 0.5.5) rake simplecov state_machine! state-machine-1.2.0/gemfiles/sequel-3.0.0.gemfile.lock0000644000175000017500000000111212305405267021617 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2) multi_json (1.6.1) rake (10.0.3) sequel (3.0.0) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) jdbc-sqlite3 (= 3.7.2) rake sequel (= 3.0.0) simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.5.5.gemfile0000644000175000017500000000023112305405267022065 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activesupport", "2.3.11" gem "mongo_mapper", "0.5.5" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongoid-2.0.0.gemfile0000644000175000017500000000025412305405267021033 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "~> 3.1.0" gem "mongo", "~> 1.7.0" gem "mongoid", "2.0.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-3.4.0.gemfile.lock0000644000175000017500000000111212305405267021623 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2) multi_json (1.6.1) rake (10.0.3) sequel (3.4.0) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) jdbc-sqlite3 (= 3.7.2) rake sequel (= 3.4.0) simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/graphviz-0.9.17.gemfile.lock0000644000175000017500000000076612305405267022267 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake multi_json (1.6.1) rake (10.0.3) ruby-graphviz (0.9.17) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) rake ruby-graphviz (= 0.9.17) simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.5.8.gemfile.lock0000644000175000017500000000127012305405267023023 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activesupport (2.3.11) appraisal (0.4.0) bundler rake jnunemaker-validatable (1.8.0) mongo (0.16) mongo_mapper (0.5.8) activesupport (>= 2.3) jnunemaker-validatable (= 1.8.0) mongo (= 0.16) multi_json (1.0.4) rake (0.9.2.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES activesupport (= 2.3.11) appraisal (~> 0.4.0) mongo_mapper (= 0.5.8) rake simplecov state_machine! state-machine-1.2.0/gemfiles/sequel-3.23.0.gemfile0000644000175000017500000000034512305405267020764 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform=>:jruby gem "sequel", "3.23.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/graphviz-1.0.3.gemfile.lock0000644000175000017500000000076412305405267022170 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake multi_json (1.6.1) rake (10.0.3) ruby-graphviz (1.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) rake ruby-graphviz (= 1.0.3) simplecov state_machine! state-machine-1.2.0/gemfiles/data_mapper-1.0.2.gemfile0000644000175000017500000000041412305405267021653 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "dm-core", "1.0.2" gem "dm-migrations", "1.0.2" gem "dm-validations", "1.0.2" gem "dm-observer", "1.0.2" gem "dm-transactions", "1.0.2" gem "dm-sqlite-adapter", "1.0.2" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongoid-3.0.0.gemfile0000644000175000017500000000022612305405267021033 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.2.13.rc1" gem "mongoid", "3.0.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongo_mapper-0.8.6.gemfile0000644000175000017500000000023112305405267022071 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activesupport", "2.3.11" gem "mongo_mapper", "0.8.6" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongo_mapper-0.9.0.gemfile.lock0000644000175000017500000000154612305405267023025 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.0.10) activesupport (= 3.0.10) builder (~> 2.1.2) i18n (~> 0.5.0) activesupport (3.0.10) appraisal (0.4.0) bundler rake bson (1.4.0) bson (1.4.0-java) builder (2.1.2) i18n (0.5.0) mongo (1.4.0) bson (= 1.4.0) mongo_mapper (0.9.0) activemodel (~> 3.0.0) activesupport (~> 3.0.0) plucky (~> 0.3.6) multi_json (1.0.4) plucky (0.3.8) mongo (~> 1.3) rake (0.9.2.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) mongo_mapper (= 0.9.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.7.0.gemfile.lock0000644000175000017500000000127412305405267023021 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activesupport (2.3.11) appraisal (0.4.0) bundler rake jnunemaker-validatable (1.8.1) mongo (0.18.3) mongo_mapper (0.7.0) activesupport (>= 2.3) jnunemaker-validatable (= 1.8.1) mongo (= 0.18.3) multi_json (1.0.4) rake (0.9.2.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES activesupport (= 2.3.11) appraisal (~> 0.4.0) mongo_mapper (= 0.7.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/sequel-3.13.0.gemfile0000644000175000017500000000034512305405267020763 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform=>:jruby gem "sequel", "3.13.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongoid-2.5.2.gemfile0000644000175000017500000000025612305405267021044 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.2.13.rc1" gem "mongo", "~> 1.7.0" gem "mongoid", "2.5.2" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongoid-2.1.4.gemfile.lock0000644000175000017500000000157712305405267022000 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.1.11) activesupport (= 3.1.11) builder (~> 3.0.0) i18n (~> 0.6) activesupport (3.1.11) multi_json (~> 1.0) appraisal (0.4.1) bundler rake bson (1.7.1) bson (1.7.1-java) builder (3.0.4) i18n (0.6.1) mongo (1.7.1) bson (~> 1.7.1) mongoid (2.1.4) activemodel (~> 3.0) mongo (~> 1.3) tzinfo (~> 0.3.22) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activemodel (~> 3.1.0) appraisal (~> 0.4.0) mongo (~> 1.7.0) mongoid (= 2.1.4) rake simplecov state_machine! state-machine-1.2.0/gemfiles/active_model-3.0.5.gemfile0000644000175000017500000000017212305405267022037 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.0.5" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongo_mapper-0.7.5.gemfile0000644000175000017500000000023112305405267022067 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activesupport", "2.3.11" gem "mongo_mapper", "0.7.5" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-3.0.0.gemfile.lock0000644000175000017500000000213112305405267023134 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.0.0) activesupport (= 3.0.0) builder (~> 2.1.2) i18n (~> 0.4.1) activerecord (3.0.0) activemodel (= 3.0.0) activesupport (= 3.0.0) arel (~> 1.0.0) tzinfo (~> 0.3.23) activerecord-jdbc-adapter (1.2.7) activerecord-jdbcsqlite3-adapter (1.2.7) activerecord-jdbc-adapter (~> 1.2.7) jdbc-sqlite3 (~> 3.7.2) activesupport (3.0.0) appraisal (0.4.1) bundler rake arel (1.0.1) activesupport (~> 3.0.0) builder (2.1.2) i18n (0.4.2) jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activerecord (= 3.0.0) activerecord-jdbcsqlite3-adapter (= 1.2.7) appraisal (~> 0.4.0) rake simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/data_mapper-1.0.2.gemfile.lock0000644000175000017500000000260012305405267022601 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: addressable (2.3.2) appraisal (0.4.1) bundler rake data_objects (0.10.12) addressable (~> 2.1) dm-core (1.0.2) addressable (~> 2.2) extlib (~> 0.9.15) dm-do-adapter (1.0.2) data_objects (~> 0.10.2) dm-core (~> 1.0.2) dm-migrations (1.0.2) dm-core (~> 1.0.2) dm-observer (1.0.2) dm-core (~> 1.0.2) dm-sqlite-adapter (1.0.2) dm-do-adapter (~> 1.0.2) do_sqlite3 (~> 0.10.2) dm-transactions (1.0.2) dm-core (~> 1.0.2) dm-validations (1.0.2) dm-core (~> 1.0.2) do_jdbc (0.10.12-java) data_objects (= 0.10.12) do_sqlite3 (0.10.12) data_objects (= 0.10.12) do_sqlite3 (0.10.12-java) data_objects (= 0.10.12) do_jdbc (= 0.10.12) jdbc-sqlite3 (>= 3.5.8) extlib (0.9.16) jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) dm-core (= 1.0.2) dm-migrations (= 1.0.2) dm-observer (= 1.0.2) dm-sqlite-adapter (= 1.0.2) dm-transactions (= 1.0.2) dm-validations (= 1.0.2) rake simplecov state_machine! state-machine-1.2.0/gemfiles/active_model-3.0.0.gemfile.lock0000644000175000017500000000117412305405267022764 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.0.0) activesupport (= 3.0.0) builder (~> 2.1.2) i18n (~> 0.4.1) activesupport (3.0.0) appraisal (0.4.1) bundler rake builder (2.1.2) i18n (0.4.2) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.0.0) appraisal (~> 0.4.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.10.0.gemfile.lock0000644000175000017500000000164612305405267023076 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.13.rc1) activesupport (= 3.2.13.rc1) builder (~> 3.0.0) activesupport (3.2.13.rc1) i18n (= 0.6.1) multi_json (~> 1.0) appraisal (0.4.0) bundler rake bson (1.4.0) bson (1.4.0-java) builder (3.0.4) i18n (0.6.1) mongo (1.4.0) bson (= 1.4.0) mongo_mapper (0.10.0) activemodel (~> 3.0) activesupport (~> 3.0) plucky (~> 0.3.8) multi_json (1.0.4) plucky (0.3.8) mongo (~> 1.3) rake (0.9.2.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.2.13.rc1) appraisal (~> 0.4.0) mongo_mapper (= 0.10.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.11.2.gemfile.lock0000644000175000017500000000166712305405267023104 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.13.rc1) activesupport (= 3.2.13.rc1) builder (~> 3.0.0) activesupport (3.2.13.rc1) i18n (= 0.6.1) multi_json (~> 1.0) appraisal (0.4.1) bundler rake bson (1.7.1) bson (1.7.1-java) builder (3.0.4) i18n (0.6.1) mongo (1.7.1) bson (~> 1.7.1) mongo_mapper (0.11.2) activemodel (~> 3.0) activesupport (~> 3.0) plucky (~> 0.5.1) multi_json (1.6.1) plucky (0.5.2) mongo (~> 1.5) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.2.13.rc1) appraisal (~> 0.4.0) mongo (~> 1.7.0) mongo_mapper (= 0.11.2) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.8.0.gemfile0000644000175000017500000000030412305405267022064 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activesupport", "2.3.11" gem "mongo", "1.0.1" gem "plucky", "0.3.0" gem "mongo_mapper", "0.8.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/graphviz-0.9.21.gemfile.lock0000644000175000017500000000077112305405267022256 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.0) bundler rake multi_json (1.0.4) rake (0.9.2.2) ruby-graphviz (0.9.21) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) rake ruby-graphviz (= 0.9.21) simplecov state_machine! state-machine-1.2.0/gemfiles/mongoid-2.6.0.gemfile0000644000175000017500000000025612305405267021043 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.2.13.rc1" gem "mongo", "~> 1.7.0" gem "mongoid", "2.6.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/graphviz-0.9.17.gemfile0000644000175000017500000000017512305405267021332 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "ruby-graphviz", "0.9.17" gemspec :path=>"../"state-machine-1.2.0/gemfiles/graphviz-1.0.0.gemfile0000644000175000017500000000017412305405267021231 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "ruby-graphviz", "1.0.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-4.0.0.gemfile0000644000175000017500000000050512305405267022211 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3", "1.3.6" gem "activerecord", "4.0.0.beta1", :git=>"git://github.com/rails/rails.git", :ref=>"92d6dac" gem "activerecord-deprecated_finders", "0.0.3" gem "protected_attributes", "1.0.0" gem "rails-observers", "0.1.1" gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-3.23.0.gemfile.lock0000644000175000017500000000111412305405267021706 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2) multi_json (1.6.1) rake (10.0.3) sequel (3.23.0) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) jdbc-sqlite3 (= 3.7.2) rake sequel (= 3.23.0) simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.6.10.gemfile.lock0000644000175000017500000000127612305405267023103 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activesupport (2.3.11) appraisal (0.4.0) bundler rake jnunemaker-validatable (1.8.1) mongo (0.18.2) mongo_mapper (0.6.10) activesupport (>= 2.3) jnunemaker-validatable (= 1.8.1) mongo (= 0.18.2) multi_json (1.0.4) rake (0.9.2.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES activesupport (= 2.3.11) appraisal (~> 0.4.0) mongo_mapper (= 0.6.10) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.7.5.gemfile.lock0000644000175000017500000000137112305405267023024 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activesupport (2.3.11) appraisal (0.4.0) bundler rake bson (1.0) jnunemaker-validatable (1.8.4) activesupport (>= 2.3.4) mongo (1.0) bson (= 1.0) mongo_mapper (0.7.5) activesupport (>= 2.3.4) jnunemaker-validatable (= 1.8.4) mongo (= 1.0) multi_json (1.0.4) rake (0.9.2.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES activesupport (= 2.3.11) appraisal (~> 0.4.0) mongo_mapper (= 0.7.5) rake simplecov state_machine! state-machine-1.2.0/gemfiles/active_model-3.2.12.gemfile.lock0000644000175000017500000000123012305405267023042 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.12) activesupport (= 3.2.12) builder (~> 3.0.0) activesupport (3.2.12) i18n (~> 0.6) multi_json (~> 1.0) appraisal (0.4.1) bundler rake builder (3.0.4) i18n (0.6.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.2.12) appraisal (~> 0.4.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.7.0.gemfile0000644000175000017500000000023112305405267022062 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activesupport", "2.3.11" gem "mongo_mapper", "0.7.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/data_mapper-0.10.2.gemfile0000644000175000017500000000043712305405267021740 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "extlib", "0.9.16" gem "dm-core", "0.10.2" gem "dm-migrations", "0.10.2" gem "dm-validations", "0.10.2" gem "dm-observer", "0.10.2" gem "data_objects", "0.10.2" gem "do_sqlite3", "0.10.2" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongo_mapper-0.6.10.gemfile0000644000175000017500000000023212305405267022143 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activesupport", "2.3.11" gem "mongo_mapper", "0.6.10" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_model-3.2.1.gemfile0000644000175000017500000000017212305405267022035 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.2.1" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-2.1.2.gemfile0000644000175000017500000000037612305405267022220 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "activerecord", "2.1.2" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform=>:jruby gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-3.34.0.gemfile.lock0000644000175000017500000000111412305405267021710 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2) multi_json (1.6.1) rake (10.0.3) sequel (3.34.0) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) jdbc-sqlite3 (= 3.7.2) rake sequel (= 3.34.0) simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/mongoid-2.2.4.gemfile0000644000175000017500000000025412305405267021041 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "~> 3.1.0" gem "mongo", "~> 1.7.0" gem "mongoid", "2.2.4" gemspec :path=>"../"state-machine-1.2.0/gemfiles/data_mapper-1.1.0.gemfile.lock0000644000175000017500000000252512305405267022606 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: addressable (2.2.8) appraisal (0.4.1) bundler rake data_objects (0.10.12) addressable (~> 2.1) dm-core (1.1.0) addressable (~> 2.2.4) dm-do-adapter (1.1.0) data_objects (~> 0.10.2) dm-core (~> 1.1.0) dm-migrations (1.1.0) dm-core (~> 1.1.0) dm-observer (1.1.0) dm-core (~> 1.1.0) dm-sqlite-adapter (1.1.0) dm-do-adapter (~> 1.1.0) do_sqlite3 (~> 0.10.2) dm-transactions (1.1.0) dm-core (~> 1.1.0) dm-validations (1.1.0) dm-core (~> 1.1.0) do_jdbc (0.10.12-java) data_objects (= 0.10.12) do_sqlite3 (0.10.12) data_objects (= 0.10.12) do_sqlite3 (0.10.12-java) data_objects (= 0.10.12) do_jdbc (= 0.10.12) jdbc-sqlite3 (>= 3.5.8) jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) dm-core (= 1.1.0) dm-migrations (= 1.1.0) dm-observer (= 1.1.0) dm-sqlite-adapter (= 1.1.0) dm-transactions (= 1.1.0) dm-validations (= 1.1.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/active_record-3.1.1.gemfile.lock0000644000175000017500000000211012305405267023133 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.1.1) activesupport (= 3.1.1) builder (~> 3.0.0) i18n (~> 0.6) activerecord (3.1.1) activemodel (= 3.1.1) activesupport (= 3.1.1) arel (~> 2.2.1) tzinfo (~> 0.3.29) activerecord-jdbc-adapter (1.2.7) activerecord-jdbcsqlite3-adapter (1.2.7) activerecord-jdbc-adapter (~> 1.2.7) jdbc-sqlite3 (~> 3.7.2) activesupport (3.1.1) multi_json (~> 1.0) appraisal (0.4.1) bundler rake arel (2.2.3) builder (3.0.4) i18n (0.6.1) jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3 (1.3.6) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activerecord (= 3.1.1) activerecord-jdbcsqlite3-adapter (= 1.2.7) appraisal (~> 0.4.0) rake simplecov sqlite3 (= 1.3.6) state_machine! state-machine-1.2.0/gemfiles/mongoid-2.2.4.gemfile.lock0000644000175000017500000000157712305405267022001 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.1.11) activesupport (= 3.1.11) builder (~> 3.0.0) i18n (~> 0.6) activesupport (3.1.11) multi_json (~> 1.0) appraisal (0.4.1) bundler rake bson (1.7.1) bson (1.7.1-java) builder (3.0.4) i18n (0.6.1) mongo (1.7.1) bson (~> 1.7.1) mongoid (2.2.4) activemodel (~> 3.0) mongo (~> 1.3) tzinfo (~> 0.3.22) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activemodel (~> 3.1.0) appraisal (~> 0.4.0) mongo (~> 1.7.0) mongoid (= 2.2.4) rake simplecov state_machine! state-machine-1.2.0/gemfiles/data_mapper-0.10.2.gemfile.lock0000644000175000017500000000225612305405267022670 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: addressable (2.3.2) appraisal (0.4.1) bundler rake data_objects (0.10.2) addressable (~> 2.1) dm-core (0.10.2) addressable (~> 2.1) extlib (~> 0.9.14) dm-migrations (0.10.2) dm-core (~> 0.10.2) dm-observer (0.10.2) dm-core (~> 0.10.2) dm-validations (0.10.2) dm-core (~> 0.10.2) do_jdbc (0.10.2-java) data_objects (= 0.10.2) do_sqlite3 (0.10.2) data_objects (= 0.10.2) do_sqlite3 (0.10.2-java) data_objects (= 0.10.2) do_jdbc (= 0.10.2) jdbc-sqlite3 (>= 3.5.8) extlib (0.9.16) jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) data_objects (= 0.10.2) dm-core (= 0.10.2) dm-migrations (= 0.10.2) dm-observer (= 0.10.2) dm-validations (= 0.10.2) do_sqlite3 (= 0.10.2) extlib (= 0.9.16) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.8.3.gemfile0000644000175000017500000000030412305405267022067 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activesupport", "2.3.11" gem "mongo", "1.0.1" gem "plucky", "0.3.3" gem "mongo_mapper", "0.8.3" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongo_mapper-0.11.1.gemfile0000644000175000017500000000017412305405267022144 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "mongo_mapper", "0.11.1" gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-3.35.0.gemfile.lock0000644000175000017500000000111412305405267021711 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2) multi_json (1.6.1) rake (10.0.3) sequel (3.35.0) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) jdbc-sqlite3 (= 3.7.2) rake sequel (= 3.35.0) simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/mongoid-2.1.4.gemfile0000644000175000017500000000025412305405267021040 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "~> 3.1.0" gem "mongo", "~> 1.7.0" gem "mongoid", "2.1.4" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-2.1.0.gemfile.lock0000644000175000017500000000150012305405267023133 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activerecord (2.1.0) activesupport (= 2.1.0) activerecord-jdbc-adapter (1.2.7) activerecord-jdbcsqlite3-adapter (1.2.7) activerecord-jdbc-adapter (~> 1.2.7) jdbc-sqlite3 (~> 3.7.2) activesupport (2.1.0) appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES activerecord (= 2.1.0) activerecord-jdbcsqlite3-adapter (= 1.2.7) appraisal (~> 0.4.0) rake simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/graphviz-1.0.0.gemfile.lock0000644000175000017500000000076712305405267022170 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.0) bundler rake multi_json (1.0.4) rake (0.9.2.2) ruby-graphviz (1.0.0) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) rake ruby-graphviz (= 1.0.0) simplecov state_machine! state-machine-1.2.0/gemfiles/sequel-2.11.0.gemfile0000644000175000017500000000034512305405267020760 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform=>:jruby gem "sequel", "2.11.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-2.0.0.gemfile0000644000175000017500000000037612305405267022215 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "activerecord", "2.0.0" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform=>:jruby gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-2.1.0.gemfile0000644000175000017500000000037612305405267022216 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "activerecord", "2.1.0" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform=>:jruby gemspec :path=>"../"state-machine-1.2.0/gemfiles/data_mapper-0.9.11.gemfile.lock0000644000175000017500000000300412305405267022670 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: addressable (2.0.2) launchy (>= 0.3.2) rake (>= 0.7.3) rspec (>= 1.0.8) appraisal (0.4.1) bundler rake data_objects (0.9.11) addressable (~> 2.0) extlib (~> 0.9.9) hoe (>= 1.8.2) diff-lcs (1.1.3) dm-core (0.9.11) addressable (~> 2.0.2) data_objects (~> 0.9.11) extlib (~> 0.9.11) dm-migrations (0.9.11) dm-core (= 0.9.11) dm-observer (0.9.11) dm-core (= 0.9.11) dm-validations (0.9.11) dm-core (= 0.9.11) do_sqlite3 (0.9.11) data_objects (= 0.9.11) hoe (>= 1.8.2) extlib (0.9.11) hoe (3.5.0) rake (>= 0.8, < 11.0) launchy (2.0.3) launchy (2.0.3-java) spoon (~> 0.0.1) multi_json (1.6.1) rake (10.0.3) rspec (2.12.0) rspec-core (~> 2.12.0) rspec-expectations (~> 2.12.0) rspec-mocks (~> 2.12.0) rspec-core (2.12.2) rspec-expectations (2.12.1) diff-lcs (~> 1.1.3) rspec-mocks (2.12.2) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) spoon (0.0.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) data_objects (= 0.9.11) dm-core (= 0.9.11) dm-migrations (= 0.9.11) dm-observer (= 0.9.11) dm-validations (= 0.9.11) do_sqlite3 (= 0.9.11) extlib (= 0.9.11) rake simplecov state_machine! state-machine-1.2.0/gemfiles/sequel-3.10.0.gemfile.lock0000644000175000017500000000111412305405267021702 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2) multi_json (1.6.1) rake (10.0.3) sequel (3.10.0) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) jdbc-sqlite3 (= 3.7.2) rake sequel (= 3.10.0) simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/data_mapper-1.2.0.gemfile.lock0000644000175000017500000000252512305405267022607 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: addressable (2.2.8) appraisal (0.4.1) bundler rake data_objects (0.10.12) addressable (~> 2.1) dm-core (1.2.0) addressable (~> 2.2.6) dm-do-adapter (1.2.0) data_objects (~> 0.10.6) dm-core (~> 1.2.0) dm-migrations (1.2.0) dm-core (~> 1.2.0) dm-observer (1.2.0) dm-core (~> 1.2.0) dm-sqlite-adapter (1.2.0) dm-do-adapter (~> 1.2.0) do_sqlite3 (~> 0.10.6) dm-transactions (1.2.0) dm-core (~> 1.2.0) dm-validations (1.2.0) dm-core (~> 1.2.0) do_jdbc (0.10.12-java) data_objects (= 0.10.12) do_sqlite3 (0.10.12) data_objects (= 0.10.12) do_sqlite3 (0.10.12-java) data_objects (= 0.10.12) do_jdbc (= 0.10.12) jdbc-sqlite3 (>= 3.5.8) jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES appraisal (~> 0.4.0) dm-core (= 1.2.0) dm-migrations (= 1.2.0) dm-observer (= 1.2.0) dm-sqlite-adapter (= 1.2.0) dm-transactions (= 1.2.0) dm-validations (= 1.2.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/active_model-3.1.1.gemfile0000644000175000017500000000017212305405267022034 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.1.1" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-2.3.12.gemfile.lock0000644000175000017500000000150412305405267023224 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activerecord (2.3.12) activesupport (= 2.3.12) activerecord-jdbc-adapter (1.2.7) activerecord-jdbcsqlite3-adapter (1.2.7) activerecord-jdbc-adapter (~> 1.2.7) jdbc-sqlite3 (~> 3.7.2) activesupport (2.3.12) appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES activerecord (= 2.3.12) activerecord-jdbcsqlite3-adapter (= 1.2.7) appraisal (~> 0.4.0) rake simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/mongoid-3.1.0.gemfile.lock0000644000175000017500000000155412305405267021770 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.13.rc1) activesupport (= 3.2.13.rc1) builder (~> 3.0.0) activesupport (3.2.13.rc1) i18n (= 0.6.1) multi_json (~> 1.0) appraisal (0.4.1) bundler rake builder (3.0.4) i18n (0.6.1) mongoid (3.1.0) activemodel (~> 3.2) moped (~> 1.4.2) origin (~> 1.0) tzinfo (~> 0.3.22) moped (1.4.2) multi_json (1.6.1) origin (1.0.11) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.2.13.rc1) appraisal (~> 0.4.0) mongoid (= 3.1.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/active_record-3.2.12.gemfile0000644000175000017500000000037212305405267022277 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3", "1.3.6", :platform=>[:ruby, :mswin, :mingw] gem "activerecord", "3.2.12" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform=>:jruby gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongo_mapper-0.8.6.gemfile.lock0000644000175000017500000000150212305405267023022 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activesupport (2.3.11) appraisal (0.4.0) bundler rake bson (1.4.0) bson (1.4.0-java) jnunemaker-validatable (1.8.4) activesupport (>= 2.3.4) mongo (1.4.0) bson (= 1.4.0) mongo_mapper (0.8.6) activesupport (>= 2.3.4) jnunemaker-validatable (~> 1.8.4) plucky (~> 0.3.6) multi_json (1.0.4) plucky (0.3.8) mongo (~> 1.3) rake (0.9.2.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES activesupport (= 2.3.11) appraisal (~> 0.4.0) mongo_mapper (= 0.8.6) rake simplecov state_machine! state-machine-1.2.0/gemfiles/sequel-3.35.0.gemfile0000644000175000017500000000034512305405267020767 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform=>:jruby gem "sequel", "3.35.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-2.0.5.gemfile0000644000175000017500000000037612305405267022222 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "activerecord", "2.0.5" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform=>:jruby gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-2.0.0.gemfile.lock0000644000175000017500000000150012305405267023132 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activerecord (2.0.0) activesupport (= 2.0.0) activerecord-jdbc-adapter (1.2.7) activerecord-jdbcsqlite3-adapter (1.2.7) activerecord-jdbc-adapter (~> 1.2.7) jdbc-sqlite3 (~> 3.7.2) activesupport (2.0.0) appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES activerecord (= 2.0.0) activerecord-jdbcsqlite3-adapter (= 1.2.7) appraisal (~> 0.4.0) rake simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/mongoid-3.0.22.gemfile.lock0000644000175000017500000000155412305405267022053 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.13.rc1) activesupport (= 3.2.13.rc1) builder (~> 3.0.0) activesupport (3.2.13.rc1) i18n (= 0.6.1) multi_json (~> 1.0) appraisal (0.4.1) bundler rake builder (3.0.4) i18n (0.6.1) mongoid (3.0.22) activemodel (~> 3.1) moped (~> 1.2) origin (~> 1.0) tzinfo (~> 0.3.22) moped (1.4.2) multi_json (1.6.1) origin (1.0.11) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.2.13.rc1) appraisal (~> 0.4.0) mongoid (= 3.0.22) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.12.0.gemfile.lock0000644000175000017500000000166712305405267023103 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.13.rc1) activesupport (= 3.2.13.rc1) builder (~> 3.0.0) activesupport (3.2.13.rc1) i18n (= 0.6.1) multi_json (~> 1.0) appraisal (0.4.1) bundler rake bson (1.7.1) bson (1.7.1-java) builder (3.0.4) i18n (0.6.1) mongo (1.7.1) bson (~> 1.7.1) mongo_mapper (0.12.0) activemodel (~> 3.0) activesupport (~> 3.0) plucky (~> 0.5.2) multi_json (1.6.1) plucky (0.5.2) mongo (~> 1.5) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.2.13.rc1) appraisal (~> 0.4.0) mongo (~> 1.7.0) mongo_mapper (= 0.12.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/active_model-3.2.13.rc1.gemfile.lock0000644000175000017500000000125112305405267023532 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.13.rc1) activesupport (= 3.2.13.rc1) builder (~> 3.0.0) activesupport (3.2.13.rc1) i18n (= 0.6.1) multi_json (~> 1.0) appraisal (0.4.1) bundler rake builder (3.0.4) i18n (0.6.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.2.13.rc1) appraisal (~> 0.4.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.6.0.gemfile0000644000175000017500000000023112305405267022061 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activesupport", "2.3.11" gem "mongo_mapper", "0.6.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/sequel-3.4.0.gemfile0000644000175000017500000000034412305405267020702 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform=>:jruby gem "sequel", "3.4.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-3.0.0.gemfile0000644000175000017500000000037612305405267022216 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "activerecord", "3.0.0" gem "activerecord-jdbcsqlite3-adapter", "1.2.7", :platform=>:jruby gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongoid-2.4.10.gemfile.lock0000644000175000017500000000162212305405267022047 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.13.rc1) activesupport (= 3.2.13.rc1) builder (~> 3.0.0) activesupport (3.2.13.rc1) i18n (= 0.6.1) multi_json (~> 1.0) appraisal (0.4.1) bundler rake bson (1.7.1) bson (1.7.1-java) builder (3.0.4) i18n (0.6.1) mongo (1.7.1) bson (~> 1.7.1) mongoid (2.4.10) activemodel (~> 3.1) mongo (~> 1.3) tzinfo (~> 0.3.22) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.2.13.rc1) appraisal (~> 0.4.0) mongo (~> 1.7.0) mongoid (= 2.4.10) rake simplecov state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.9.0.gemfile0000644000175000017500000000017312305405267022071 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "mongo_mapper", "0.9.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-3.2.13.rc1.gemfile.lock0000644000175000017500000000215412305405267023713 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.13.rc1) activesupport (= 3.2.13.rc1) builder (~> 3.0.0) activerecord (3.2.13.rc1) activemodel (= 3.2.13.rc1) activesupport (= 3.2.13.rc1) arel (~> 3.0.2) tzinfo (~> 0.3.29) activerecord-jdbc-adapter (1.2.7) activerecord-jdbcsqlite3-adapter (1.2.7) activerecord-jdbc-adapter (~> 1.2.7) jdbc-sqlite3 (~> 3.7.2) activesupport (3.2.13.rc1) i18n (= 0.6.1) multi_json (~> 1.0) appraisal (0.4.1) bundler rake arel (3.0.2) builder (3.0.4) i18n (0.6.1) jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3 (1.3.6) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activerecord (= 3.2.13.rc1) activerecord-jdbcsqlite3-adapter (= 1.2.7) appraisal (~> 0.4.0) rake simplecov sqlite3 (= 1.3.6) state_machine! state-machine-1.2.0/gemfiles/sequel-3.29.0.gemfile0000644000175000017500000000034512305405267020772 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "sqlite3-ruby", "1.3.1", :platform=>[:ruby, :mswin, :mingw] gem "jdbc-sqlite3", "3.7.2", :platform=>:jruby gem "sequel", "3.29.0" gemspec :path=>"../"state-machine-1.2.0/gemfiles/mongo_mapper-0.11.2.gemfile0000644000175000017500000000026412305405267022145 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.2.13.rc1" gem "mongo", "~> 1.7.0" gem "mongo_mapper", "0.11.2" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_record-2.0.5.gemfile.lock0000644000175000017500000000150012305405267023137 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activerecord (2.0.5) activesupport (= 2.0.5) activerecord-jdbc-adapter (1.2.7) activerecord-jdbcsqlite3-adapter (1.2.7) activerecord-jdbc-adapter (~> 1.2.7) jdbc-sqlite3 (~> 3.7.2) activesupport (2.0.5) appraisal (0.4.1) bundler rake jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3-ruby (1.3.1) PLATFORMS java ruby DEPENDENCIES activerecord (= 2.0.5) activerecord-jdbcsqlite3-adapter (= 1.2.7) appraisal (~> 0.4.0) rake simplecov sqlite3-ruby (= 1.3.1) state_machine! state-machine-1.2.0/gemfiles/mongo_mapper-0.8.4.gemfile.lock0000644000175000017500000000150212305405267023020 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activesupport (2.3.11) appraisal (0.4.0) bundler rake bson (1.4.0) bson (1.4.0-java) jnunemaker-validatable (1.8.4) activesupport (>= 2.3.4) mongo (1.4.0) bson (= 1.4.0) mongo_mapper (0.8.4) activesupport (>= 2.3.4) jnunemaker-validatable (~> 1.8.4) plucky (~> 0.3.5) multi_json (1.0.4) plucky (0.3.8) mongo (~> 1.3) rake (0.9.2.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) PLATFORMS java ruby DEPENDENCIES activesupport (= 2.3.11) appraisal (~> 0.4.0) mongo_mapper (= 0.8.4) rake simplecov state_machine! state-machine-1.2.0/gemfiles/active_record-3.2.12.gemfile.lock0000644000175000017500000000211712305405267023225 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.2.12) activesupport (= 3.2.12) builder (~> 3.0.0) activerecord (3.2.12) activemodel (= 3.2.12) activesupport (= 3.2.12) arel (~> 3.0.2) tzinfo (~> 0.3.29) activerecord-jdbc-adapter (1.2.7) activerecord-jdbcsqlite3-adapter (1.2.7) activerecord-jdbc-adapter (~> 1.2.7) jdbc-sqlite3 (~> 3.7.2) activesupport (3.2.12) i18n (~> 0.6) multi_json (~> 1.0) appraisal (0.4.1) bundler rake arel (3.0.2) builder (3.0.4) i18n (0.6.1) jdbc-sqlite3 (3.7.2.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) sqlite3 (1.3.6) tzinfo (0.3.35) PLATFORMS java ruby DEPENDENCIES activerecord (= 3.2.12) activerecord-jdbcsqlite3-adapter (= 1.2.7) appraisal (~> 0.4.0) rake simplecov sqlite3 (= 1.3.6) state_machine! state-machine-1.2.0/gemfiles/data_mapper-1.0.1.gemfile0000644000175000017500000000041412305405267021652 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "dm-core", "1.0.1" gem "dm-migrations", "1.0.1" gem "dm-validations", "1.0.1" gem "dm-observer", "1.0.1" gem "dm-transactions", "1.0.1" gem "dm-sqlite-adapter", "1.0.1" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_model-3.2.12.gemfile0000644000175000017500000000017312305405267022120 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "activemodel", "3.2.12" gemspec :path=>"../"state-machine-1.2.0/gemfiles/active_model-3.1.1.gemfile.lock0000644000175000017500000000122412305405267022762 0ustar boutilboutilPATH remote: /home/aaron/Projects/Personal/pluginaweek/state_machine specs: state_machine (1.2.0) GEM remote: http://www.rubygems.org/ specs: activemodel (3.1.1) activesupport (= 3.1.1) builder (~> 3.0.0) i18n (~> 0.6) activesupport (3.1.1) multi_json (~> 1.0) appraisal (0.4.1) bundler rake builder (3.0.4) i18n (0.6.1) multi_json (1.6.1) rake (10.0.3) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) simplecov-html (0.7.1) PLATFORMS java ruby DEPENDENCIES activemodel (= 3.1.1) appraisal (~> 0.4.0) rake simplecov state_machine! state-machine-1.2.0/gemfiles/data_mapper-1.1.0.gemfile0000644000175000017500000000041412305405267021652 0ustar boutilboutil# This file was generated by Appraisal source "http://www.rubygems.org" gem "dm-core", "1.1.0" gem "dm-migrations", "1.1.0" gem "dm-validations", "1.1.0" gem "dm-observer", "1.1.0" gem "dm-transactions", "1.1.0" gem "dm-sqlite-adapter", "1.1.0" gemspec :path=>"../"