Skip to content

Commit

Permalink
Merge & CoreDataQuery scopes for RubyMotion
Browse files Browse the repository at this point in the history
Merge branch 'master' of https://github.com/aasm/aasm into RubyMotion
CoreDataQuery now supports named scopes defined by AASM
  • Loading branch information
Infotaku committed Mar 19, 2016
2 parents 3214e46 + 9e22db4 commit cf09125
Show file tree
Hide file tree
Showing 45 changed files with 771 additions and 128 deletions.
13 changes: 9 additions & 4 deletions .travis.yml
Expand Up @@ -3,18 +3,18 @@ language: ruby
cache: bundler

rvm:
# - 1.8.7
# - 1.9.2
- 1.9.3
- 2.0.0
- 2.1
- 2.2
# - jruby-18mode # JRuby in 1.8 mode
- 2.3
- jruby-1.7 # JRuby in 1.9 mode
- jruby-9.0.4.0
- rbx-2.5.8

services: mongodb
services:
- mongodb
- redis-server

gemfile:
- gemfiles/rails_3.2_stable.gemfile
Expand All @@ -35,8 +35,13 @@ matrix:
allow_failures:
- rvm: rbx-2.2.1
- rvm: jruby-1.7
- rvm: 2.3
exclude:
- rvm: 1.9.3
gemfile: gemfiles/rails_4.1.gemfile
- rvm: 2.2
gemfile: gemfiles/rails_3.2_stable.gemfile
- rvm: 2.3
gemfile: gemfiles/rails_3.2_stable.gemfile
- rvm: jruby-19mode
gemfile: gemfiles/rails_4.1.gemfile
17 changes: 16 additions & 1 deletion CHANGELOG.md
@@ -1,12 +1,27 @@
# CHANGELOG

## unreleased
## 4.10.0 (not yet released)

