lego-core 0.0.1

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.
@@ -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