circuit 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/Gemfile +34 -0
  2. data/LICENSE +20 -0
  3. data/README.md +161 -0
  4. data/Rakefile +27 -0
  5. data/config.ru +7 -0
  6. data/description.md +5 -0
  7. data/docs/COMPATIBILITY.md +14 -0
  8. data/docs/ROADMAP.md +29 -0
  9. data/lib/circuit.rb +125 -0
  10. data/lib/circuit/behavior.rb +99 -0
  11. data/lib/circuit/compatibility.rb +73 -0
  12. data/lib/circuit/middleware.rb +6 -0
  13. data/lib/circuit/middleware/rewriter.rb +43 -0
  14. data/lib/circuit/rack.rb +14 -0
  15. data/lib/circuit/rack/behavioral.rb +45 -0
  16. data/lib/circuit/rack/builder.rb +50 -0
  17. data/lib/circuit/rack/multi_site.rb +22 -0
  18. data/lib/circuit/rack/request.rb +81 -0
  19. data/lib/circuit/railtie.rb +24 -0
  20. data/lib/circuit/storage.rb +74 -0
  21. data/lib/circuit/storage/memory_model.rb +70 -0
  22. data/lib/circuit/storage/nodes.rb +56 -0
  23. data/lib/circuit/storage/nodes/memory_store.rb +63 -0
  24. data/lib/circuit/storage/nodes/model.rb +67 -0
  25. data/lib/circuit/storage/nodes/mongoid_store.rb +56 -0
  26. data/lib/circuit/storage/sites.rb +38 -0
  27. data/lib/circuit/storage/sites/memory_store.rb +53 -0
  28. data/lib/circuit/storage/sites/model.rb +29 -0
  29. data/lib/circuit/storage/sites/mongoid_store.rb +43 -0
  30. data/lib/circuit/validators.rb +74 -0
  31. data/lib/circuit/version.rb +3 -0
  32. data/spec/internal/app/behaviors/change_path.rb +7 -0
  33. data/spec/internal/app/controllers/application_controller.rb +5 -0
  34. data/spec/internal/app/helpers/application_helper.rb +2 -0
  35. data/spec/internal/config/initializers/circuit.rb +7 -0
  36. data/spec/internal/config/routes.rb +3 -0
  37. data/spec/internal/db/schema.rb +1 -0
  38. data/spec/lib/circuit/behavior_spec.rb +113 -0
  39. data/spec/lib/circuit/middleware/rewriter_spec.rb +79 -0
  40. data/spec/lib/circuit/rack/behavioral_spec.rb +60 -0
  41. data/spec/lib/circuit/rack/builder_spec.rb +125 -0
  42. data/spec/lib/circuit/rack/multi_site_spec.rb +34 -0
  43. data/spec/lib/circuit/rack/request_spec.rb +80 -0
  44. data/spec/lib/circuit/railtie_spec.rb +34 -0
  45. data/spec/lib/circuit/storage/nodes_spec.rb +62 -0
  46. data/spec/lib/circuit/storage/sites_spec.rb +60 -0
  47. data/spec/lib/circuit/storage_spec.rb +20 -0
  48. data/spec/lib/circuit/validators_spec.rb +69 -0
  49. data/spec/lib/circuit_spec.rb +139 -0
  50. data/spec/spec_helper.rb +79 -0
  51. data/spec/support/blueprints.rb +24 -0
  52. data/spec/support/matchers/be_current_time_matcher.rb +14 -0
  53. data/spec/support/matchers/extended_have_key.rb +31 -0
  54. data/spec/support/matchers/have_accessor_matcher.rb +13 -0
  55. data/spec/support/matchers/have_attribute_matcher.rb +22 -0
  56. data/spec/support/matchers/have_block_matcher.rb +14 -0
  57. data/spec/support/matchers/have_errors_on_matcher.rb +30 -0
  58. data/spec/support/matchers/have_module_matcher.rb +13 -0
  59. data/spec/support/matchers/have_reader_matcher.rb +18 -0
  60. data/spec/support/matchers/have_writer_matcher.rb +18 -0
  61. data/spec/support/matchers/set_instance_variable.rb +32 -0
  62. data/spec/support/spec_helpers/base_behaviors.rb +20 -0
  63. data/spec/support/spec_helpers/base_models.rb +64 -0
  64. data/spec/support/spec_helpers/logger_helpers.rb +58 -0
  65. data/spec/support/spec_helpers/multi_site_helper.rb +48 -0
  66. data/spec/support/spec_helpers/rack_helpers.rb +8 -0
  67. data/spec/support/spec_helpers/shared_examples/node_store.rb +87 -0
  68. data/spec/support/spec_helpers/shared_examples/site_store.rb +76 -0
  69. data/spec/support/spec_helpers/simple_machinable.rb +29 -0
  70. data/spec/support/spec_helpers/stores_cleaner.rb +46 -0
  71. data/vendor/active_support-3.2/core_ext/string/inflections.rb +53 -0
  72. data/vendor/active_support-3.2/inflector/methods.rb +65 -0
  73. data/vendor/rack-1.4/builder.rb +167 -0
  74. data/vendor/rack-1.4/urlmap.rb +96 -0
  75. metadata +238 -0
