rory 0.3.8 → 0.3.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,14 +1,29 @@
1
- require 'erb'
1
+ require_relative 'renderer'
2
2
 
3
3
  module Rory
4
4
  # Interface for Controller class. Subclass this to create controllers
5
5
  # with actions that will be called by the Dispatcher when a route matches.
6
6
  class Controller
7
- def initialize(request, context = nil)
7
+ attr_accessor :locals
8
+
9
+ def initialize(request, routing, app = nil)
8
10
  @request = request
9
- @route = request[:route]
11
+ @dispatcher = routing[:dispatcher]
12
+ @route = routing[:route]
10
13
  @params = request.params
11
- @context = context
14
+ @app = app
15
+ @locals = {}
16
+ end
17
+
18
+ def expose(hsh)
19
+ locals.merge!(hsh)
20
+ end
21
+
22
+ def params
23
+ @converted_params ||= @params.inject({}) { |memo, (key, value)|
24
+ memo[key.to_sym] = memo[key.to_s] = value
25
+ memo
26
+ }
12
27
  end
13
28
 
14
29
  def route_template
@@ -19,33 +34,45 @@ module Rory
19
34
  nil
20
35
  end
21
36
 
22
- def render(template, opts = {})
23
- opts = { :layout => layout }.merge(opts)
24
- file = view_path(template)
25
- output = ERB.new(File.read(file)).result(binding)
26
- if layout = opts[:layout]
27
- output = render(File.join('layouts', layout.to_s), { :layout => false }) { output }
28
- end
29
- @body = output
37
+ def default_renderer_options
38
+ {
39
+ :layout => layout,
40
+ :locals => locals,
41
+ :app => @app,
42
+ :base_url => @request.script_name
43
+ }
30
44
  end
31
45
 
32
- def view_path(template)
33
- root = @context ? @context.root : Rory.root
34
- File.expand_path(File.join('views', "#{template}.html.erb"), root)
46
+ def render(template_name, opts = {})
47
+ opts = default_renderer_options.merge(opts)
48
+ renderer = Rory::Renderer.new(template_name, opts)
49
+ @body = renderer.render
35
50
  end
36
51
 
37
52
  def redirect(path)
38
- @response = @request[:dispatcher].redirect(path)
53
+ @response = @dispatcher.redirect(path)
39
54
  end
40
55
 
41
56
  def render_not_found
42
- @response = @request[:dispatcher].render_not_found
57
+ @response = @dispatcher.render_not_found
58
+ end
59
+
60
+ # This method is called before the action method.
61
+ def before_action
62
+ #noop
63
+ end
64
+
65
+ # This method is called after the action method.
66
+ def after_action
67
+ #noop
43
68
  end
44
69
 
45
70
  def present
46
71
  # if a method exists on the controller for the requested action, call it.
47
72
  action = @route[:action]
73
+ before_action
48
74
  self.send(action) if self.respond_to?(action)
75
+ after_action
49
76
 
50
77
  if @response
51
78
  # that method may have resulted in a response already being generated
@@ -3,13 +3,14 @@ module Rory
3
3
  # appropriate controller, after examining the routes.
4
4
  class Dispatcher
5
5
  attr_reader :request
6
- def initialize(rack_request, context = nil)
6
+ def initialize(rack_request, app = nil)
7
7
  @request = rack_request
8
- @context = context
8
+ @routing = {}
9
+ @app = app
9
10
  end
10
11
 
11
12
  def route
12
- @request[:route] ||= get_route
13
+ @routing[:route] ||= get_route
13
14
  end
14
15
 
15
16
  def dispatch
@@ -45,9 +46,8 @@ module Rory
45
46
 
46
47
  def controller
47
48
  if klass = controller_class
48
- request_for_delivery = @request.dup
49
- request_for_delivery[:dispatcher] = self
50
- klass.new(request_for_delivery, @context)
49
+ @routing.merge!(:dispatcher => self)
50
+ klass.new(request, @routing, @app)
51
51
  end
52
52
  end
53
53
 
@@ -77,7 +77,7 @@ module Rory
77
77
  end
