From 5f8be13c8cbcf1abace79831b9f987542ba1bb18 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thorsten=20B=C3=B6ttger?= <boettger@mt7.de>
Date: Fri, 24 Jan 2014 12:02:22 +0100
Subject: [PATCH] support event guards #85

---
 CHANGELOG.md            |  5 +++--
 README.md               | 11 +++++++++++
 lib/aasm/event.rb       | 19 +++++++++++++++----
 spec/models/guardian.rb | 31 +++++++++++++++++++++----------
 spec/unit/guard_spec.rb | 30 ++++++++++++++++++++++++++++++
 5 files changed, 80 insertions(+), 16 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e834f4..be8626c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,9 +6,10 @@
 
 ## 3.1.0 (not yet released)
 
- * support multiple guards per transition
- * allow configuring behavior of nested transactions (see [issue #107](https://github.com/aasm/aasm/issues/107))
  * validating the current state (see [issue #95](https://github.com/aasm/aasm/issues/95), thanks to [@ivantsepp](https://github.com/ivantsepp))
+ * allow configuring behavior of nested transactions (see [issue #107](https://github.com/aasm/aasm/issues/107))
+ * support multiple guards per transition
+ * support event guards (see [issue #85](https://github.com/aasm/aasm/issues/85))
 
 ## 3.0.26
 
diff --git a/README.md b/README.md
index 8f708d0..2adc07a 100644
--- a/README.md
+++ b/README.md
@@ -196,11 +196,22 @@ job.sleep       # => raises AASM::InvalidTransition
 You can even provide a number of guards, which all have to succeed to proceed
 
 ```ruby
+    def walked_the_dog?; ...; end
+
     event :sleep do
       transitions :from => :running, :to => :sleeping, :guards => [:cleaning_needed?, :walked_the_dog?]
     end
 ```
 
+If you want to provide guards for all transitions withing an event, you can use event guards
+
+```ruby
+    event :sleep, :guards => [:walked_the_dog?] do
+      transitions :from => :running, :to => :sleeping, :guards => [:cleaning_needed?]
+      transitions :from => :cleaning, :to => :sleeping
+    end
+```
+
 ### ActiveRecord
 
 AASM comes with support for ActiveRecord and allows automatical persisting of the object's
diff --git a/lib/aasm/event.rb b/lib/aasm/event.rb
index 89aac8f..338c669 100644
--- a/lib/aasm/event.rb
+++ b/lib/aasm/event.rb
@@ -6,6 +6,7 @@ module AASM
     def initialize(name, options = {}, &block)
       @name = name
       @transitions = []
+      @guards = Array(options[:guard] || options[:guards])
       update(options, &block)
     end
 
@@ -57,18 +58,28 @@ module AASM
     ## DSL interface
     def transitions(definitions=nil)
       if definitions # define new transitions
-        # Create a separate transition for each from state to the given state
+        # Create a separate transition for each from-state to the given state
         Array(definitions[:from]).each do |s|
-          @transitions << AASM::Transition.new(definitions.merge({:from => s.to_sym}))
+          @transitions << AASM::Transition.new(attach_event_guards(definitions.merge(:from => s.to_sym)))
+        end
+        # Create a transition if :to is specified without :from (transitions from ANY state)
+        if @transitions.empty? && definitions[:to]
+          @transitions << AASM::Transition.new(attach_event_guards(definitions))
         end
-        # Create a transition if to is specified without from (transitions from ANY state)
-        @transitions << AASM::Transition.new(definitions) if @transitions.empty? && definitions[:to]
       end
       @transitions
     end
 
   private
 
+    def attach_event_guards(definitions)
+      unless @guards.empty?
+        given_guards = Array(definitions.delete(:guard) || definitions.delete(:guards))
+        definitions[:guards] = given_guards + @guards
+      end
+      definitions
+    end
+
     def update(options = {}, &block)
       @options = options
       if block then
diff --git a/spec/models/guardian.rb b/spec/models/guardian.rb
index 3e254d4..2f90d0a 100644
--- a/spec/models/guardian.rb
+++ b/spec/models/guardian.rb
@@ -11,6 +11,7 @@ class Guardian
     event :use_one_guard_that_fails do
       transitions :from => :alpha, :to => :beta, :guard => :fail
     end
+
     event :use_guards_that_succeed do
       transitions :from => :alpha, :to => :beta, :guards => [:succeed, :another_succeed]
     end
@@ -20,18 +21,28 @@ class Guardian
     event :use_guards_where_the_second_fails do
       transitions :from => :alpha, :to => :beta, :guards => [:fail, :succeed]
     end
+
+    event :use_event_guards_that_succeed, :guards => [:succeed, :another_succeed] do
+      transitions :from => :alpha, :to => :beta
+    end
+    event :use_event_and_transition_guards_that_succeed, :guards => [:succeed, :another_succeed] do
+      transitions :from => :alpha, :to => :beta, :guards => [:more_succeed]
+    end
+    event :use_event_guards_where_the_first_fails, :guards => [:succeed, :fail] do
+      transitions :from => :alpha, :to => :beta
+    end
+    event :use_event_guards_where_the_second_fails, :guards => [:fail, :succeed] do
+      transitions :from => :alpha, :to => :beta, :guard => :another_succeed
+    end
+    event :use_event_and_transition_guards_where_third_fails, :guards => [:succeed, :another_succeed] do
+      transitions :from => :alpha, :to => :beta, :guards => [:fail]
+    end
   end
 
-  def fail
-    false
-  end
+  def fail; false; end
 
-  def succeed
-    true
-  end
-
-  def another_succeed
-    true
-  end
+  def succeed; true; end
+  def another_succeed; true; end
+  def more_succeed; true; end
 
 end
diff --git a/spec/unit/guard_spec.rb b/spec/unit/guard_spec.rb
index 085b842..1ab4ac1 100644
--- a/spec/unit/guard_spec.rb
+++ b/spec/unit/guard_spec.rb
@@ -28,3 +28,33 @@ describe "per-transition guards" do
     expect(guardian).to be_alpha
   end
 end
+
+describe "event guards" do
+  let(:guardian) { Guardian.new }
+
+  it "allows the transition if the event guards succeed" do
+    expect { guardian.use_event_guards_that_succeed! }.to_not raise_error
+    expect(guardian).to be_beta
+  end
+
+  it "allows the transition if the event and transition guards succeed" do
+    expect { guardian.use_event_and_transition_guards_that_succeed! }.to_not raise_error
+    expect(guardian).to be_beta
+  end
+
+  it "stops the transition if the first event guard fails" do
+    expect { guardian.use_event_guards_where_the_first_fails! }.to raise_error(AASM::InvalidTransition)
+    expect(guardian).to be_alpha
+  end
+
+  it "stops the transition if the second event guard fails" do
+    expect { guardian.use_event_guards_where_the_second_fails! }.to raise_error(AASM::InvalidTransition)
+    expect(guardian).to be_alpha
+  end
+
+  it "stops the transition if the transition guard fails" do
+    expect { guardian.use_event_and_transition_guards_where_third_fails! }.to raise_error(AASM::InvalidTransition)
+    expect(guardian).to be_alpha
+  end
+
+end