@@ -0,0 +1,48 @@
1
+ require 'active_support/concern'
2
+
3
+ module SpecHelpers
4
+ module MultiSiteHelper
5
+ extend ActiveSupport::Concern
6
+ include BaseModels
7
+
8
+ def setup_site!(site, behavior)
9
+ site.route.behavior = behavior
10
+ site.route.save!
11
+ site
12
+ end
13
+
14
+ def stub_app_with_circuit_site(my_site, middleware_klass=set_site_middleware)
15
+ Rack::Builder.app do
16
+ use middleware_klass, my_site
17
+ use Circuit::Rack::Behavioral
18
+ run Proc.new {|env| [404, {}, ["downstream #{env['PATH_INFO']}"]] }
19
+ end
20
+ end
21
+
22
+ def set_site_middleware()
23
+ (Class.new do
24
+ def initialize(app, site)
25
+ @app = app
26
+ @site = site
27
+ end
28
+
29
+ def call(env)
30
+ request = Rack::Request.new(env)
31
+ request.site = @site
32
+ @app.call(request.env)
33
+ end
34
+ end)
35
+ end
36
+
37
+ module ClassMethods
38
+
39
+ def get(*args)
40
+ before do
41
+ super(*args) if respond_to?(:super)
42
+ get *args
43
+ end
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,8 @@
1
+ module SpecHelpers
2
+ module RackHelpers
3
+ def mock_request(env={})
4
+ path = env.delete(:path) || "/foo/bar/baz"
5
+ ::Rack::Request.new(Rack::MockRequest.env_for("http://www.example.com"+path, env))
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,87 @@
1
+ require "active_support/inflector"
2
+
3
+ shared_examples "node store" do
4
+ subject { Circuit.node_store }
5
+ it { should be_instance_of(Circuit::Storage::Nodes.const_get(store.to_s.classify)) }
6
+
7
+ context "get root" do
8
+ before { root }
9
+ it { subject.get(site, "/").should == [root] }
10
+ end
11
+
12
+ context "get missing root" do
13
+ it do
14
+ expect { subject.get!(dup_site_1, "/") }.
15
+ to raise_error(Circuit::Storage::Nodes::NotFoundError, "Path not found")
16
+ end
17
+ end
18
+
19
+ context "get nodes" do
20
+ # before { great_grandchild }
21
+ it do
22
+ subject.get(site, great_grandchild.path).
23
+ should == [root, child, grandchild, great_grandchild]
24
+ end
25
+ end
26
+
27
+ context "get missing route" do
28
+ before { child }
29
+ it { subject.get(site, child.path+"/foobar").
30
+ should == [root, child] }
31
+ it { subject.get!(site, child.path+"/foobar").
32
+ should == [root, child] }
33
+ end
34
+
35
+ describe Circuit::Node do
36
+ subject { child }
37
+
38
+ context "has slug" do
39
+ it { should respond_to(:slug) }
40
+ it { subject.slug.should_not be_blank}
41
+ end
42
+
43
+ context "requires slug" do
44
+ before { subject.slug = nil }
45
+ it { subject.save.should be_false }
46
+ end
47
+
48
+ context "has behavior_klass" do
49
+ it { should respond_to(:behavior_klass) }
50
+ it { subject.behavior_klass.should_not be_blank}
51
+ end
52
+
53
+ context "requires behavior_klass" do
54
+ before { subject.behavior_klass = nil }
55
+ it { subject.save.should be_false }
56
+ end
57
+
58
+ context "behavior is constantized behavior_klass" do
59
+ it { subject.behavior.should == subject.behavior_klass.constantize }
60
+ end
61
+
62
+ context "behavior_klass is settable by behavior= with module" do
63
+ before { subject.behavior = ChangePath }
64
+ it { subject.behavior_klass.should == "ChangePath" }
65
+ end
66
+
67
+ context "behavior_klass is settable by behavior= with cru" do
68
+ before { subject.behavior = Circuit::Behavior.get("Redirect") }
69
+ it { subject.behavior_klass.should == "Redirect" }
70
+ end
71
+
72
+ context "root has a site, slug is nil" do
73
+ it { root.site.should == site }
74
+ it { root.root?.should be_true}
75
+ it { root.slug.should be_nil }
76
+ end
77
+
78
+ context "child does not have a site" do
79
+ it { subject.site.should be_nil }
80
+ it { subject.root?.should be_false }
81
+ end
82
+
83
+ context "child should build path" do
84
+ it { subject.path.should == "/#{subject.slug}" }
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,76 @@
1
+ require "active_support/inflector"
2
+
3
+ shared_examples "site store" do
4
+ subject { Circuit.site_store }
5
+ it { should be_instance_of(Circuit::Storage::Sites.const_get(store.to_s.classify)) }
6
+
7
+ context "get host" do
8
+ before { root }
9
+ it { subject.get(site.host).should eql(site) }
10
+ end
11
+
12
+ context "get alias" do
13
+ it { subject.get(site.aliases.first).should == site }
14
+ end
15
+
16
+ context "get missing host" do
17
+ it { subject.get("www.missinghost.com").should be_nil }
18
+ it do
19
+ expect { subject.get!("www.missinghost.com") }.
20
+ to raise_error(Circuit::Storage::Sites::NotFoundError, "Host not found")
21
+ end
22
+ end
23
+
24
+ context "get duplicated host" do
25
+ it do
26
+ expect { dup_site_1_dup; subject.get(dup_site_1.host) }.
27
+ to raise_error(Circuit::Storage::Sites::MultipleFoundError, "Multiple sites found")
28
+ end
29
+ end
30
+
31
+ context "get duplicated host by alias" do
32
+ it do
33
+ expect { dup_site_2_dup; subject.get(dup_site_2.host) }.
34
+ to raise_error(Circuit::Storage::Sites::MultipleFoundError, "Multiple sites found")
35
+ end
36
+ end
37
+
38
+ context "get duplicated alias" do
39
+ it do
40
+ expect { dup_site_3_dup; subject.get(dup_site_3.aliases.first) }.
41
+ to raise_error(Circuit::Storage::Sites::MultipleFoundError, "Multiple sites found")
42
+ end
43
+ end
44
+
45
+ describe Circuit::Site do
46
+ subject { site }
47
+
48
+ context "has aliases" do
49
+ it { should respond_to(:aliases) }
50
+ it { subject.aliases.should_not be_blank }
51
+ it { subject.aliases.should be_a(Array) }
52
+ it { subject.aliases.length.should == 2 }
53
+ end
54
+
55
+ context "allows blank aliases" do
56
+ before { subject.aliases = [] }
57
+ it { subject.aliases.should be_empty }
58
+ it { subject.save.should be_true }
59
+ end
60
+
61
+ context "has host" do
62
+ it { should respond_to(:host) }
63
+ it { subject.host.should_not be_blank}
64
+ end
65
+
66
+ context "requires hosts" do
67
+ before { subject.host = nil }
68
+ it { subject.save.should be_false }
69
+ end
70
+
71
+ context "has route" do
72
+ before { root }
73
+ it { subject.route.should == root }
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,29 @@
1
+ require 'active_support/concern'
2
+
3
+ module SimpleMachinable
4
+ extend ActiveSupport::Concern
5
+
6
+ class SimpleBlueprint < Machinist::Blueprint
7
+ def make!(attributes={})
8
+ make(attributes).tap(&:save!)
9
+ end
10
+ end
11
+
12
+ included do
13
+ extend Machinist::Machinable
14
+ # ClassMethods is included *before* the included block, so blueprint_class
15
+ # would be overriden by the extend if it were in the ClassMethods module
16
+ class_eval do
17
+ def self.blueprint_class
18
+ SimpleBlueprint
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.ensure_machinable(*klasses)
24
+ klasses.each do |klass|
25
+ next if klass.respond_to?(:blueprint)
26
+ klass.send(:include, SimpleMachinable)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,46 @@
1
+ require 'active_support/concern'
2
+
3
+ module SpecHelpers
4
+ module StoresCleaner
5
+ extend ActiveSupport::Concern
6
+ include CircuitBlueprints
7
+
8
+ included do
9
+ around :each do |example|
10
+ orig_site_store = Circuit::Storage::Sites.instance_variable_get(:@instance)
11
+ orig_node_store = Circuit::Storage::Nodes.instance_variable_get(:@instance)
12
+ clear_storage
13
+
14
+ if @storage
15
+ Circuit.set_site_store @storage
16
+ Circuit.set_node_store @storage
17
+ ensure_blueprints
18
+ end
19
+
20
+ example.run
21
+
22
+ clear_storage
23
+ silence_warnings do
24
+ Circuit.set_site_store orig_site_store
25
+ Circuit.set_node_store orig_node_store
26
+ end
27
+ ensure_blueprints
28
+ end
29
+ end
30
+
31
+ module ClassMethods
32
+ def use_storage(val)
33
+ before(:all) { @storage = val }
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def clear_storage
40
+ Circuit::Storage::Sites.instance_variable_set(:@instance, nil)
41
+ Circuit::Storage::Nodes.instance_variable_set(:@instance, nil)
42
+ Circuit.send(:remove_const, :Site) rescue NameError
43
+ Circuit.send(:remove_const, :Node) rescue NameError
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,53 @@
1
+ ## Copied from Rails 3.2.6 on 19 June 2012.
2
+ ## see http://github.com/rails/rails/blob/v3.2.6/activesupport/lib/active_support/core_ext/string/inflections.rb
3
+
4
+ # Copyright (c) 2005-2011 David Heinemeier Hansson
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ # String inflections define new methods on the String class to transform names for different purposes.
26
+ # For instance, you can figure out the name of a table from the name of a class.
27
+ #
28
+ # "ScaleScore".tableize # => "scale_scores"
29
+ #
30
+ class String
31
+ # Removes the module part from the constant expression in the string.
32
+ #
33
+ # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
34
+ # "Inflections".demodulize # => "Inflections"
35
+ #
36
+ # See also +deconstantize+.
37
+ def demodulize
38
+ ActiveSupport::Inflector.demodulize(self)
39
+ end
40
+
41
+ # Removes the rightmost segment from the constant expression in the string.
42
+ #
43
+ # "Net::HTTP".deconstantize # => "Net"
44
+ # "::Net::HTTP".deconstantize # => "::Net"
45
+ # "String".deconstantize # => ""
46
+ # "::String".deconstantize # => ""
47
+ # "".deconstantize # => ""
48
+ #
49
+ # See also +demodulize+.
50
+ def deconstantize
51
+ ActiveSupport::Inflector.deconstantize(self)
52
+ end
53
+ end
@@ -0,0 +1,65 @@
1
+ ## Copied from Rails 3.2.6 on 19 June 2012.
2
+ ## see http://github.com/rails/rails/blob/v3.2.6/activesupport/lib/active_support/inflector/methods.rb
3
+
4
+ # Copyright (c) 2005-2011 David Heinemeier Hansson
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ module ActiveSupport
26
+ # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
27
+ # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
28
+ # in inflections.rb.
29
+ #
30
+ # The Rails core team has stated patches for the inflections library will not be accepted
31
+ # in order to avoid breaking legacy applications which may be relying on errant inflections.
32
+ # If you discover an incorrect inflection and require it for your application, you'll need
33
+ # to correct it yourself (explained below).
34
+ module Inflector
35
+ extend self
36
+
37
+ # Removes the module part from the expression in the string:
38
+ #
39
+ # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
40
+ # "Inflections".demodulize # => "Inflections"
41
+ #
42
+ # See also +deconstantize+.
43
+ def demodulize(path)
44
+ path = path.to_s
45
+ if i = path.rindex('::')
46
+ path[(i+2)..-1]
47
+ else
48
+ path
49
+ end
50
+ end
51
+
52
+ # Removes the rightmost segment from the constant expression in the string:
53
+ #
54
+ # "Net::HTTP".deconstantize # => "Net"
55
+ # "::Net::HTTP".deconstantize # => "::Net"
56
+ # "String".deconstantize # => ""
57
+ # "::String".deconstantize # => ""
58
+ # "".deconstantize # => ""
59
+ #
60
+ # See also +demodulize+.
61
+ def deconstantize(path)
62
+ path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,167 @@
1
+ ## Copied from Rack 1.4.1 on 19 June 2012.
2
+ ## see http://github.com/rack/rack/blob/1.4.1/lib/rack/builder.rb
3
+
4
+ # Copyright (c) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen>
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to
8
+ # deal in the Software without restriction, including without limitation the
9
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
+ # sell copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19
+ # THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module Rack
24
+ # Rack::Builder implements a small DSL to iteratively construct Rack
25
+ # applications.
26
+ #
27
+ # Example:
28
+ #
29
+ # require 'rack/lobster'
30
+ # app = Rack::Builder.new do
31
+ # use Rack::CommonLogger
32
+ # use Rack::ShowExceptions
33
+ # map "/lobster" do
34
+ # use Rack::Lint
35
+ # run Rack::Lobster.new
36
+ # end
37
+ # end
38
+ #
39
+ # run app
40
+ #
41
+ # Or
42
+ #
43
+ # app = Rack::Builder.app do
44
+ # use Rack::CommonLogger
45
+ # run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
46
+ # end
47
+ #
48
+ # run app
49
+ #
50
+ # +use+ adds a middleware to the stack, +run+ dispatches to an application.
51
+ # You can use +map+ to construct a Rack::URLMap in a convenient way.
52
+
53
+ class Builder
54
+ def self.parse_file(config, opts = Server::Options.new)
55
+ options = {}
56
+ if config =~ /\.ru$/
57
+ cfgfile = ::File.read(config)
58
+ if cfgfile[/^#\\(.*)/] && opts
59
+ options = opts.parse! $1.split(/\s+/)
60
+ end
61
+ cfgfile.sub!(/^__END__\n.*\Z/m, '')
62
+ app = eval "Rack::Builder.new {\n" + cfgfile + "\n}.to_app",
63
+ TOPLEVEL_BINDING, config
64
+ else
65
+ require config
66
+ app = Object.const_get(::File.basename(config, '.rb').capitalize)
67
+ end
68
+ return app, options
69
+ end
70
+
71
+ def initialize(default_app = nil,&block)
72
+ @use, @map, @run = [], nil, default_app
73
+ instance_eval(&block) if block_given?
74
+ end
75
+
76
+ def self.app(default_app = nil, &block)
77
+ self.new(default_app, &block).to_app
78
+ end
79
+
80
+ # Specifies a middleware to use in a stack.
81
+ #
82
+ # class Middleware
83
+ # def initialize(app)
84
+ # @app = app
85
+ # end
86
+ #
87
+ # def call(env)
88
+ # env["rack.some_header"] = "setting an example"
89
+ # @app.call(env)
90
+ # end
91
+ # end
92
+ #
93
+ # use Middleware
94
+ # run lambda { |env| [200, { "Content-Type => "text/plain" }, ["OK"]] }
95
+ #
96
+ # All requests through to this application will first be processed by the middleware class.
97
+ # The +call+ method in this example sets an additional environment key which then can be
98
+ # referenced in the application if required.
99
+ def use(middleware, *args, &block)
100
+ if @map
101
+ mapping, @map = @map, nil
102
+ @use << proc { |app| generate_map app, mapping }
103
+ end
104
+ @use << proc { |app| middleware.new(app, *args, &block) }
105
+ end
106
+
107
+ # Takes an argument that is an object that responds to #call and returns a Rack response.
108
+ # The simplest form of this is a lambda object:
109
+ #
110
+ # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
111
+ #
112
+ # However this could also be a class:
113
+ #
114
+ # class Heartbeat
115
+ # def self.call(env)
116
+ # [200, { "Content-Type" => "text/plain" }, ["OK"]]
117
+ # end
118
+ # end
119
+ #
120
+ # run Heartbeat
121
+ def run(app)
122
+ @run = app
123
+ end
124
+
125
+ # Creates a route within the application.
126
+ #
127
+ # Rack::Builder.app do
128
+ # map '/' do
129
+ # run Heartbeat
130
+ # end
131
+ # end
132
+ #
133
+ # The +use+ method can also be used here to specify middleware to run under a specific path:
134
+ #
135
+ # Rack::Builder.app do
136
+ # map '/' do
137
+ # use Middleware
138
+ # run Heartbeat
139
+ # end
140
+ # end
141
+ #
142
+ # This example includes a piece of middleware which will run before requests hit +Heartbeat+.
143
+ #
144
+ def map(path, &block)
145
+ @map ||= {}
146
+ @map[path] = block
147
+ end
148
+
149
+ def to_app
150
+ app = @map ? generate_map(@run, @map) : @run
151
+ fail "missing run or map statement" unless app
152
+ @use.reverse.inject(app) { |a,e| e[a] }
153
+ end
154
+
155
+ def call(env)
156
+ to_app.call(env)
157
+ end
158
+
159
+ private
160
+
161
+ def generate_map(default_app, mapping)
162
+ mapped = default_app ? {'/' => default_app} : {}
163
+ mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b) }
164
+ URLMap.new(mapped)
165
+ end
166
+ end
167
+ end