78
78
 
79
79
  def route_map
80
- @context ? @context.routes : []
80
+ @app ? @app.routes : []
81
81
  end
82
82
  end
83
83
  end
@@ -0,0 +1,43 @@
1
+ require 'erb'
2
+ require_relative 'renderer/context'
3
+
4
+ module Rory
5
+ class Renderer
6
+ def initialize(template_name, options = {})
7
+ @template_name = template_name
8
+ @options = options
9
+ @app = options[:app]
10
+ end
11
+
12
+ def render(&block)
13
+ erb = ERB.new(view_template)
14
+ output = erb.result(view_binding(&block))
15
+ if layout_path
16
+ layout_renderer = self.class.new(layout_path, @options.merge(:layout => false))
17
+ output = layout_renderer.render { output }
18
+ end
19
+ output
20
+ end
21
+
22
+ def view_binding
23
+ render_context = Context.new(@options)
24
+ render_context.get_binding { |*args|
25
+ yield(args) if block_given?
26
+ }
27
+ end
28
+
29
+ def view_template
30
+ File.read(view_path)
31
+ end
32
+
33
+ def layout_path
34
+ return nil unless @options[:layout]
35
+ File.join('layouts', @options[:layout].to_s)
36
+ end
37
+
38
+ def view_path
39
+ root = @app ? @app.root : Rory.root
40
+ File.expand_path(File.join('views', "#{@template_name}.html.erb"), root)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ module Rory
2
+ class Renderer
3
+ class Context
4
+ attr_reader :base_url
5
+
6
+ def initialize(options = {})
7
+ (options[:locals] || {}).each do |key, value|
8
+ singleton_class.send(:define_method, key) { value }
9
+ end
10
+ @app = options[:app]
11
+ @base_url = options[:base_url]
12
+ end
13
+
14
+ def get_binding
15
+ binding
16
+ end
17
+
18
+ def render(template_name, opts = {})
19
+ opts = { :layout => false, :app => @app }.merge(opts)
20
+ renderer = Rory::Renderer.new(template_name, opts)
21
+ renderer.render
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,3 +1,3 @@
1
1
  module Rory
2
- VERSION = '0.3.8'
2
+ VERSION = '0.3.9'
3
3
  end
@@ -1,5 +1,6 @@
1
1
  class ForRealsController < Rory::Controller
2
2
  def srsly
3
- @gibbit = @params[:parbles]
3
+ expose :gibbit => @params[:parbles]
4
+ expose :but_when => 'again'
4
5
  end
5
6
  end
@@ -1,6 +1,6 @@
1
1
  module Goose
2
2
  class LumpiesController
3
- def initialize(args, context)
3
+ def initialize(args, routing, context)
4
4
  @args = args
5
5
  end
6
6
 
@@ -1,7 +1,7 @@
1
1
  module Goose
2
2
  module Wombat
3
3
  class RabbitsController
4
- def initialize(args, context)
4
+ def initialize(args, routing, context)
5
5
  @args = args
6
6
  end
7
7
 
@@ -1,5 +1,5 @@
1
1
  class StubController
2
- def initialize(args, context)
2
+ def initialize(args, routing, context)
3
3
  @args = args
4
4
  end
5
5
 
@@ -0,0 +1 @@
1
+ But it's <%= when_is_it %> enough!
@@ -1 +1 @@
1
- You've done it again, <%= @gibbit %>!
1
+ You've done it <%= but_when %>, <%= gibbit %>!
@@ -0,0 +1 @@
1
+ You came from <%= base_url %>.
@@ -0,0 +1 @@
1
+ Don't Say <%= render 'test/nested' %>
@@ -0,0 +1 @@
1
+ A Bad <%= render 'test/dynamic', :locals => { :word => 'Poop' } %>!
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  describe Rory::Application do
4
2
  describe ".configure" do
5
3
  it 'yields the given block to self' do
