ellington 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Ellington
1
+ # Ellington
2
2
  Named after [Duke Ellington](http://www.dukeellington.com/) whose signature tune was ["Take the 'A' Train"](http://en.wikipedia.org/wiki/Take_the_%22A%22_Train).
3
3
  The song was written about [New York City's A train](http://en.wikipedia.org/wiki/A_%28New_York_City_Subway_service%29).
4
4
 
@@ -7,7 +7,7 @@ The song was written about [New York City's A train](http://en.wikipedia.org/wik
7
7
  #### Ellington is an architecture for modeling complex business processes.
8
8
 
9
9
  Ellington is a collection of simple concepts designed to bring discipline, organization, and modularity to a project.
10
- The base implementation is very light, weighing in around 500 lines.
10
+ ([View the slides from the intro talk.](https://speakerdeck.com/hopsoft/ellington-intro))
11
11
 
12
12
  The nomenclature is taken from [New York's subway system](http://en.wikipedia.org/wiki/New_York_City_Subway).
13
13
  We've found that using cohesive physical metaphors helps people reason more clearly about the complexities of software.
@@ -34,45 +34,3 @@ The Ellington architecture should only be applied **after** a good understanding
34
34
 
35
35
  ![Ellington Diagram](https://raw.github.com/hopsoft/ellington/master/doc/primary-terms.png)
36
36
 
37
- #### Additional Terms
38
-
39
- - **[State Catalog](https://github.com/hopsoft/ellington/wiki/State)** - A collection of states and their transitions.
40
- - **[State Transition](https://github.com/hopsoft/ellington/wiki/State)** - The `transition`, performed on the `passenger`, from one `state` to another.
41
- - **[Ticket](https://github.com/hopsoft/ellington/wiki/Ticket)** - An authorization token that indicates the `passenger` can ride a specific `route`.
42
- - **[Goal](https://github.com/hopsoft/ellington/wiki/Goal)** - A list of expected `states`.
43
- - **[Connection](https://github.com/hopsoft/ellington/wiki/Connection)** - A link between `lines`.
44
- - **[Transfer](Transfer)** - A link between `routes`.
45
- - **[Network](Network)** - An entire system of `routes` & `transfers` designed to work together.
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
- ```
68
- Route class
69
- composed of line instances
70
- each line instance holds a ref to this route's class
71
-
72
- Line class
73
- composed of station instances
74
- each station instance holds a ref to this line's class
75
-
76
- Station class
77
- ```
78
-
@@ -2,40 +2,46 @@ require "thread"
2
2
 
3
3
  module Ellington
4
4
  class Conductor
5
- attr_reader :route, :conducting
5
+ attr_reader :route
6
6
 
7
7
  def initialize(route)
8
8
  @route = route
9
9
  @conducting = false
10
10
  end
11
11
 
12
- def start(delay=0)
13
- return if conducting
12
+ def conducting?
13
+ @conducting
14
+ end
15
+
16
+ def start
17
+ return if conducting?
14
18
 
15
19
  mutex.synchronize do
16
20
  @stop = false
17
21
  @conducting = true
18
22
  end
19
23
 
20
- thread = Thread.new do
24
+ @thread = Thread.new do
21
25
  loop do
22
26
  if @stop
23
27
  mutex.synchronize { @conducting = false }
24
- break
28
+ Thread.current.exit
25
29
  end
26
30
  gather_passengers.each do |passenger|
27
31
  escort(passenger)
28
32
  end
29
- sleep delay
30
33
  end
31
34
  end
32
- thread.join
33
35
  end
34
36
 
35
37
  def stop
36
38
  mutex.synchronize { @stop = true }
37
39
  end
38
40
 
41
+ def wait
42
+ thread.join
43
+ end
44
+
39
45
  def verify(passenger)
40
46
  true
41
47
  end
@@ -49,7 +55,9 @@ module Ellington
49
55
  route.lines.first.board passenger
50
56
  end
51
57
 
52
- private
58
+ protected
59
+
60
+ attr_reader :thread
53
61
 
54
62
  def mutex
55
63
  @mutex ||= Mutex.new
@@ -1,10 +1,25 @@
1
1
  module Ellington
2
2
  class Connection
3
- attr_reader :line, :states
3
+ attr_reader :line, :type, :states
4
4
 
5
- def initialize(line, states)
5
+ def initialize(line, type, *states)
6
6
  @line = line
7
- @states = states
7
+ @type = type
8
+ @states = Ellington::Target.new(*states)
9
+ end
10
+
11
+ def required?(passenger)
12
+ return false if line.boarded?(passenger)
13
+
14
+ if type == :if_any
15
+ return states.satisfied?(passenger)
16
+ end
17
+
18
+ if type == :if_all
19
+ return (passenger.state_history & states).length == states.length
20
+ end
21
+
22
+ false
8
23
  end
9
24
 
10
25
  end
@@ -2,10 +2,7 @@ module Ellington
2
2
  class ListAlreadyContainsType < StandardError
3
3
  end
4
4
 
5
- class InvalidStates < StandardError
6
- end
7
-
8
- class AttendandDisapproves < StandardError
5
+ class AttendantDisapproves < StandardError
9
6
  end
10
7
 
11
8
  class InvalidStateTransition < StandardError
@@ -14,9 +11,12 @@ module Ellington
14
11
  class NotImplementedError < StandardError
15
12
  end
16
13
 
17
- class StationAlreadyBelongsToLine < StandardError
14
+ class NoStationsDeclared < StandardError
15
+ end
16
+
17
+ class NoLinesDeclared < StandardError
18
18
  end
19
19
 
20
- class PassengerAlreadyAssignedToTicket < StandardError
20
+ class NoGoalDeclared < StandardError
21
21
  end
22
22
  end
@@ -5,7 +5,6 @@ module Ellington
5
5
  class Line
6
6
  class << self
7
7
  include Observable
8
- attr_accessor :route
9
8
 
10
9
  def stations
11
10
  @stations ||= Ellington::StationList.new(self)
@@ -16,30 +15,42 @@ module Ellington
16
15
  end
17
16
  alias_method :passed, :pass_target
18
17
  alias_method :goal, :pass_target
19
-
20
- def connections
21
- @connections ||= {}
22
- end
23
18
  end
24
19
 
25
20
  extend Forwardable
26
21
  include Observable
27
22
  include HasTargets
28
23
 
24
+ attr_accessor :route_class, :route
25
+
29
26
  def_delegators :"self.class",
30
- :route,
31
- :route=,
32
27
  :stations,
33
28
  :pass_target,
34
29
  :passed,
35
30
  :goal
36
31
 
32
+ def initialize
33
+ if stations.empty?
34
+ message = "#{self.class.name} has no stations!"
35
+ raise Ellington::NoStationsDeclared.new(message)
36
+ end
37
+
38
+ if goal.empty?
39
+ message = "#{self.class.name} has no goal!"
40
+ raise Ellington::NoGoalDeclared.new(message)
41
+ end
42
+ end
43
+
37
44
  def board(passenger, options={})
38
45
  formula.run passenger, options
39
46
  end
40
47
 
48
+ def boarded?(passenger)
49
+ !(passenger.state_history & states.keys).empty?
50
+ end
51
+
41
52
  def name
42
- @name ||= "#{self.class.name}::#{route.name}"
53
+ @name ||= "#{self.class.name}::#{route_class.name}"
43
54
  end
44
55
 
45
56
  def formula
@@ -19,12 +19,13 @@ module Ellington
19
19
  if options[:line_completed]
20
20
  message << "[LINE COMPLETED]"
21
21
  message << "[#{line.state(passenger)}]"
22
+ message << "[#{line.name}]"
22
23
  end
23
24
  if options[:station_completed]
24
25
  message << "[STATION COMPLETED]"
25
26
  message << "[#{station.state(passenger)}]"
27
+ message << "[#{station_full_name}]"
26
28
  end
27
- message << "[#{station_full_name}]"
28
29
  line.route.log_passenger_attrs.each do |attr|
29
30
  message << "[#{attr}:#{passenger.send(attr)}]"
30
31
  end
@@ -2,22 +2,22 @@ require "delegate"
2
2
 
3
3
  module Ellington
4
4
  class LineList < SimpleDelegator
5
- attr_reader :route
5
+ attr_reader :route_class
6
6
 
7
- def initialize(route)
8
- @route = route
7
+ def initialize(route_class)
8
+ @route_class = route_class
9
9
  @inner_list = UniqueTypeArray.new
10
10
  super @inner_list
11
11
  end
12
-
12
+
13
13
  def push(line)
14
14
  value = inner_list << line
15
- line.route = route
15
+ line.route_class = route_class
16
16
  value
17
17
  end
18
18
  alias_method :<<, :push
19
19
 
20
- protected
20
+ protected
21
21
 
22
22
  attr_reader :inner_list
23
23
  end
@@ -39,6 +39,10 @@ module Ellington
39
39
  @current_state = value
40
40
  end
41
41
 
42
+ def state_history
43
+ @state_history ||= []
44
+ end
45
+
42
46
  def transition_to(new_state)
43
47
  if !locked?
44
48
  message = "Cannot transition an unlocked #{self.class.name}'s state"
@@ -58,6 +62,8 @@ module Ellington
58
62
  self.current_state = new_state
59
63
  end
60
64
 
65
+ state_history << new_state
66
+
61
67
  changed
62
68
  notify_observers Ellington::TransitionInfo.new(self, old_state, new_state)
63
69
  return_value || new_state
@@ -5,7 +5,30 @@ module Ellington
5
5
 
6
6
  def initialize
7
7
  super self.class
8
- init unless initialized?
8
+
9
+ if lines.empty?
10
+ message = "#{self.class.name} has no lines!"
11
+ raise Ellington::NoLinesDeclared.new(message)
12
+ end
13
+
14
+ if goal.empty?
15
+ message = "#{self.class.name} has no lines!"
16
+ raise Ellington::NoGoalDeclared.new(message)
17
+ end
18
+
19
+ initialize_lines
20
+ states
21
+ end
22
+
23
+ def initialize_lines
24
+ lines.each do |line|
25
+ line.route = self
26
+ line.add_observer self, :line_completed
27
+ line.stations.each do |station|
28
+ station.line = line
29
+ station.add_observer line, :station_completed
30
+ end
31
+ end
9
32
  end
10
33
 
11
34
  class << self
@@ -16,21 +39,6 @@ module Ellington
16
39
  @initialized
17
40
  end
18
41
 
19
- def init
20
- initialize_lines
21
- states
22
- @initialized = true
23
- end
24
-
25
- def initialize_lines
26
- lines.each do |line|
27
- line.add_observer self, :line_completed
28
- line.stations.each do |station|
29
- station.add_observer line, :station_completed
30
- end
31
- end
32
- end
33
-
34
42
  def board(passenger, options={})
35
43
  lines.first.board passenger, options
36
44
  end
@@ -75,7 +83,9 @@ module Ellington
75
83
  end
76
84
 
77
85
  def connect_to(line, options)
78
- connections << Ellington::Connection.new(line, options[:if])
86
+ type = options.keys.first
87
+ states = options[type]
88
+ connections << Ellington::Connection.new(line, type, states)
79
89
  end
80
90
 
81
91
  def log_passenger_attrs(*attrs)
@@ -86,7 +96,7 @@ module Ellington
86
96
  route_info = Ellington::RouteInfo.new(self, line_info)
87
97
 
88
98
  required_connections = connections.select do |connection|
89
- connection.states.satisfied?(route_info.passenger)
99
+ connection.required?(route_info.passenger)
90
100
  end
91
101
 
92
102
  if required_connections.empty?
@@ -14,7 +14,7 @@ module Ellington
14
14
  message = []
15
15
  message << "[ROUTE COMPLETED]"
16
16
  message << "[#{route.state(passenger)}]"
17
- message << "[#{station_full_name}]"
17
+ message << "[#{route.name}]"
18
18
  line.route.log_passenger_attrs.each do |attr|
19
19
  message << "[#{attr}:#{passenger.send(attr)}]"
20
20
  end
@@ -6,11 +6,11 @@ module Ellington
6
6
  class Station
7
7
  extend Forwardable
8
8
  include Observable
9
- attr_accessor :line
9
+ attr_accessor :line_class, :line
10
10
  def_delegators :line, :route
11
11
 
12
12
  def name
13
- @name ||= "#{self.class.name}::#{line.name}"
13
+ @name ||= "#{self.class.name}::#{line_class.name}"
14
14
  end
15
15
 
16
16
  def state_name(state)
@@ -55,7 +55,7 @@ module Ellington
55
55
  passenger.add_observer attendant
56
56
  engage passenger, options
57
57
  passenger.delete_observer attendant
58
- raise Ellington::AttendandDisapproves unless attendant.approve?
58
+ raise Ellington::AttendantDisapproves unless attendant.approve?
59
59
  changed
60
60
  notify_observers Ellington::StationInfo.new(self, passenger, attendant.passenger_transitions.first, options)
61
61
  end
@@ -63,15 +63,15 @@ module Ellington
63
63
  passenger
64
64
  end
65
65
 
66
- def pass(passenger)
66
+ def pass_passenger(passenger)
67
67
  passenger.transition_to passed
68
68
  end
69
69
 
70
- def fail(passenger)
70
+ def fail_passenger(passenger)
71
71
  passenger.transition_to failed
72
72
  end
73
73
 
74
- def error(passenger)
74
+ def error_passenger(passenger)
75
75
  passenger.transition_to errored
76
76
  end
77
77
 
@@ -2,17 +2,17 @@ require "delegate"
2
2
 
3
3
  module Ellington
4
4
  class StationList < SimpleDelegator
5
- attr_reader :line
5
+ attr_reader :line_class
6
6
 
7
- def initialize(line)
8
- @line = line
7
+ def initialize(line_class)
8
+ @line_class = line_class
9
9
  @inner_list = UniqueTypeArray.new
10
10
  super @inner_list
11
11
  end
12
12
 
13
13
  def push(station)
14
14
  value = inner_list << station
15
- station.line = line
15
+ station.line_class = line_class
16
16
  value
17
17
  end
18
18
  alias_method :<<, :push
@@ -13,10 +13,7 @@ module Ellington
13
13
  inner_list.push value
14
14
  end
15
15
 
16
- def <<(value)
17
- check value
18
- inner_list << value
19
- end
16
+ alias_method :<<, :push
20
17
 
21
18
  def contains_a?(klass)
22
19
  each do |entry|
@@ -1,3 +1,3 @@
1
1
  module Ellington
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
data/lib/ellington.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require "bundler"
2
2
  Bundler.require
3
- #Bundler.require :development, :test
3
+ Bundler.require :development, :test if ENV["ELLINGTON_DEV"]
4
4
 
5
5
  path = File.join(File.dirname(__FILE__), "ellington/**/*.rb")
6
6
  Dir[path].each { |file| require file }
@@ -3,11 +3,11 @@ require_relative "test_helper"
3
3
  class AttendantTest < MicroTest::Test
4
4
 
5
5
  before do
6
- @route = BasicMath.new
7
- @line = @route.lines.first
8
- @station = @line.stations.first
9
- @passenger = Ellington::Passenger.new(NumberWithHistory.new(0), @route)
10
- @passenger.current_state = @route.initial_state
6
+ route = BasicMath.new
7
+ line = route.lines.first
8
+ @station = line.stations.first
9
+ @passenger = Ellington::Passenger.new(NumberWithHistory.new(0), route)
10
+ @passenger.current_state = route.initial_state
11
11
  @passenger.lock
12
12
  @attendant = Ellington::Attendant.new(@station)
13
13
  @passenger.add_observer @attendant
@@ -2,31 +2,60 @@ require_relative "test_helper"
2
2
 
3
3
  class ConductorTest < MicroTest::Test
4
4
 
5
- #before do
6
- # route = Ellington::Route.new("Example Route", StateJacket::Catalog.new)
7
- # route.add Ellington::Line.new("A Line")
8
- # route["A Line"] << ConductorTest::Station.new
9
- # @conductor = Ellington::Conductor.new(route)
10
- #end
11
-
12
- #test "verify is abstract" do
13
- # error = nil
14
- # begin
15
- # @conductor.verify nil
16
- # rescue Ellington::NotImplementedError => e
17
- # error = e
18
- # end
19
- # assert !error.nil?
20
- #end
21
-
22
- #test "gather_passengers is abstract" do
23
- # error = nil
24
- # begin
25
- # @conductor.gather_passengers
26
- # rescue Ellington::NotImplementedError => e
27
- # error = e
28
- # end
29
- # assert !error.nil?
30
- #end
5
+ before do
6
+ @route = BasicMath.new
7
+ @conductor = Ellington::Conductor.new(@route)
8
+ @passenger = Ellington::Passenger.new(NumberWithHistory.new(0), @route)
9
+ @passenger.current_state = @route.initial_state
10
+ @passenger.lock
11
+ end
12
+
13
+ test "not conducting" do
14
+ assert !@conductor.conducting?
15
+ end
16
+
17
+ test "verify" do
18
+ assert @conductor.verify(@passenger)
19
+ end
20
+
21
+ test "gather_passengers is abstract" do
22
+ error = nil
23
+ begin
24
+ @conductor.gather_passengers
25
+ rescue Ellington::NotImplementedError => e
26
+ error = e
27
+ end
28
+ assert !error.nil?
29
+ end
30
+
31
+ test "escort" do
32
+ @conductor.escort(@passenger)
33
+ assert @passenger.current_state != @route.initial_state
34
+ end
35
+
36
+ test "escort prevented when passenger doesn't verify" do
37
+ def @conductor.verify(passenger)
38
+ false
39
+ end
40
+ @conductor.escort(@passenger)
41
+ assert @passenger.current_state == @route.initial_state
42
+ end
43
+
44
+ test "escort prevented when passenger is not locked" do
45
+ @passenger.unlock
46
+ assert @passenger.current_state == @route.initial_state
47
+ end
48
+
49
+ test "start/stop with conducting? checks" do
50
+ def @conductor.gather_passengers
51
+ sleep 0.1
52
+ []
53
+ end
54
+ @conductor.start
55
+ assert @conductor.conducting?
56
+ @conductor.stop
57
+ sleep 0.2
58
+ puts !@conductor.conducting?
59
+ end
31
60
 
32
61
  end
@@ -0,0 +1,19 @@
1
+ require_relative "test_helper"
2
+
3
+ class ConnectionTest < MicroTest::Test
4
+
5
+ before do
6
+ @route = BasicMath.new
7
+ @line = @route.lines.first
8
+ @connection = Ellington::Connection.new(@route.lines[1], :if_currently, @line.passed)
9
+ end
10
+
11
+ test "line" do
12
+ assert @connection.line == @route.lines[1]
13
+ end
14
+
15
+ test "states" do
16
+ assert @connection.states == @line.passed
17
+ end
18
+
19
+ end