ellington 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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