@@ -0,0 +1,125 @@
1
+ describe Rory::Controller do
2
+ before :each do
3
+ @routing = {
4
+ :route => {
5
+ :controller => 'test',
6
+ :action => 'letsgo'
7
+ }
8
+ }
9
+
10
+ @request = double('Rack::Request', {
11
+ :params => { 'violet' => 'invisibility', :dash => 'superspeed' },
12
+ :script_name => 'script_root'
13
+ })
14
+ end
15
+
16
+ describe '#layout' do
17
+ it 'defaults to nil' do
18
+ controller = Rory::Controller.new(@request, @routing)
19
+ controller.layout.should be_nil
20
+ end
21
+ end
22
+
23
+ describe '#params' do
24
+ it 'returns params from request, converted for indifferent key access' do
25
+ controller = Rory::Controller.new(@request, @routing)
26
+ expect(controller.params).to eq({
27
+ 'violet' => 'invisibility',
28
+ 'dash' => 'superspeed',
29
+ :violet => 'invisibility',
30
+ :dash => 'superspeed'
31
+ })
32
+ end
33
+ end
34
+
35
+ describe "#render" do
36
+ it "returns renderer output" do
37
+ controller = Rory::Controller.new(@request, @routing)
38
+ allow(Rory::Renderer).to receive(:new).
39
+ with('not/real', controller.default_renderer_options).
40
+ and_return(double('Renderer', :render => 'Here ya go'))
41
+ controller.render('not/real').should == 'Here ya go'
42
+ end
43
+
44
+ it "passes layout, exposed locals, and app to renderer" do
45
+ controller = Rory::Controller.new(@request, @routing, :scooby)
46
+ controller.expose(:a => 1)
47
+ allow(controller).to receive(:layout).and_return('pretend')
48
+ renderer_options = {
49
+ :layout => 'pretend',
50
+ :locals => { :a => 1 },
51
+ :app => :scooby,
52
+ :base_url => 'script_root'
53
+ }
54
+ allow(Rory::Renderer).to receive(:new).
55
+ with('also/fake', renderer_options).
56
+ and_return(double('Renderer', :render => 'Scamazing!'))
57
+ controller.render('also/fake').should == 'Scamazing!'
58
+ end
59
+ end
60
+
61
+ describe "#redirect" do
62
+ it "delegates to dispatcher from request" do
63
+ @routing[:dispatcher] = dispatcher = double
64
+ dispatcher.should_receive(:redirect).with(:whatever)
65
+ controller = Rory::Controller.new(@request, @routing)
66
+ controller.redirect(:whatever)
67
+ end
68
+ end
69
+
70
+ describe "#render_not_found" do
71
+ it "delegates to dispatcher from request" do
72
+ @routing[:dispatcher] = dispatcher = double
73
+ dispatcher.should_receive(:render_not_found)
74
+ controller = Rory::Controller.new(@request, @routing)
75
+ controller.render_not_found
76
+ end
77
+ end
78
+
79
+ describe "#present" do
80
+ it "calls filters and action from route if exists on controller" do
81
+ controller = Rory::Controller.new(@request, @routing)
82
+ expect(controller).to receive('before_action').ordered
83
+ expect(controller).to receive('letsgo').ordered
84
+ expect(controller).to receive('after_action').ordered
85
+ expect(controller).to receive('render').ordered
86
+ controller.present
87
+ end
88
+
89
+ it "doesn't try to call action from route if nonexistent on controller" do
90
+ controller = Rory::Controller.new(@request, @routing)
91
+ allow(controller).to receive(:respond_to?).with('letsgo').and_return(false)
92
+ expect(controller).to receive('before_action').ordered
93
+ expect(controller).to receive('letsgo').never
94
+ expect(controller).to receive('after_action').ordered
95
+ expect(controller).to receive('render').ordered
96
+ controller.present
97
+ end
98
+
99
+ it "just returns a response if @response exists" do
100
+ controller = Rory::Controller.new(@request, @routing)
101
+ controller.instance_variable_set(:@response, 'Forced response')
102
+ controller.present.should == 'Forced response'
103
+ end
104
+
105
+ it "renders and returns the default template as a rack response" do
106
+ controller = Rory::Controller.new(@request, @routing)
107
+ controller.present.should == [
108
+ 200,
109
+ {'Content-type' => 'text/html', 'charset' => 'UTF-8'},
110
+ ["Let's go content"]
111
+ ]
112
+ end
113
+
114
+ it "returns previously set @body as a rack response" do
115
+ controller = Rory::Controller.new(@request, @routing)
116
+ controller.instance_variable_set(:@body, 'Forced body')
117
+ controller.should_receive(:render).never
118
+ controller.present.should == [
119
+ 200,
120
+ {'Content-type' => 'text/html', 'charset' => 'UTF-8'},
121
+ ["Forced body"]
122
+ ]
123
+ end
124
+ end
125
+ end
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  describe Rory::Dispatcher do
4
2
  describe "#redirect" do
