merb 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +71 -14
- data/Rakefile +20 -31
- data/examples/skeleton.tar +0 -0
- data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +1 -1
- data/lib/merb.rb +3 -2
- data/lib/merb/caching.rb +5 -0
- data/lib/merb/caching/action_cache.rb +56 -0
- data/lib/merb/caching/fragment_cache.rb +38 -0
- data/lib/merb/caching/store/file_cache.rb +82 -0
- data/lib/merb/caching/store/memory_cache.rb +67 -0
- data/lib/merb/core_ext.rb +1 -1
- data/lib/merb/core_ext/merb_hash.rb +35 -0
- data/lib/merb/core_ext/merb_object.rb +88 -2
- data/lib/merb/merb_controller.rb +71 -69
- data/lib/merb/merb_dispatcher.rb +72 -0
- data/lib/merb/merb_exceptions.rb +6 -1
- data/lib/merb/merb_handler.rb +19 -47
- data/lib/merb/merb_mailer.rb +1 -1
- data/lib/merb/merb_request.rb +11 -3
- data/lib/merb/merb_router.rb +113 -8
- data/lib/merb/merb_server.rb +71 -12
- data/lib/merb/merb_upload_handler.rb +8 -6
- data/lib/merb/merb_upload_progress.rb +1 -1
- data/lib/merb/merb_view_context.rb +13 -3
- data/lib/merb/mixins/basic_authentication_mixin.rb +1 -3
- data/lib/merb/mixins/controller_mixin.rb +149 -17
- data/lib/merb/mixins/form_control_mixin.rb +1 -0
- data/lib/merb/mixins/render_mixin.rb +148 -151
- data/lib/merb/mixins/responder_mixin.rb +133 -18
- data/lib/merb/mixins/view_context_mixin.rb +24 -0
- data/lib/merb/session/merb_ar_session.rb +4 -4
- data/lib/merb/session/merb_memory_session.rb +6 -5
- data/lib/merb/template.rb +10 -0
- data/lib/merb/template/erubis.rb +52 -0
- data/lib/merb/template/haml.rb +77 -0
- data/lib/merb/template/markaby.rb +48 -0
- data/lib/merb/template/xml_builder.rb +34 -0
- metadata +19 -17
- data/lib/merb/mixins/merb_status_codes.rb +0 -59
@@ -1,32 +1,94 @@
|
|
1
1
|
module Merb
|
2
2
|
|
3
3
|
module RenderMixin
|
4
|
-
@@erbs = {}
|
5
|
-
@@mtimes = {}
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
|
27
|
-
|
5
|
+
# universal render method. Template handlers are registered
|
6
|
+
# by template extension. So you can use the same render method
|
7
|
+
# for any kind of template that implements an adapter module.
|
8
|
+
# out of the box Merb support Erubis, Markaby and Builder templates
|
9
|
+
#
|
10
|
+
# Erubis template ext: .herb .jerb .erb
|
11
|
+
# Markaby template ext: .mab
|
12
|
+
# Builder template ext: .rxml .builder .xerb
|
13
|
+
#
|
14
|
+
# Examples:
|
15
|
+
#
|
16
|
+
# render
|
17
|
+
# looks for views/controllername/actionname.* and renders
|
18
|
+
# the template with the proper engine based on its file extension.
|
19
|
+
#
|
20
|
+
# render :layout => :none
|
21
|
+
# renders the current template with no layout. XMl Builder templates
|
22
|
+
# are exempt from layout by default.
|
23
|
+
#
|
24
|
+
# render :action => 'foo'
|
25
|
+
# renders views/controllername/foo.*
|
26
|
+
#
|
27
|
+
# render :nothing => 200
|
28
|
+
# renders nothing with a status of 200
|
29
|
+
#
|
30
|
+
# render :js => "$('some-div').toggle();"
|
31
|
+
# if the right hand side of :js => is a string then the proper
|
32
|
+
# javascript headers will be set and the string will be returned
|
33
|
+
# verbatim as js.
|
34
|
+
#
|
35
|
+
# render :js => :spinner
|
36
|
+
# when the rhs of :js => is a Symbol, it will be used as the
|
37
|
+
# action/template name so: views/controllername/spinner.jerb
|
38
|
+
# will be rendered as javascript
|
39
|
+
#
|
40
|
+
# render :js => true
|
41
|
+
# this will just look for the current controller/action tenmplate
|
42
|
+
# with the .jerb extension and render it as javascript
|
43
|
+
#
|
44
|
+
# render :xml => @posts.to_xml
|
45
|
+
# render :xml => "<foo><bar>Hi!</bar></foo>"
|
46
|
+
# this will set the appropriate xml headers and render the rhs
|
47
|
+
# of :xml => as a string. SO you can pass any xml string to this
|
48
|
+
# to be rendered.
|
49
|
+
#
|
50
|
+
def render(opts={}, &blk)
|
51
|
+
action = opts[:action] || params[:action]
|
52
|
+
opts[:layout] ||= ancestral_trait[:layout]
|
53
|
+
|
54
|
+
case
|
55
|
+
when status = opts[:nothing]
|
56
|
+
return render_nothing(status)
|
57
|
+
when partial = opts[:partial]
|
58
|
+
template = find_partial(partial, opts)
|
59
|
+
opts[:layout] = :none
|
60
|
+
when js = opts[:js]
|
61
|
+
headers['Content-Type'] = "text/javascript"
|
62
|
+
opts[:layout] = :none
|
63
|
+
if String === js
|
64
|
+
return js
|
65
|
+
elsif Symbol === js
|
66
|
+
template = find_template(:action => js, :ext => 'jerb')
|
67
|
+
else
|
68
|
+
template = find_template(:action => action, :ext => 'jerb')
|
69
|
+
end
|
70
|
+
when xml = opts[:xml]
|
71
|
+
headers['Content-Type'] = 'application/xml'
|
72
|
+
headers['Encoding'] = 'UTF-8'
|
73
|
+
return xml
|
74
|
+
else
|
75
|
+
template = find_template(:action => action)
|
76
|
+
end
|
77
|
+
|
78
|
+
engine = engine_for(template)
|
79
|
+
options = {
|
80
|
+
:file => template,
|
81
|
+
:view_context => (opts[:clean_context] ? clean_view_context : _view_context),
|
82
|
+
:opts => opts
|
83
|
+
}
|
84
|
+
content = engine.transform(options)
|
85
|
+
if engine.exempt_from_layout? || opts[:layout] == :none
|
86
|
+
content
|
87
|
+
else
|
88
|
+
wrap_layout(content, opts)
|
89
|
+
end
|
28
90
|
end
|
29
|
-
|
91
|
+
|
30
92
|
# this returns a ViewContext object populated with all
|
31
93
|
# the instance variables in your controller. This is used
|
32
94
|
# as the view context object for the Erubis templates.
|
@@ -34,36 +96,32 @@ module Merb
|
|
34
96
|
@_view_context_cache ||= ViewContext.new(self)
|
35
97
|
end
|
36
98
|
|
99
|
+
def clean_view_context
|
100
|
+
ViewContext.new(self)
|
101
|
+
end
|
102
|
+
|
37
103
|
# does a render with no layout. Also sets the
|
38
104
|
# content type header to text/javascript. Use
|
39
105
|
# this when you want to render a template with
|
40
106
|
# .jerb extension.
|
41
|
-
def render_js(template=
|
42
|
-
|
43
|
-
template = new_eruby_obj(template_dir(self.class.name.snake_case) / "/#{template}.#{template_extension_for(:js)}")
|
44
|
-
template.evaluate(_view_context)
|
107
|
+
def render_js(template=nil)
|
108
|
+
render :js => true, :action => (template || params[:action])
|
45
109
|
end
|
46
|
-
|
110
|
+
|
47
111
|
# renders nothing but sets the status, defaults
|
48
|
-
# to 200. does send one
|
112
|
+
# to 200. does send one ' ' space char, this is for
|
49
113
|
# safari and flash uploaders to work.
|
50
114
|
def render_nothing(status=200)
|
51
115
|
@status = status
|
52
|
-
return "
|
116
|
+
return " "
|
53
117
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
# the name of the running action. Otherwise you can
|
58
|
-
# explicitely set the template name excluding the file
|
59
|
-
# extension
|
60
|
-
def render_no_layout(template=current_method_name(1))
|
61
|
-
template = new_eruby_obj(template_dir(self.class.name.snake_case) / "/#{template}.#{template_extension_for(:html)}")
|
62
|
-
template.evaluate(_view_context)
|
118
|
+
|
119
|
+
def render_no_layout(opts={})
|
120
|
+
render opts.update({:layout => :none})
|
63
121
|
end
|
64
122
|
|
65
123
|
# This is merb's partial render method. You name your
|
66
|
-
# partials _partialname
|
124
|
+
# partials _partialname.* , and then call it like
|
67
125
|
# partial(:partialname). If there is no '/' character
|
68
126
|
# in the argument passed in it will look for the partial
|
69
127
|
# in the view directory that corresponds to the current
|
@@ -72,120 +130,59 @@ module Merb
|
|
72
130
|
# if you create a views/shared directory then you can call
|
73
131
|
# partials that live there like partial('shared/foo')
|
74
132
|
def partial(template)
|
75
|
-
|
76
|
-
t = template.split('/')
|
77
|
-
template = t.pop
|
78
|
-
tmpl = new_eruby_obj(template_dir(t.join('/')) / "/_#{template}.#{template_extension_for(:html)}")
|
79
|
-
else
|
80
|
-
tmpl = new_eruby_obj(template_dir(self.class.name.snake_case) / "/_#{template}.#{template_extension_for(:html)}")
|
81
|
-
end
|
82
|
-
tmpl.evaluate(_view_context)
|
133
|
+
render :partial => template
|
83
134
|
end
|
84
135
|
|
85
|
-
|
86
|
-
# with the template from path. If there is no matching
|
87
|
-
# template then we rescue the Errno::ENOENT exception
|
88
|
-
# and raise a no template found message
|
89
|
-
def new_eruby_obj(path)
|
90
|
-
if @@erbs[path] && !cache_template?(path)
|
91
|
-
return @@erbs[path]
|
92
|
-
else
|
93
|
-
begin
|
94
|
-
returning Erubis::MEruby.new(IO.read(path)) do |eruby|
|
95
|
-
eruby.init_evaluator :filename => path
|
96
|
-
if cache_template?(path)
|
97
|
-
@@erbs[path] = eruby
|
98
|
-
@@mtimes[path] = Time.now
|
99
|
-
end
|
100
|
-
end
|
101
|
-
rescue Errno::ENOENT
|
102
|
-
raise "No template found at path: #{path}"
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def cache_template?(path)
|
108
|
-
return false unless Merb::Server.config[:cache_templates]
|
109
|
-
return true unless @@erbs[path]
|
110
|
-
@@mtimes[path] < File.mtime(path) ||
|
111
|
-
(File.symlink?(path) && (@@mtimes[path] < File.lstat(path).mtime))
|
112
|
-
end
|
136
|
+
private
|
113
137
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
# in the context of the xml object. So your .xerb templates
|
118
|
-
# will look like this:
|
119
|
-
# xml.foo {|xml|
|
120
|
-
# xml.bar "baz"
|
121
|
-
# }
|
122
|
-
def render_xml(template=current_method_name(1))
|
123
|
-
_xml_body = IO.read( template_dir(self.class.name.snake_case) + "/#{template}.#{template_extension_for(:xml)}" )
|
124
|
-
headers['Content-Type'] = 'application/xml'
|
125
|
-
headers['Encoding'] = 'UTF-8'
|
126
|
-
_view_context.instance_eval %{
|
127
|
-
xml = Builder::XmlMarkup.new :indent => 2
|
128
|
-
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
129
|
-
#{_xml_body}
|
130
|
-
return xml.target!
|
131
|
-
}
|
132
|
-
end
|
133
|
-
|
134
|
-
# This is the main render method that handles layouts.
|
135
|
-
# render will use layout/application.rhtml unless
|
136
|
-
# there is a layout named after the current controller
|
137
|
-
# or if self.layout= has been set to another value in
|
138
|
-
# the current controller. You can over-ride this setting
|
139
|
-
# by passing an options hash with a :layout => 'layoutname'.
|
140
|
-
# if you with to not render a layout then pass :layout => :none
|
141
|
-
# the first argument to render is the template name. if you do
|
142
|
-
# not pass a template name, it will set the template to
|
143
|
-
# views/controller/action automatically.
|
144
|
-
# examples:
|
145
|
-
# class Test < Merb::Controller
|
146
|
-
# # renders views/test/foo.herb
|
147
|
-
# # in layout application.herb
|
148
|
-
# def foo
|
149
|
-
# # code
|
150
|
-
# render
|
151
|
-
# end
|
152
|
-
#
|
153
|
-
# # renders views/test/foo.herb
|
154
|
-
# # in layout application.herb
|
155
|
-
# def bar
|
156
|
-
# # code
|
157
|
-
# render :foo
|
158
|
-
# end
|
159
|
-
#
|
160
|
-
# # renders views/test/baz.herb
|
161
|
-
# # with no layout
|
162
|
-
# def baz
|
163
|
-
# # code
|
164
|
-
# render :layout => :none
|
165
|
-
# end
|
166
|
-
def render(opts={})
|
167
|
-
template = opts.is_a?(Symbol) ? opts : (opts[:action] || params[:action])
|
168
|
-
tmpl_ext = template_extension_for(:html)
|
169
|
-
MERB_LOGGER.info("Rendering template: #{template}.#{tmpl_ext}")
|
170
|
-
name = self.class.name.snake_case
|
171
|
-
template = new_eruby_obj(template_dir(name) / "/#{template}.#{tmpl_ext}")
|
172
|
-
layout_content = template.evaluate(_view_context)
|
173
|
-
self.layout = opts[:layout].to_sym if opts.has_key?(:layout)
|
174
|
-
return layout_content if (layout == :none)
|
175
|
-
if layout != :application
|
176
|
-
layout_choice = layout
|
177
|
-
else
|
178
|
-
if File.exist?(template_dir("layout/#{name}.#{tmpl_ext}"))
|
179
|
-
layout_choice = name
|
138
|
+
def wrap_layout(content, opts={})
|
139
|
+
if opts[:layout] != :application
|
140
|
+
layout_choice = find_template(:layout => opts[:layout])
|
180
141
|
else
|
181
|
-
|
142
|
+
if name = find_template(:layout => self.class.name.snake_case)
|
143
|
+
layout_choice = name
|
144
|
+
else
|
145
|
+
layout_choice = find_template(:layout => :application)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
_view_context.instance_variable_set('@_layout_content', content)
|
150
|
+
engine = engine_for(layout_choice)
|
151
|
+
options = {
|
152
|
+
:file => layout_choice,
|
153
|
+
:view_context => _view_context,
|
154
|
+
:opts => opts
|
155
|
+
}
|
156
|
+
engine.transform(options)
|
157
|
+
end
|
158
|
+
|
159
|
+
# OPTIMIZE : combine find_template and find_partial ?
|
160
|
+
def find_template(opts={})
|
161
|
+
if action = opts[:action]
|
162
|
+
path =
|
163
|
+
File.expand_path(MERB_ROOT / "dist/app/views" / self.class.name.snake_case / action)
|
164
|
+
elsif layout = opts[:layout]
|
165
|
+
path = ancestral_trait[:layout_root] / layout
|
166
|
+
else
|
167
|
+
raise "called find_template without an :action or :layout"
|
182
168
|
end
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
169
|
+
extensions = [ancestral_trait[:template_extensions].keys].flatten.uniq
|
170
|
+
glob = "#{path}.{#{opts[:ext] || extensions.join(',')}}"
|
171
|
+
Dir[glob].first
|
172
|
+
end
|
173
|
+
|
174
|
+
def find_partial(template, opts={})
|
175
|
+
if template =~ /\//
|
176
|
+
t = template.split('/')
|
177
|
+
template = t.pop
|
178
|
+
path = ancestral_trait[:template_root] / t.join('/') / "_#{template}"
|
179
|
+
else
|
180
|
+
path = ancestral_trait[:template_root] / self.class.name.snake_case / "_#{template}"
|
181
|
+
end
|
182
|
+
extensions = [ancestral_trait[:template_extensions].keys].flatten.uniq
|
183
|
+
glob = "#{path}.{#{opts[:ext] || extensions.join(',')}}"
|
184
|
+
Dir[glob].first
|
185
|
+
end
|
189
186
|
|
190
187
|
end
|
191
|
-
end
|
188
|
+
end
|
@@ -9,31 +9,146 @@ module Merb
|
|
9
9
|
# type.yaml { @foo.to_yaml }
|
10
10
|
# end
|
11
11
|
|
12
|
+
# TODO : revisit this whole patern. Can we improve on this?
|
12
13
|
module ResponderMixin
|
13
|
-
|
14
|
-
|
15
|
-
@
|
16
|
-
|
14
|
+
|
15
|
+
def respond_to(&block)
|
16
|
+
responder = Rest::Responder.new(@env['HTTP_ACCEPT'], params)
|
17
|
+
block.call(responder)
|
18
|
+
responder.respond(headers)
|
19
|
+
@status = responder.status
|
20
|
+
responder.body
|
17
21
|
end
|
18
22
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
:yaml => %w[application/yaml text/yaml],
|
23
|
+
module Rest
|
24
|
+
|
25
|
+
TYPES = {
|
26
|
+
:all => %w[*/*],
|
27
|
+
:yaml => %w[application/x-yaml text/yaml],
|
25
28
|
:text => %w[text/plain],
|
26
|
-
:html => %w[text/html
|
27
|
-
:xml => %w[application/xml],
|
28
|
-
:js => %w[application/json text/x-json]
|
29
|
+
:html => %w[text/html application/xhtml+xml application/html],
|
30
|
+
:xml => %w[application/xml text/xml application/x-xml],
|
31
|
+
:js => %w[application/json text/x-json text/javascript application/javascript application/x-javascript]
|
29
32
|
}
|
33
|
+
|
34
|
+
class Responder
|
35
|
+
|
36
|
+
attr_reader :body, :type, :status
|
37
|
+
|
38
|
+
def initialize(accept_header, params={})
|
39
|
+
MERB_LOGGER.info accept_header
|
40
|
+
@accepts = Responder.parse(accept_header)
|
41
|
+
@params = params
|
42
|
+
@stack = {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def method_missing(symbol, &block)
|
46
|
+
raise "respond_to expects a block" unless block_given?
|
47
|
+
# the first method we encounter here will be used for the catch all mime-type */*
|
48
|
+
@stack[:all] = block unless @stack[:all]
|
49
|
+
@stack[symbol] = block
|
50
|
+
end
|
51
|
+
|
52
|
+
def respond(headers)
|
53
|
+
unless @stack.keys.all?{|k| TYPES.has_key?(k) }
|
54
|
+
raise "unrecognized mime type in respond_to block"
|
55
|
+
end
|
56
|
+
mime_type = negotiate_content
|
57
|
+
if mime_type
|
58
|
+
headers['Content-Type'] = mime_type.super_range
|
59
|
+
@status = 200
|
60
|
+
@body = @stack[mime_type.to_sym].call
|
61
|
+
else
|
62
|
+
headers['Content-Type'] = nil
|
63
|
+
@status = 406
|
64
|
+
@body = nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
def self.parse(accept_header)
|
71
|
+
index = 0
|
72
|
+
list = accept_header.split(/,/).map! do |entry|
|
73
|
+
AcceptType.new(entry,index += 1)
|
74
|
+
end.sort!.uniq
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def negotiate_content
|
80
|
+
if @params['format']
|
81
|
+
negotiate_by_format
|
82
|
+
elsif (@stack.keys & @accepts.map(&:to_sym)).size > 0
|
83
|
+
negotiate_by_accept_header
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def negotiate_by_format
|
88
|
+
format = @params['format'].to_sym
|
89
|
+
if @stack[format]
|
90
|
+
if @accepts.map(&:to_sym).include?(format)
|
91
|
+
@accepts.select{|a| a.to_sym == format }.first
|
92
|
+
else
|
93
|
+
AcceptType.new(TYPES[format].first,0)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def negotiate_by_accept_header
|
99
|
+
@accepts.each do |accept|
|
100
|
+
return accept if @stack[accept.to_sym] || accept.to_sym == :all
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
class AcceptType
|
30
107
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
@
|
108
|
+
attr_reader :media_range, :quality, :index, :type, :sub_type
|
109
|
+
|
110
|
+
def initialize(entry,index)
|
111
|
+
@index = index
|
112
|
+
@media_range, quality = entry.split(/;\s*q=/).map(&:strip)
|
113
|
+
@type, @sub_type = @media_range.split(/\//)
|
114
|
+
quality ||= 0.0 if @media_range == '*/*'
|
115
|
+
@quality = ((quality || 1.0).to_f * 100).to_i
|
116
|
+
end
|
117
|
+
|
118
|
+
def <=>(entry)
|
119
|
+
returning (entry.quality <=> quality).to_s do |c|
|
120
|
+
c.replace((index <=> entry.index).to_s) if c == '0'
|
121
|
+
end.to_i
|
35
122
|
end
|
123
|
+
|
124
|
+
def eql?(entry)
|
125
|
+
synonyms.include?(entry.media_range)
|
126
|
+
end
|
127
|
+
|
128
|
+
def ==(entry); eql?(entry); end
|
129
|
+
|
130
|
+
def hash; super_range.hash; end
|
131
|
+
|
132
|
+
def synonyms
|
133
|
+
TYPES.values.select{|e| e.include?(@media_range)}.flatten
|
134
|
+
end
|
135
|
+
|
136
|
+
def super_range
|
137
|
+
synonyms.first || @media_range
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_sym
|
141
|
+
TYPES.select{|k,v| v == synonyms }.flatten.first
|
142
|
+
end
|
143
|
+
|
144
|
+
def to_s
|
145
|
+
@media_range
|
146
|
+
end
|
147
|
+
|
36
148
|
end
|
149
|
+
|
37
150
|
end
|
151
|
+
|
38
152
|
end
|
39
|
-
|
153
|
+
|
154
|
+
end
|