ellington 0.0.1 → 0.0.2

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/Gemfile CHANGED
@@ -1,4 +1,16 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in ellington.gemspec
4
- gemspec
3
+ gem "hero", "~> 0.1.7"
4
+ gem "state_jacket", "~> 0.0.7"
5
+
6
+ group :development, :test do
7
+ gem "yell"
8
+ gem "awesome_print"
9
+ gem "pry"
10
+ #gem "pry-nav"
11
+ gem "pry-stack_explorer"
12
+ #gem "pry-rescue"
13
+ gem "pry-exception_explorer"
14
+ gem "micro_test"
15
+ gem "micro_mock"
16
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,40 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ awesome_print (1.1.0)
5
+ binding_of_caller (0.6.9)
6
+ debug_inspector (>= 0.0.1)
7
+ coderay (1.0.8)
8
+ debug_inspector (0.0.2)
9
+ hero (0.1.7)
10
+ method_source (0.8.1)
11
+ micro_mock (1.1.0)
12
+ micro_test (0.3.5)
13
+ os
14
+ os (0.9.6)
15
+ pry (0.9.12)
16
+ coderay (~> 1.0.5)
17
+ method_source (~> 0.8)
18
+ slop (~> 3.4)
19
+ pry-exception_explorer (0.2.3)
20
+ pry-stack_explorer (>= 0.4.6)
21
+ pry-stack_explorer (0.4.8)
22
+ binding_of_caller (~> 0.6.8)
23
+ pry (~> 0.9.11)
24
+ slop (3.4.3)
25
+ state_jacket (0.0.7)
26
+ yell (1.2.3)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ awesome_print
33
+ hero (~> 0.1.7)
34
+ micro_mock
35
+ micro_test
36
+ pry
37
+ pry-exception_explorer
38
+ pry-stack_explorer
39
+ state_jacket (~> 0.0.7)
40
+ yell
data/README.md CHANGED
@@ -1,29 +1,78 @@
1
- # Ellington
1
+ # Ellington
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
+ The song was written about [New York City's A train](http://en.wikipedia.org/wiki/A_%28New_York_City_Subway_service%29).
2
4
 
3
- TODO: Write a gem description
5
+ ![Subway Tunnel](https://raw.github.com/hopsoft/ellington/master/doc/tunnel.jpg)
4
6
 
5
- ## Installation
7
+ #### Ellington is an architecture for modeling complex business processes.
6
8
 
7
- Add this line to your application's Gemfile:
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.
8
11
 
9
- gem 'ellington'
12
+ The nomenclature is taken from [New York's subway system](http://en.wikipedia.org/wiki/New_York_City_Subway).
13
+ We've found that using cohesive physical metaphors helps people reason more clearly about the complexities of software.
14
+ *The subway analogy isn't perfect but gets pretty close.*
10
15
 
11
- And then execute:
16
+ ### Important
12
17
 
13
- $ bundle
18
+ The Ellington architecture should only be applied **after** a good understanding of the problem domain has been established.
19
+ *We recommend [spiking a solution](http://en.wikipedia.org/wiki/Software_prototyping) to learn your project's requirements and then coming back to Ellington.*
14
20
 
15
- Or install it yourself as:
21
+ ## Goals
16
22
 
17
- $ gem install ellington
23
+ - Provide a nomenclature simple enough to be shared by engineering and the business team.
24
+ - Establish constraints to ensure that complex projects are easy to manage, develop, and maintain.
18
25
 
19
- ## Usage
26
+ ## Lexicon
20
27
 
21
- TODO: Write usage instructions here
28
+ - **[Conductor](https://github.com/hopsoft/ellington/wiki/Conductor)** - A supervisor responsible for gathering `passengers` and putting them on a `route`.
29
+ - **[Passenger](https://github.com/hopsoft/ellington/wiki/Passenger)** - The stateful context that will be riding the virtual subway.
30
+ - **[Route](https://github.com/hopsoft/ellington/wiki/Route)** - A collection of `lines` and their `connections`.
31
+ - **[Line](https://github.com/hopsoft/ellington/wiki/Line)** - A linear track that moves the `passenger` from point A to point B.
32
+ - **[Station](https://github.com/hopsoft/ellington/wiki/Station)** - A discreet chunk of business logic.
33
+ - **[State](https://github.com/hopsoft/ellington/wiki/State)** - A status or disposition assigned to the `passenger`.
22
34
 
23
- ## Contributing
35
+ ![Ellington Diagram](https://raw.github.com/hopsoft/ellington/master/doc/primary-terms.png)
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
+ ```
24
78
 
25
- 1. Fork it
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
data/lib/ellington.rb CHANGED
@@ -1,5 +1,7 @@
1
- require "ellington/version"
1
+ require "bundler"
2
+ Bundler.require
3
+ #Bundler.require :development, :test
4
+
5
+ path = File.join(File.dirname(__FILE__), "ellington/**/*.rb")
6
+ Dir[path].each { |file| require file }
2
7
 
3
- module Ellington
4
- # Your code goes here...
5
- end
@@ -0,0 +1,21 @@
1
+ module Ellington
2
+
3
+ class Attendant
4
+ attr_reader :station, :passenger_transitions
5
+
6
+ def initialize(station)
7
+ @station = station
8
+ @passenger_transitions = []
9
+ end
10
+
11
+ def update(transition_info)
12
+ passenger_transitions << transition_info
13
+ end
14
+
15
+ def approve?
16
+ passenger_transitions.length == 1 &&
17
+ station.states.include?(passenger_transitions.first.new_state)
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,59 @@
1
+ require "thread"
2
+
3
+ module Ellington
4
+ class Conductor
5
+ attr_reader :route, :conducting
6
+
7
+ def initialize(route)
8
+ @route = route
9
+ @conducting = false
10
+ end
11
+
12
+ def start(delay=0)
13
+ return if conducting
14
+
15
+ mutex.synchronize do
16
+ @stop = false
17
+ @conducting = true
18
+ end
19
+
20
+ thread = Thread.new do
21
+ loop do
22
+ if @stop
23
+ mutex.synchronize { @conducting = false }
24
+ break
25
+ end
26
+ gather_passengers.each do |passenger|
27
+ escort(passenger)
28
+ end
29
+ sleep delay
30
+ end
31
+ end
32
+ thread.join
33
+ end
34
+
35
+ def stop
36
+ mutex.synchronize { @stop = true }
37
+ end
38
+
39
+ def verify(passenger)
40
+ true
41
+ end
42
+
43
+ def gather_passengers
44
+ raise Ellington::NotImplementedError
45
+ end
46
+
47
+ def escort(passenger)
48
+ return unless verify(passenger) && passenger.locked?
49
+ route.lines.first.board passenger
50
+ end
51
+
52
+ private
53
+
54
+ def mutex
55
+ @mutex ||= Mutex.new
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,11 @@
1
+ module Ellington
2
+ class Connection
3
+ attr_reader :line, :states
4
+
5
+ def initialize(line, states)
6
+ @line = line
7
+ @states = states
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+ require "delegate"
2
+ module Ellington
3
+ class ConnectionList < SimpleDelegator
4
+
5
+ def initialize
6
+ @inner_list = []
7
+ super inner_list
8
+ end
9
+
10
+ def push(connection)
11
+ check connection
12
+ inner_list.push connection
13
+ end
14
+
15
+ alias_method :<<, :push
16
+
17
+ protected
18
+
19
+ attr_reader :inner_list
20
+
21
+ def check(connection)
22
+ matches = inner_list.select do |c|
23
+ c.line == connection && c.states == connection.states
24
+ end
25
+ raise Ellington::ListAlreadyContainsConnection unless matches.empty?
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,22 @@
1
+ module Ellington
2
+ class ListAlreadyContainsType < StandardError
3
+ end
4
+
5
+ class InvalidStates < StandardError
6
+ end
7
+
8
+ class AttendandDisapproves < StandardError
9
+ end
10
+
11
+ class InvalidStateTransition < StandardError
12
+ end
13
+
14
+ class NotImplementedError < StandardError
15
+ end
16
+
17
+ class StationAlreadyBelongsToLine < StandardError
18
+ end
19
+
20
+ class PassengerAlreadyAssignedToTicket < StandardError
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ module Ellington
2
+ module HasTargets
3
+
4
+ def fail_target
5
+ @non_goal_target ||= begin
6
+ state_list = states.keys - goal
7
+ state_list.reject! { |state| state.to_s =~ /\AERROR/ }
8
+ Ellington::Target.new *state_list
9
+ end
10
+ end
11
+ alias_method :failed, :fail_target
12
+
13
+ def error_target
14
+ @error_target ||= begin
15
+ Ellington::Target.new states.keys - goal - fail_target
16
+ end
17
+ end
18
+ alias_method :errored, :error_target
19
+
20
+ def state(passenger)
21
+ case
22
+ when passed.satisfied?(passenger) then "PASS"
23
+ when failed.satisfied?(passenger) then "FAIL"
24
+ when errored.satisfied?(passenger) then "ERROR"
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,95 @@
1
+ require "forwardable"
2
+ require "observer"
3
+
4
+ module Ellington
5
+ class Line
6
+ class << self
7
+ include Observable
8
+ attr_accessor :route
9
+
10
+ def stations
11
+ @stations ||= Ellington::StationList.new(self)
12
+ end
13
+
14
+ def pass_target(*states)
15
+ @goal ||= Ellington::Target.new(*states)
16
+ end
17
+ alias_method :passed, :pass_target
18
+ alias_method :goal, :pass_target
19
+
20
+ def connections
21
+ @connections ||= {}
22
+ end
23
+ end
24
+
25
+ extend Forwardable
26
+ include Observable
27
+ include HasTargets
28
+
29
+ def_delegators :"self.class",
30
+ :route,
31
+ :route=,
32
+ :stations,
33
+ :pass_target,
34
+ :passed,
35
+ :goal
36
+
37
+ def board(passenger, options={})
38
+ formula.run passenger, options
39
+ end
40
+
41
+ def name
42
+ @name ||= "#{self.class.name}::#{route.name}"
43
+ end
44
+
45
+ def formula
46
+ @formula ||= begin
47
+ Hero::Formula[name]
48
+ Hero::Formula[name].steps.clear
49
+ stations.each do |station|
50
+ Hero::Formula[name].add_step station
51
+ end
52
+ Hero::Formula[name]
53
+ end
54
+ end
55
+
56
+ def states
57
+ @states ||= begin
58
+ catalog = StateJacket::Catalog.new
59
+ stations.each_with_index do |station, index|
60
+ catalog.merge! station.states
61
+ if index < stations.length - 1
62
+ next_station = stations[index + 1]
63
+ catalog[station.passed] = next_station.states.keys
64
+ end
65
+ end
66
+ catalog.lock
67
+ catalog
68
+ end
69
+ end
70
+
71
+ def station_completed(station_info)
72
+ line_info = Ellington::LineInfo.new(self, station_info)
73
+ if line_info.station == stations.last ||
74
+ line_info.passenger.current_state == line_info.station.failed
75
+ log line_info, :station_completed => true
76
+ log line_info, :line_completed => true
77
+ changed
78
+ notify_observers line_info
79
+ else
80
+ log line_info, :station_completed => true
81
+ if line_info.passenger.current_state == line_info.station.errored
82
+ Ellington.logger.info "\n" if Ellington.logger
83
+ end
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def log(line_info, options={})
90
+ return unless Ellington.logger
91
+ Ellington.logger.info line_info.log_message(options)
92
+ end
93
+
94
+ end
95
+ end