5
3
  it "redirects to given path if path has scheme" do
@@ -35,31 +33,29 @@ describe Rory::Dispatcher do
35
33
  end
36
34
 
37
35
  it "instantiates a controller with the parsed request and calls present" do
38
- request[:route] = { :controller => 'stub' }
36
+ allow(dispatcher).to receive(:get_route).and_return({ :controller => 'stub' })
39
37
  dispatcher.dispatch.should == {
40
38
  :whatever => :yay,
41
- :route => request[:route],
42
- :dispatcher => dispatcher,
43
39
  :present_called => true # see StubController in /spec/fixture_app
44
40
  }
45
41
  end
46
42
 
47
43
  it "dispatches properly to a scoped controller" do
48
- request[:route] = { :controller => 'lumpies', :module => 'goose' }
44
+ allow(dispatcher).to receive(:get_route).and_return({
45
+ :controller => 'lumpies', :module => 'goose'
46
+ })
49
47
  dispatcher.dispatch.should == {
50
48
  :whatever => :yay,
51
- :route => request[:route],
52
- :dispatcher => dispatcher,
53
49
  :in_scoped_controller => true # see Goose::LumpiesController in /spec/fixture_app
54
50
  }
55
51
  end
56
52
 
57
53
  it "dispatches properly to a nested scoped controller" do
58
- request[:route] = { :controller => 'rabbits', :module => 'goose/wombat' }
54
+ allow(dispatcher).to receive(:get_route).and_return({
55
+ :controller => 'rabbits', :module => 'goose/wombat'
56
+ })
59
57
  dispatcher.dispatch.should == {
60
58
  :whatever => :yay,
61
- :route => request[:route],
62
- :dispatcher => dispatcher,
63
59
  :in_scoped_controller => true # see Goose::Wombat::RabbitsController in /spec/fixture_app
64
60
  }
65
61
  end
@@ -73,7 +69,7 @@ describe Rory::Dispatcher do
73
69
  end
74
70
 
75
71
  it "returns route from request if already set" do
76
- @request[:route] = 'snaky pigeons'
72
+ @dispatcher.instance_variable_set(:@routing, { :route => 'snaky pigeons' })
77
73
  @dispatcher.route.should == 'snaky pigeons'
78
74
  end
79
75
 
