lego-core 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ #
2
+ #
3
+
4
+ class Lego::Controller::ActionContext
5
+
6
+ attr_accessor :response, :env, :route
7
+
8
+ def initialize
9
+ setup_defaults
10
+ end
11
+
12
+ def options(key)
13
+ "#{self.class::ApplicationClass.current_config.options(key)}"
14
+ end
15
+
16
+ def run(route, env)
17
+ @route = route
18
+ @env = env
19
+ setup_instance_vars_from_route
20
+ evaluate_action
21
+ [@response[:code], @response[:headers], @response[:body]]
22
+ end
23
+
24
+ private
25
+
26
+ #
27
+ # setup_instance_vars_from_route extracts variables found in route[:instance_vars] thats setup by the route matchers
28
+ # and converts them to instance variables which makes them availible to the ActionContext.
29
+ #
30
+
31
+ def setup_instance_vars_from_route
32
+ @route[:instance_vars].each_key do |var|
33
+ instance_variable_set("@#{var}", @route[:instance_vars][var])
34
+ end if @route[:instance_vars]
35
+ @response[:code] = @route[:set_response_code] if @route[:set_response_code]
36
+ end
37
+
38
+ #
39
+ # evaluate_action executes route[:action_block] and appends the output to @response[:body]
40
+ #
41
+
42
+ def evaluate_action
43
+ @response[:body] << instance_eval(&@route[:action_block]) if @route[:action_block]
44
+ end
45
+
46
+ #
47
+ # setup_defaults creates a default @response object with enough data to satisfies Rack.
48
+ #
49
+
50
+ def setup_defaults
51
+ @response = {}
52
+ @response[:headers] = {'Content-Type' => 'text/html'}
53
+ @response[:code] = 200
54
+ @response[:body] = ''
55
+ end
56
+ end
@@ -0,0 +1,25 @@
1
+ module Lego::Controller::Config
2
+ extend self
3
+
4
+ def extended(mod)
5
+ mod.instance_eval do
6
+ def config
7
+ @config ||= {}.merge(Lego::Controller::Config.config)
8
+ end
9
+ end
10
+ end
11
+
12
+ def options(key)
13
+ config[key.to_s]
14
+ end
15
+
16
+ def config
17
+ @config ||= {}
18
+ end
19
+
20
+ def set(options={})
21
+ options.keys.each do |key|
22
+ config[key.to_s] = options[key]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,86 @@
1
+ #
2
+ # RouteHandler is the module that holds and handles routes for every controller class.
3
+ #
4
+
5
+ module Lego::Controller::RouteHandler
6
+
7
+ # InvalidMatcher Exception
8
+
9
+ class InvalidMatcher < Exception; end
10
+
11
+ extend self
12
+
13
+ #
14
+ # When extended we need to copy out matchers and routes to the class extending.
15
+ #
16
+
17
+ def extended(base)
18
+ base.matchers.concat(matchers)
19
+ base.routes.merge!(routes)
20
+ end
21
+
22
+ def add_route(method, options)
23
+ if method == :not_found
24
+ routes[:not_found] = options
25
+ else
26
+ routes[method] << options
27
+ end
28
+ end
29
+
30
+ def add_matcher(module_name)
31
+ raise InvalidMatcher if not validate_matcher(module_name)
32
+ matchers << module_name
33
+ end
34
+
35
+ # Getter for cached instance variable holding routes.
36
+
37
+ def routes
38
+ cached_routes
39
+ end
40
+
41
+ # Getter for instance variable holding matchers.
42
+
43
+ def matchers
44
+ cached_matchers
45
+ end
46
+
47
+ def validate_matcher(module_name)
48
+ eval(module_name.to_s).respond_to?(:match_route)
49
+ end
50
+
51
+ def run_matchers(route, env)
52
+ matchers.each do |matcher|
53
+ return true if matcher.match_route(route, env) == true
54
+ end
55
+ false
56
+ end
57
+
58
+ def match_all_routes(env)
59
+ method = extract_method_from_env(env)
60
+ routes[method].each do |route|
61
+ return route if run_matchers(route, env)
62
+ end
63
+ nil
64
+ end
65
+
66
+ private
67
+
68
+ def extract_method_from_env(env)
69
+ env["REQUEST_METHOD"].downcase.to_sym if env["REQUEST_METHOD"]
70
+ end
71
+
72
+ def cached_routes
73
+ @route_cache ||= {
74
+ :get => [],
75
+ :post => [],
76
+ :put => [],
77
+ :head => [],
78
+ :delete => [],
79
+ :not_found => nil
80
+ }
81
+ end
82
+
83
+ def cached_matchers
84
+ @matcher_cache ||= []
85
+ end
86
+ end
@@ -0,0 +1,5 @@
1
+ module Lego::Plugin
2
+ module Controller
3
+ autoload :NotFound, 'lego/plugin/controller/not_found'
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module Lego::Plugin::Controller
2
+ module NotFound
3
+ def self.register(lego)
4
+ lego.add_plugin :controller, self
5
+ end
6
+
7
+ def not_found(&block)
8
+ add_route(:not_found, { :set_response_code => 404, :action_block => block })
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,82 @@
1
+ require File.join('spec', '/spec_helper')
2
+
3
+ describe 'Full stack request' do
4
+
5
+ context 'with no routes specified' do
6
+ before do
7
+ reset_lego_base
8
+ class MyApp < Lego::Controller; end
9
+ rack_env = {
10
+ 'PATH_INFO' => '/' ,
11
+ 'REQUEST_METHOD' => 'GET'
12
+ }
13
+ @result = MyApp.call(rack_env)
14
+ end
15
+
16
+ it 'should respond with a valid 404 response' do
17
+ @result[0].should eql(404)
18
+ @result[1].should eql({"Content-Type"=>"text/html"})
19
+ @result[2].should eql('404 - Not found')
20
+ end
21
+
22
+ after do
23
+ rm_const 'MyApp'
24
+ end
25
+ end
26
+
27
+ context 'with global plugins' do
28
+ before do
29
+
30
+ reset_lego_base
31
+
32
+ module GlobalPlugin
33
+ def self.register(lego)
34
+ lego.add_plugin :view, View
35
+ lego.add_plugin :router, Matcher
36
+ lego.add_plugin :controller, Routes
37
+ end
38
+ module View
39
+ def h1(content)
40
+ "<h1>#{content}</h1>"
41
+ end
42
+ end
43
+ module Routes
44
+ def get(path, &block)
45
+ add_route(:get, {:path => path, :action_block => block})
46
+
47
+ end
48
+ end
49
+ module Matcher
50
+ def self.match_route(route, env)
51
+ (route[:path] == env['PATH_INFO']) ? true : false
52
+ end
53
+ end
54
+ end
55
+
56
+ Lego.plugin GlobalPlugin
57
+
58
+ class App1 < Lego::Controller
59
+ get '/hello' do
60
+ h1 'Hello world'
61
+ end
62
+ end
63
+
64
+ rack_env = {
65
+ 'PATH_INFO' => '/hello' ,
66
+ 'REQUEST_METHOD' => 'GET'
67
+ }
68
+ @result = App1.call(rack_env)
69
+ end
70
+
71
+ it 'should respond with with valid data' do
72
+ @result[0].should eql(200)
73
+ @result[1].should eql({"Content-Type"=>"text/html"})
74
+ @result[2].should eql('<h1>Hello world</h1>')
75
+ end
76
+
77
+ after do
78
+ rm_const 'App1', 'App2'
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,73 @@
1
+ require File.join('spec', '/spec_helper')
2
+
3
+ describe Lego::Controller::ActionContext do
4
+
5
+ context 'generates rack response from @response hash' do
6
+ before do
7
+ @instance = Lego::Controller::ActionContext.new
8
+ end
9
+
10
+ it 'should set the current message body' do
11
+ @instance.response[:body] = "Hello again"
12
+ @instance.run({},nil).should eql([ 200, {"Content-Type"=>"text/html"}, 'Hello again' ])
13
+ end
14
+
15
+ it 'should set response headers' do
16
+ @instance.response[:headers] = {'Content-Type' => 'cust/head'}
17
+ @instance.run({},nil).should eql([ 200, {"Content-Type"=>"cust/head"}, '' ])
18
+ end
19
+
20
+ it 'should set response code' do
21
+ @instance.response[:code] = 666
22
+ @instance.run({},nil).should eql([ 666, {"Content-Type"=>"text/html"}, '' ])
23
+ end
24
+ end
25
+
26
+ context '.run instance method' do
27
+ before do
28
+ @instance = Lego::Controller::ActionContext.new
29
+ @env = []
30
+ end
31
+
32
+ it 'should have a default Rack response' do
33
+ @instance.run("", @env).should eql([200, {'Content-Type' => 'text/html'} , ''])
34
+ end
35
+
36
+ it 'should evaluate :action_block in instance if exists in route' do
37
+ route = { :action_block => Proc.new{ "Hello my world!" }}
38
+ @instance.run(route, @env).should eql([200, {'Content-Type' => 'text/html'} , "Hello my world!" ])
39
+ end
40
+
41
+ it 'should convert route[:instance_vars] to instance variables' do
42
+ route = { :instance_vars => { :myvar => "This is my var" } }
43
+ @instance.run(route, @env).should eql([200, {'Content-Type' => 'text/html'} , '' ])
44
+ @instance.instance_variable_get(:@myvar).should eql("This is my var")
45
+ end
46
+
47
+ it 'should set response[:code] if set_response_route is set in route' do
48
+ route = { :set_response_code => 666 }
49
+ @instance.run(route, @env).should eql([666, {'Content-Type' => 'text/html'} , '' ])
50
+ end
51
+ end
52
+
53
+ context ".options helper method" do
54
+ before do
55
+ create_new_app "App", Lego::Controller
56
+ Lego::Controller::ActionContext::ApplicationClass = App
57
+ @instance = Lego::Controller::ActionContext.new
58
+ @env = []
59
+ end
60
+
61
+ it 'should evaluate :action_block in instance if exists in route' do
62
+ Lego::Controller.set :foo => "bar"
63
+ route = { :action_block => Proc.new{ options(:foo) }}
64
+ @instance.run(route, @env).should eql([200, {'Content-Type' => 'text/html'} , "bar" ])
65
+ end
66
+
67
+ after do
68
+ rm_const "App"
69
+ end
70
+ end
71
+
72
+ end
73
+
@@ -0,0 +1,102 @@
1
+ require File.join('spec', '/spec_helper')
2
+
3
+ describe Lego::Controller::Config do
4
+
5
+ context ".set options" do
6
+
7
+ context "on Lego::Controller" do
8
+
9
+ before do
10
+ create_new_app "App1", Lego::Controller
11
+ create_new_app "App2", Lego::Controller
12
+ Lego::Controller.set :foo => "bar"
13
+ end
14
+
15
+ it "should be accessible to Lego::Controller" do
16
+ Lego::Controller.current_config.options(:foo).should eql("bar")
17
+ end
18
+
19
+ it "should be accessible to App1" do
20
+ App1.current_config.options(:foo).should eql("bar")
21
+ end
22
+
23
+ it "should be accessible to App2" do
24
+ App2.current_config.options(:foo).should eql("bar")
25
+ end
26
+
27
+ after do
28
+ clean_config! Lego::Controller
29
+ rm_const "App1", "App2"
30
+ end
31
+ end
32
+
33
+ context "on Lego::Controller subclasses" do
34
+
35
+ before do
36
+ create_new_app "App1", Lego::Controller
37
+ create_new_app "App2", Lego::Controller
38
+ App1.set :app1 => "App1"
39
+ end
40
+
41
+ it "should be available to App1" do
42
+ App1.current_config.options(:app1).should eql("App1")
43
+ end
44
+
45
+ it "should NOT be available to App2" do
46
+ App2.current_config.options(:app1).should eql(nil)
47
+ end
48
+
49
+ it "should NOT be available to Lego::Controller" do
50
+ App2.current_config.options(:app1).should eql(nil)
51
+ end
52
+
53
+ after do
54
+ clean_config! Lego::Controller, App1, App2
55
+ rm_const "App1", "App2"
56
+ end
57
+ end
58
+ end
59
+
60
+ context ".config" do
61
+
62
+ before do
63
+ Lego::Controller.set :foo => "bar",
64
+ :baz => "quux"
65
+ end
66
+
67
+ it "should contain the newly set options" do
68
+ Lego::Controller.current_config.config.should eql({"foo"=>"bar","baz"=>"quux"})
69
+ end
70
+
71
+ after do
72
+ clean_config! Lego::Controller
73
+ end
74
+ end
75
+
76
+ context ".options key" do
77
+
78
+ before do
79
+ Lego::Controller.set :my_symbol => "my_symbol",
80
+ "my_string" => "my_string"
81
+ end
82
+
83
+ it "should allow string acces to symbol keys" do
84
+ Lego::Controller.current_config.options("my_symbol").should eql("my_symbol")
85
+ end
86
+
87
+ it "should allow symbol acces to string keys" do
88
+ Lego::Controller.current_config.options(:my_string).should eql("my_string")
89
+ end
90
+
91
+ after do
92
+ clean_config! Lego::Controller
93
+ end
94
+ end
95
+ end
96
+
97
+ def clean_config!(*consts)
98
+ consts.each do |const|
99
+ const::Config.class_eval "remove_instance_variable :@config if @config"
100
+ const::Config.class_eval "remove_instance_variable :@env if @env "
101
+ end
102
+ end