ellington 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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