@@ -0,0 +1,47 @@
1
+ describe Rory::Renderer do
2
+ describe "#render" do
3
+ it "returns text of template" do
4
+ renderer = Rory::Renderer.new('test/static')
5
+ renderer.render.should == 'Static content'
6
+ end
7
+
8
+ it "returns text of template in given layout" do
9
+ controller = Rory::Renderer.new('test/static', :layout => 'surround')
10
+ controller.render.should == 'Surrounding Static content is fun'
11
+ end
12
+
13
+ it "handles symbolized layout name" do
14
+ controller = Rory::Renderer.new('test/static', :layout => :surround)
15
+ controller.render.should == 'Surrounding Static content is fun'
16
+ end
17
+
18
+ it "exposes locals to template" do
19
+ controller = Rory::Renderer.new('test/dynamic', :locals => { :word => 'hockey' })
20
+ controller.render.should == 'Word: hockey'
21
+ end
22
+
23
+ it "can render nested templates" do
24
+ controller = Rory::Renderer.new('test/double_nested', :locals => { :word => 'hockey' })
25
+ controller.render.should ==
26
+ "Don't Say A Bad Word: Poop!"
27
+ end
28
+
29
+ it "exposes base_url to template" do
30
+ controller = Rory::Renderer.new('test/a_link', :base_url => 'spoo')
31
+ controller.render.should == 'You came from spoo.'
32
+ end
33
+ end
34
+
35
+ describe '#view_path' do
36
+ it 'returns path to template from app root' do
37
+ fake_app = double('Application', :root => 'marbles')
38
+ renderer = Rory::Renderer.new('goose', :app => fake_app)
39
+ renderer.view_path.should == File.expand_path(File.join('views', 'goose.html.erb'), 'marbles')
40
+ end
41
+
42
+ it 'uses Rory.root if no app specified' do
43
+ renderer = Rory::Renderer.new('goose')
44
+ renderer.view_path.should == File.join(Rory.root, 'views', 'goose.html.erb')
45
+ end
46
+ end
47
+ end
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  describe Rory::Support do
4
2
  describe ".camelize" do
5
3
  it "camelizes given snake-case string" do
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  describe Rory do
4
2
  describe '.application' do
5
3
  it 'is by default set to the Rory::Application instance' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.8
4
+ version: 0.3.9
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-03-01 00:00:00.000000000 Z
12
+ date: 2014-03-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -192,6 +192,8 @@ files:
192
192
  - lib/rory/application.rb
193
193
  - lib/rory/controller.rb
194
194
  - lib/rory/dispatcher.rb
195
+ - lib/rory/renderer/context.rb
196
+ - lib/rory/renderer.rb
195
197
  - lib/rory/route_mapper.rb
196
198
  - lib/rory/support.rb
197
199
  - lib/rory/tasks.rb
@@ -199,24 +201,29 @@ files:
199
201
  - lib/rory.rb
200
202
  - lib/tasks/db.rake
201
203
  - lib/tasks/rory.rake
202
- - spec/application_spec.rb
203
- - spec/controller_spec.rb
204
- - spec/dispatcher_spec.rb
205
204
  - spec/fixture_app/config/application.rb
206
205
  - spec/fixture_app/config/routes.rb
207
206
  - spec/fixture_app/controllers/for_reals_controller.rb
208
207
  - spec/fixture_app/controllers/goose/lumpies_controller.rb
209
208
  - spec/fixture_app/controllers/goose/wombat/rabbits_controller.rb
210
209
  - spec/fixture_app/controllers/stub_controller.rb
210
+ - spec/fixture_app/views/for_reals/but_wait.html.erb
211
211
  - spec/fixture_app/views/for_reals/srsly.html.erb
212
212
  - spec/fixture_app/views/layouts/surround.html.erb
213
+ - spec/fixture_app/views/test/a_link.html.erb
214
+ - spec/fixture_app/views/test/double_nested.html.erb
213
215
  - spec/fixture_app/views/test/dynamic.html.erb
214
216
  - spec/fixture_app/views/test/letsgo.html.erb
217
+ - spec/fixture_app/views/test/nested.html.erb
215
218
  - spec/fixture_app/views/test/static.html.erb
219
+ - spec/lib/rory/application_spec.rb
220
+ - spec/lib/rory/controller_spec.rb
221
+ - spec/lib/rory/dispatcher_spec.rb
222
+ - spec/lib/rory/renderer_spec.rb
223
+ - spec/lib/rory/support_spec.rb
224
+ - spec/lib/rory_spec.rb
216
225
  - spec/requests/controller_spec.rb
217
- - spec/rory_spec.rb
218
226
  - spec/spec_helper.rb
219
- - spec/support_spec.rb
220
227
  - LICENSE.txt
221
228
  - Rakefile
222
229
  - README.rdoc