* fix: some issues with RubyMotion (see [issue #320](https://github.com/aasm/aasm/pull/320) for details, thanks to [@Infotaku](https://github.com/Infotaku))
* fix: transitions now work in dup'ed copies (see [issue #325](https://github.com/aasm/aasm/pull/325) which fixes [issue #273](https://github.com/aasm/aasm/pull/273) for details, thanks to [@lingceng](https://github.com/lingceng))
* fix: allow skipping the `aasm_ensure_initial_state` callback (see [issue #326](https://github.com/aasm/aasm/pull/326) for details, thanks to [@sineed](https://github.com/sineed))
* fix: has_many association helper works again for Mongoid (see [issue #333](https://github.com/aasm/aasm/pull/333) which fixes [issue #332](https://github.com/aasm/aasm/pull/332) for details, thanks to [@anilmaurya](https://github.com/anilmaurya))
* improve performance / refactor: load and run only code which is needed (see [issue #336](https://github.com/aasm/aasm/pull/336) for details, thanks to [@csmuc](https://github.com/csmuc))
* improve: warn when overriding an existing method (see [issue #340](https://github.com/aasm/aasm/pull/340) which fixes [issue #335](https://github.com/aasm/aasm/pull/335) for details, thanks to [@pirj](https://github.com/pirj))
* fix: correct error message (by not evaluating the current state lazily) (see [issue #341](https://github.com/aasm/aasm/pull/341) which fixes [issue #312](https://github.com/aasm/aasm/pull/312) for details, thanks to [@pirj](https://github.com/pirj))
* addition: support for Redis as persistence layer (see [issue #190](https://github.com/aasm/aasm/pull/190) for details, thanks to [@javajax](https://github.com/javajax))
* addition: support transition `:success` callbacks (see [issue #239](https://github.com/aasm/aasm/pull/239) which fixes [issue #236](https://github.com/aasm/aasm/pull/236) for details, thanks to [@brega](https://github.com/brega))
* addition: support for namespacing methods and state names (see [issue #259](https://github.com/aasm/aasm/pull/259) for details, thanks to [@allspiritseve](https://github.com/allspiritseve))
* addition: support for defining multiple states in one line (see [issue #288](https://github.com/aasm/aasm/pull/288) which fixes [issue #146](https://github.com/aasm/aasm/pull/146) for details, thanks to [@HParker](https://github.com/HParker))

## 4.9.0

* add support for callback classes (`after` only) (see [issue #316](https://github.com/aasm/aasm/pull/316) for details, thanks to [@mlr](https://github.com/mlr))
* allow easier extension of _AASM_ (utilising the idea of _ApplicationRecords_ from _Rails 5_) (see [issue #296](https://github.com/aasm/aasm/pull/296) for details, thanks to [@mlr](https://github.com/mlr))
* support pessimistic locking for _ActiveRecord_ (see [issue #283](https://github.com/aasm/aasm/pull/283) for details, thanks to [@HoyaBoya](https://github.com/HoyaBoya))
* fix: support database sharding for _ActiveRecord_ (see [issue #289](https://github.com/aasm/aasm/pull/289) for details, thanks to [@scambra](https://github.com/scambra))
* fix: some issues with RubyMotion (see [issue #318](https://github.com/aasm/aasm/pull/318) for details, thanks to [@Infotaku](https://github.com/Infotaku))
* fix: Rails generator now features the correct namespace (see [issue #328](https://github.com/aasm/aasm/pull/328) and [issue #329](https://github.com/aasm/aasm/pull/329) for details, thanks to [@anilmaurya](https://github.com/anilmaurya))


## 4.8.0
Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Expand Up @@ -17,4 +17,7 @@ gem 'sequel'
# gem 'mongo_mapper', '~> 0.13'
# gem 'bson_ext', :platforms => :ruby

# uncomment if you want to run specs for Redis persistence
# gem "redis-objects"

gemspec
30 changes: 23 additions & 7 deletions README.md
Expand Up @@ -28,8 +28,7 @@ class Job

aasm do
state :sleeping, :initial => true
state :running
state :cleaning
state :running, :cleaning

event :run do
transitions :from => :sleeping, :to => :running
Expand Down Expand Up @@ -97,6 +96,7 @@ class Job
aasm do
state :sleeping, :initial => true, :before_enter => :do_something
state :running
state :finished

after_all_transitions :log_status_change

Expand Down Expand Up @@ -149,7 +149,7 @@ In this case `do_something` is called before actually entering the state `sleepi
while `notify_somebody` is called after the transition `run` (from `sleeping` to `running`)
is finished.

AASM will also initialize `LogRunTime` and run the `call` method for you after the transition from `runnung` to `finished` in the example above. You can pass arguments to the class by defining an initialize method on it, like this:
AASM will also initialize `LogRunTime` and run the `call` method for you after the transition from `running` to `finished` in the example above. You can pass arguments to the class by defining an initialize method on it, like this:

```
class LogRunTime
Expand Down Expand Up @@ -180,7 +180,8 @@ begin
new_state before_enter
new_state enter
...update state...
event success # if persist successful
transition success # if persist successful
event success # if persist successful
old_state after_exit
new_state after_enter
event after
Expand Down Expand Up @@ -404,7 +405,7 @@ simple.aasm(:work).current

_AASM_ doesn't prohibit to define the same event in more than one state machine. The
latest definition "wins" and overrides previous definitions. Nonetheless, a warning is issued:
`SimpleMultipleExample: The aasm event name run is already used!`.
`SimpleMultipleExample: overriding method 'run'!`.

All _AASM_ class- and instance-level `aasm` methods accept a state machine selector.
So, for example, to use inspection on a class level, you have to use
Expand All @@ -423,7 +424,7 @@ AASM allows you to easily extend `AASM::Base` for your own application purposes.

Let's suppose we have common logic across many AASM models. We can embody this logic in a sub-class of `AASM::Base`.

```
```ruby
class CustomAASMBase < AASM::Base
# A custom transiton that we want available across many AASM models.
def count_transitions!
Expand Down Expand Up @@ -460,7 +461,7 @@ end

When we declare our model that has an AASM state machine, we simply declare the AASM block with a `:with` key to our own class.

```
```ruby
class SimpleCustomExample
include AASM

Expand Down Expand Up @@ -662,6 +663,21 @@ class Job
end
```

### Redis

AASM also supports persistence in Redis.
Make sure to include Redis::Objects before you include AASM.

```ruby
class User
include Redis::Objects
include AASM

aasm do
end
end
```

### Automatic Scopes

AASM will automatically create scope methods for each state in the model.
Expand Down
1 change: 1 addition & 0 deletions gemfiles/rails_4.0.gemfile
Expand Up @@ -10,5 +10,6 @@ gem 'mongoid', '~>4.0' if Gem::Version.create(RUBY_VERSION.dup) >= Gem::Version.
gem 'sequel'
gem 'dynamoid', '~> 1', :platforms => :ruby
gem 'aws-sdk', '~>2', :platforms => :ruby
gem "redis-objects"

gemspec :path => "../"
1 change: 1 addition & 0 deletions gemfiles/rails_4.1.gemfile
Expand Up @@ -10,5 +10,6 @@ gem 'mongoid', '~>4.0' if Gem::Version.create(RUBY_VERSION.dup) >= Gem::Version.
gem 'sequel'
gem 'dynamoid', '~> 1', :platforms => :ruby
gem 'aws-sdk', '~>2', :platforms => :ruby
gem "redis-objects"

gemspec :path => "../"
1 change: 1 addition & 0 deletions gemfiles/rails_4.2_mongo_mapper.gemfile
Expand Up @@ -12,5 +12,6 @@ gem 'mongo_mapper'
gem 'bson_ext', :platforms => :ruby
gem 'dynamoid', '~> 1', :platforms => :ruby
gem 'aws-sdk', '~>2', :platforms => :ruby
gem "redis-objects"

gemspec :path => "../"
6 changes: 6 additions & 0 deletions lib/aasm/aasm.rb
Expand Up @@ -71,6 +71,11 @@ def aasm(name=:default)
@aasm[name.to_sym] ||= AASM::InstanceBase.new(self, name.to_sym)
end

def initialize_dup(other)
@aasm = {}
super
end

private

# Takes args and a from state and removes the first
Expand Down Expand Up @@ -145,6 +150,7 @@ def aasm_fired(state_machine_name, event, old_state, new_state_name, options, *a
persist_successful = aasm(state_machine_name).set_current_state_with_persistence(new_state_name)
if persist_successful
yield if block_given?
event.fire_transition_callbacks(self, *process_args(event, old_state.name, *args))
event.fire_callbacks(:success, self)
end
else
Expand Down
95 changes: 67 additions & 28 deletions lib/aasm/base.rb
@@ -1,8 +1,7 @@
module AASM
class Base

attr_reader :klass,
:state_machine
attr_reader :klass, :state_machine

def initialize(klass, name, state_machine, options={}, &block)
@klass = klass
Expand Down Expand Up @@ -38,6 +37,9 @@ def initialize(klass, name, state_machine, options={}, &block)

configure :enum, nil

# Set to true to namespace reader methods and constants
configure :namespace, false

# make sure to raise an error if no_direct_assignment is enabled
# and attribute is directly assigned though
aasm_name = @name
Expand Down Expand Up @@ -71,48 +73,54 @@ def initial_state(new_initial_state=nil)
end

# define a state
def state(name, options={})
@state_machine.add_state(name, klass, options)

if klass.instance_methods.include?("#{name}?")
warn "#{klass.name}: The aasm state name #{name} is already used!"
end

aasm_name = @name
klass.send :define_method, "#{name}?", ->() do
aasm(:"#{aasm_name}").current_state == :"#{name}"
end
# args
# [0] state
# [1] options (or nil)
# or
# [0] state
# [1..] state
def state(*args)
names, options = interpret_state_args(args)
names.each do |name|
@state_machine.add_state(name, klass, options)

aasm_name = @name.to_sym
state = name.to_sym

method_name = namespace? ? "#{namespace}_#{name}" : name
safely_define_method klass, "#{method_name}?", -> do
aasm(aasm_name).current_state == state
end

unless klass.const_defined?("STATE_#{name.upcase}")
klass.const_set("STATE_#{name.upcase}", name)
const_name = namespace? ? "STATE_#{namespace.upcase}_#{name.upcase}" : "STATE_#{name.upcase}"
unless klass.const_defined?(const_name)
klass.const_set(const_name, name)
end
end
end

# define an event
def event(name, options={}, &block)
@state_machine.add_event(name, options, &block)

if klass.instance_methods.include?("may_#{name}?".to_sym)
warn "#{klass.name}: The aasm event name #{name} is already used!"
end
aasm_name = @name.to_sym
event = name.to_sym

# an addition over standard aasm so that, before firing an event, you can ask
# may_event? and get back a boolean that tells you whether the guard method
# on the transition will let this happen.
aasm_name = @name

klass.send :define_method, "may_#{name}?", ->(*args) do
aasm(:"#{aasm_name}").may_fire_event?(:"#{name}", *args)
safely_define_method klass, "may_#{name}?", ->(*args) do
aasm(aasm_name).may_fire_event?(event, *args)
end

klass.send :define_method, "#{name}!", ->(*args, &block) do
aasm(:"#{aasm_name}").current_event = :"#{name}!"
aasm_fire_event(:"#{aasm_name}", :"#{name}", {:persist => true}, *args, &block)
safely_define_method klass, "#{name}!", ->(*args, &block) do
aasm(aasm_name).current_event = :"#{name}!"
aasm_fire_event(aasm_name, event, {:persist => true}, *args, &block)
end

klass.send :define_method, "#{name}", ->(*args, &block) do
aasm(:"#{aasm_name}").current_event = :"#{name}"
aasm_fire_event(:"#{aasm_name}", :"#{name}", {:persist => false}, *args, &block)
safely_define_method klass, name, ->(*args, &block) do
aasm(aasm_name).current_event = event
aasm_fire_event(aasm_name, event, {:persist => false}, *args, &block)
end
end

Expand Down Expand Up @@ -165,6 +173,7 @@ def from_states_for_state(state, options={})
if options[:transition]
@state_machine.events[options[:transition]].transitions_to_state(state).flatten.map(&:from).flatten
else

events.map {|e| e.transitions_to_state(state)}.flatten.map(&:from).flatten
end
end
Expand All @@ -183,5 +192,35 @@ def configure(key, default_value)
end
end

def safely_define_method(klass, method_name, method_definition)
if klass.instance_methods.include?(method_name.to_sym)
warn "#{klass.name}: overriding method '#{method_name}'!"
end

klass.send(:define_method, method_name, method_definition)
end

def namespace?
!!@state_machine.config.namespace
end

def namespace
if @state_machine.config.namespace == true
@name
else
@state_machine.config.namespace
end
end

def interpret_state_args(args)
if args.last.is_a?(Hash) && args.size == 2
[[args.first], args.last]
elsif args.size > 0
[args, {}]
else
raise "count not parse states: #{args}"
end
end

end
end
3 changes: 3 additions & 0 deletions lib/aasm/configuration.rb
Expand Up @@ -25,5 +25,8 @@ class Configuration
attr_accessor :with_klass

attr_accessor :enum

# namespace reader methods and constants
attr_accessor :namespace
end
end
9 changes: 8 additions & 1 deletion lib/aasm/core/event.rb
Expand Up @@ -8,6 +8,7 @@ def initialize(name, state_machine, options = {}, &block)
@name = name
@state_machine = state_machine
@transitions = []
@valid_transitions = {}
@guards = Array(options[:guard] || options[:guards] || options[:if])
@unless = Array(options[:unless]) #TODO: This could use a better name

Expand Down Expand Up @@ -62,6 +63,12 @@ def fire_callbacks(callback_name, record, *args)
invoke_callbacks(@options[callback_name], record, args)
end

def fire_transition_callbacks(obj, *args)
from_state = obj.aasm(state_machine.name).current_state
transition = @valid_transitions[from_state]
@valid_transitions[from_state].invoke_success_callbacks(obj, *args) if transition
end

def ==(event)
if event.is_a? Symbol
name == event
Expand Down Expand Up @@ -103,7 +110,6 @@ def attach_event_guards(definitions)
definitions
end

# Execute if test == false, otherwise return true/false depending on whether it would fire
def _fire(obj, options={}, to_state=nil, *args)
result = options[:test_only] ? false : nil
if @transitions.map(&:from).any?
Expand All @@ -130,6 +136,7 @@ def _fire(obj, options={}, to_state=nil, *args)
if options[:test_only]
# result = true
else
Array(transition.to).each {|to| @valid_transitions[to] = transition }
transition.execute(obj, *args)
end

Expand Down

0 comments on commit cf09125

Please sign in to comment.