@@ -1,125 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Rory::Controller do
4
- before :each do
5
- @request = {
6
- :route => {
7
- :controller => 'test',
8
- :action => 'letsgo'
9
- }
10
- }
11
-
12
- @request.stub(:params => {})
13
- end
14
-
15
- describe '#layout' do
16
- it 'defaults to nil' do
17
- controller = Rory::Controller.new(@request)
18
- controller.layout.should be_nil
19
- end
20
- end
21
-
22
- describe "#render" do
23
- it "returns text of template" do
24
- controller = Rory::Controller.new(@request)
25
- controller.render('test/static').should == 'Static content'
26
- end
27
-
28
- it "returns text of template in controller's layout" do
29
- controller = Rory::Controller.new(@request)
30
- controller.stub(:layout => 'surround')
31
- controller.render('test/static').should == 'Surrounding Static content is fun'
32
- end
33
-
34
- it "handles symbolized layout name" do
35
- controller = Rory::Controller.new(@request)
36
- controller.stub(:layout => :surround)
37
- controller.render('test/static').should == 'Surrounding Static content is fun'
38
- end
39
-
40
- it "returns text of template in given layout from options" do
41
- controller = Rory::Controller.new(@request)
42
- controller.render('test/static', :layout => 'surround').should == 'Surrounding Static content is fun'
43
- end
44
-
45
- it "evaluates ERB in controller's context" do
46
- controller = Rory::Controller.new(@request)
47
- controller.stub(:word).and_return('hockey')
48
- controller.render('test/dynamic').should == 'Word: hockey'
49
- end
50
- end
51
-
52
- describe "#redirect" do
53
- it "delegates to dispatcher from request" do
54
- @request[:dispatcher] = dispatcher = double
55
- dispatcher.should_receive(:redirect).with(:whatever)
56
- controller = Rory::Controller.new(@request)
57
- controller.redirect(:whatever)
58
- end
59
- end
60
-
61
- describe "#render_not_found" do
62
- it "delegates to dispatcher from request" do
63
- @request[:dispatcher] = dispatcher = double
64
- dispatcher.should_receive(:render_not_found)
65
- controller = Rory::Controller.new(@request)
66
- controller.render_not_found
67
- end
68
- end
69
-
70
- describe "#present" do
71
- it "calls action from route if exists on controller" do
72
- controller = Rory::Controller.new(@request)
73
- controller.stub(:render)
74
- controller.should_receive('letsgo')
75
- controller.present
76
- end
77
-
78
- it "doesn't try to call action from route if nonexistent on controller" do
79
- controller = Rory::Controller.new(@request)
80
- controller.stub(:render)
81
- controller.stub(:respond_to?).with('letsgo').and_return(false)
82
- controller.should_receive('letsgo').never
83
- controller.present
84
- end
85
-
86
- it "just returns a response if @response exists" do
87
- controller = Rory::Controller.new(@request)
88
- controller.instance_variable_set(:@response, 'Forced response')
89
- controller.present.should == 'Forced response'
90
- end
91
-
92
- it "renders and returns the default template as a rack response" do
93
- controller = Rory::Controller.new(@request)
94
- controller.present.should == [
95
- 200,
96
- {'Content-type' => 'text/html', 'charset' => 'UTF-8'},
97
- ["Let's go content"]
98
- ]
99
- end
100
-
101
- it "returns previously set @body as a rack response" do
102
- controller = Rory::Controller.new(@request)
103
- controller.instance_variable_set(:@body, 'Forced body')
104
- controller.should_receive(:render).never
105
- controller.present.should == [
106
- 200,
107
- {'Content-type' => 'text/html', 'charset' => 'UTF-8'},
108
- ["Forced body"]
109
- ]
110
- end
111
- end
112
-
113
- describe '#view_path' do
114
- it 'returns path to template from context root' do
115
- fake_context = double('Context', :root => 'marbles')
116
- controller = Rory::Controller.new(@request, fake_context)
117
- controller.view_path('goose').should == File.expand_path(File.join('views', 'goose.html.erb'), 'marbles')
118
- end
119
-
120
- it 'uses Rory.root if no context' do
121
- controller = Rory::Controller.new(@request)
122
- controller.view_path('goose').should == File.join(Rory.root, 'views', 'goose.html.erb')
123
- end
124
- end
125
- end