merb 0.0.7 → 0.0.8

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.
Files changed (62) hide show
  1. data/README +66 -31
  2. data/Rakefile +3 -1
  3. data/bin/merb +47 -13
  4. data/examples/app_skeleton/Rakefile +4 -3
  5. data/examples/app_skeleton/dist/app/helpers/global_helper.rb +6 -0
  6. data/examples/app_skeleton/dist/conf/merb.yml +11 -0
  7. data/examples/app_skeleton/dist/conf/mup.conf +5 -0
  8. data/examples/app_skeleton/dist/conf/router.rb +1 -3
  9. data/examples/app_skeleton/scripts/merb_stop +10 -2
  10. data/examples/sample_app/Rakefile +3 -3
  11. data/examples/sample_app/dist/app/controllers/files.rb +3 -3
  12. data/examples/sample_app/dist/app/controllers/posts.rb +25 -23
  13. data/examples/sample_app/dist/app/controllers/test.rb +7 -3
  14. data/examples/sample_app/dist/app/helpers/global_helper.rb +7 -0
  15. data/examples/sample_app/dist/app/helpers/posts_helper.rb +4 -0
  16. data/examples/sample_app/dist/app/views/layout/application.herb +5 -4
  17. data/examples/sample_app/dist/app/views/layout/foo.herb +1 -1
  18. data/examples/sample_app/dist/app/views/posts/new.herb +9 -2
  19. data/examples/sample_app/dist/app/views/shared/_test.herb +1 -0
  20. data/examples/sample_app/dist/conf/merb.yml +7 -7
  21. data/examples/sample_app/dist/conf/merb_init.rb +8 -1
  22. data/examples/sample_app/dist/conf/mup.conf +5 -11
  23. data/examples/sample_app/dist/conf/router.rb +1 -1
  24. data/examples/sample_app/dist/public/test.html +5 -0
  25. data/examples/sample_app/dist/schema/migrations/002_add_sessions_table.rb +1 -1
  26. data/examples/sample_app/dist/schema/schema.rb +1 -1
  27. data/examples/sample_app/log/merb.4000.pid +1 -0
  28. data/lib/merb.rb +35 -17
  29. data/lib/merb/core_ext.rb +2 -0
  30. data/lib/merb/{merb_class_extensions.rb → core_ext/merb_class.rb} +42 -0
  31. data/lib/merb/core_ext/merb_enumerable.rb +7 -0
  32. data/lib/merb/{merb_utils.rb → core_ext/merb_hash.rb} +1 -78
  33. data/lib/merb/core_ext/merb_kernel.rb +16 -0
  34. data/lib/merb/core_ext/merb_module.rb +10 -0
  35. data/lib/merb/core_ext/merb_numeric.rb +20 -0
  36. data/lib/merb/core_ext/merb_object.rb +6 -0
  37. data/lib/merb/core_ext/merb_string.rb +40 -0
  38. data/lib/merb/core_ext/merb_symbol.rb +12 -0
  39. data/lib/merb/merb_constants.rb +18 -0
  40. data/lib/merb/merb_controller.rb +150 -76
  41. data/lib/merb/{session/merb_drb_server.rb → merb_drb_server.rb} +13 -46
  42. data/lib/merb/merb_exceptions.rb +4 -0
  43. data/lib/merb/merb_handler.rb +29 -17
  44. data/lib/merb/merb_request.rb +95 -0
  45. data/lib/merb/merb_upload_handler.rb +46 -0
  46. data/lib/merb/merb_upload_progress.rb +48 -0
  47. data/lib/merb/merb_view_context.rb +46 -0
  48. data/lib/merb/merb_yaml_store.rb +31 -0
  49. data/lib/merb/mixins/basic_authentication_mixin.rb +2 -2
  50. data/lib/merb/mixins/controller_mixin.rb +24 -75
  51. data/lib/merb/mixins/erubis_capture_mixin.rb +84 -0
  52. data/lib/merb/mixins/javascript_mixin.rb +103 -19
  53. data/lib/merb/mixins/merb_status_codes.rb +59 -0
  54. data/lib/merb/mixins/render_mixin.rb +114 -40
  55. data/lib/merb/mixins/responder_mixin.rb +2 -1
  56. data/lib/merb/session/merb_ar_session.rb +120 -0
  57. data/lib/merb/session/merb_drb_session.rb +0 -6
  58. data/lib/merb/vendor/paginator/paginator.rb +102 -99
  59. metadata +44 -8
  60. data/examples/sample_app/script/startdrb +0 -8
  61. data/lib/merb/session/merb_session.rb +0 -64
  62. data/lib/mutex_hotfix.rb +0 -34
@@ -0,0 +1,84 @@
1
+ module Merb
2
+
3
+ module ErubisCaptureMixin
4
+ # Capture allows you to extract a part of the template into an
5
+ # instance variable. You can use this instance variable anywhere
6
+ # in your templates and even in your layout.
7
+ #
8
+ # Example of capture being used in a .herb page:
9
+ #
10
+ # <% @foo = capture do %>
11
+ # <p>Some Foo content!</p>
12
+ # <% end %>
13
+ def capture(*args, &block)
14
+ # execute the block
15
+ begin
16
+ buffer = eval("_buf", block.binding)
17
+ rescue
18
+ buffer = nil
19
+ end
20
+
21
+ if buffer.nil?
22
+ capture_block(*args, &block)
23
+ else
24
+ capture_erb_with_buffer(buffer, *args, &block)
25
+ end
26
+ end
27
+
28
+ # Calling throw_content stores the block of markup for later use.
29
+ # Subsequently, you can make calls to it by name with <tt>catch_content</tt>
30
+ # in another template or in the layout.
31
+ #
32
+ # Example:
33
+ #
34
+ # <% throw_content :header do %>
35
+ # alert('hello world')
36
+ # <% end %>
37
+ #
38
+ # You can use catch_content :header anywhere in your templates.
39
+ #
40
+ # <%= catch_content :header %>
41
+ def throw_content(name, content = nil, &block)
42
+ eval "@_#{name}_content = (@_#{name}_content || '') + capture(&block)"
43
+ end
44
+
45
+ # catch_content catches the thrown content from another template
46
+ # So when you throw_content(:foo) {...} you can catch_content :foo
47
+ # in another view or the layout.
48
+ def catch_content(name)
49
+ instance_variable_get("@_#{name}_content")
50
+ end
51
+
52
+ private
53
+ def capture_block(*args, &block)
54
+ block.call(*args)
55
+ end
56
+
57
+ def capture_erb(*args, &block)
58
+ buffer = eval("_buf", block.binding)
59
+ capture_erb_with_buffer(buffer, *args, &block)
60
+ end
61
+
62
+ def capture_erb_with_buffer(buffer, *args, &block)
63
+ pos = buffer.length
64
+ block.call(*args)
65
+
66
+ # extract the block
67
+ data = buffer[pos..-1]
68
+
69
+ # replace it in the original with empty string
70
+ buffer[pos..-1] = ''
71
+
72
+ data
73
+ end
74
+
75
+ def erb_content_for(name, &block)
76
+ eval "@_#{name}_content = (@_#{name}_content|| '') + capture_erb(&block)"
77
+ end
78
+
79
+ def block_content_for(name, &block)
80
+ eval "@_#{name}_content = (@_#{name}_content|| '') + capture_block(&block)"
81
+ end
82
+ end
83
+
84
+ end
@@ -6,10 +6,15 @@ module Merb
6
6
  (javascript || '').gsub('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }
7
7
  end
8
8
 
9
+ # creates an <a> tag with with an onclick containing
10
+ # a js function
11
+ # link_to_function('click me', "alert('hi!')")
9
12
  def link_to_function(name, function)
10
13
  %{<a href="#" onclick="#{function}; return false;">#{name}</a>}
11
14
  end
12
15
 
16
+ # calls .to_json on data. This will use fjson if installed
17
+ # so it can be faster than escape_js
13
18
  def js(data)
14
19
  if data.respond_to? :to_json
15
20
  data.to_json
@@ -18,43 +23,122 @@ module Merb
18
23
  end
19
24
  end
20
25
 
21
- def require_js(*scripts)
22
- return nil if scripts.empty?
23
- scripts.inject('') do |memo,script|
24
- script = script.to_s
25
- memo << %Q|<script src="/javascripts/#{script=~/\.js$/ ? script : script+'.js' }" type="text/javascript">//</script>\n|
26
+ # Requiring javascripts and stylesheets:
27
+ # you can use require_js(:prototype) or require_css(:shinystyles)
28
+ # from any view or layout and the scripts will only be included once
29
+ # in the head of the final page. In the head of your layout you will
30
+ # need to add these two tags:
31
+ #
32
+ # <%= include_required_js %>
33
+ # <%= include_required_css %>
34
+ #
35
+ # --app/views/layouts/application.rhtml
36
+ #
37
+ # <html>
38
+ # <head>
39
+ # <%= include_required_js %>
40
+ # <%= include_required_css %>
41
+ # </head>
42
+ # <body>
43
+ # <%= catch_content :layout %>
44
+ # </body>
45
+ # </html>
46
+ #
47
+ # --app/views/whatever/index.rhtml
48
+ #
49
+ # <%= partial(:part1) %>
50
+ # <%= partial(:part2) %>
51
+ #
52
+ # --app/views/whatever/_part1.rhtml
53
+ #
54
+ # <% require_js 'this' -%>
55
+ # <% require_css 'that', 'another_one' -%>
56
+ #
57
+ # --app/views/whatever/_part2.rhtml
58
+ #
59
+ # <% require_js 'this', 'something_else' -%>
60
+ # <% require_css 'that' -%>
61
+
62
+
63
+ # require_js(:myjs) can be used to require any javascript
64
+ # file anywhere in your templates. It will only include the
65
+ # javascript tag once in the header
66
+ def require_js(*js)
67
+ @required_js ||= []
68
+ @required_js |= js
69
+ end
70
+
71
+ # require_css(:mystyles) can be used to require any javascript
72
+ # file anywhere in your templates. It will only include the
73
+ # javascript tag once in the header
74
+ def require_css(*css)
75
+ @required_css ||= []
76
+ @required_css |= css
77
+ end
78
+
79
+ # this goes in the head of your layout if you will be using
80
+ # require_js
81
+ def include_required_js
82
+ js_include_tag(*@required_js)
83
+ end
84
+
85
+ # this goes in the head of your layout if you will be using
86
+ # require_css
87
+ def include_required_css
88
+ css_link_tag(*@required_js)
26
89
  end
27
- end
28
90
 
29
- def require_css(*scripts)
30
- return nil if scripts.empty?
31
- scripts.inject('') do |memo,script|
32
- script = script.to_s
33
- memo << %Q|<link href="/stylesheets/#{script=~/\.css$/ ? script : script+'.css' }" media="all" rel="Stylesheet" type="text/css"/>\n|
91
+ # js_include_tag(:foo, :bar, :baz) will create a javascript
92
+ # include tag for each script in the arguments. It will append
93
+ # '.js' if it is left out of the call.
94
+ def js_include_tag(*scripts)
95
+ return nil if scripts.empty?
96
+ scripts.inject('') do |memo,script|
97
+ script = script.to_s
98
+ memo << %Q|<script src="/javascripts/#{script=~/\.js$/ ? script : script+'.js' }" type="text/javascript">//</script>\n|
99
+ end
34
100
  end
35
- end
36
101
 
37
- def js_hash(options)
38
- '{' + options.map {|k, v| "#{k}:#{v}"}.join(', ') + '}'
39
- end
102
+ # css_include_tag(:foo, :bar, :baz) will create a stylesheet
103
+ # link tag for each stylesheet in the arguments. It will append
104
+ # '.css' if it is left out of the call.
105
+ def css_include_tag(*scripts)
106
+ return nil if scripts.empty?
107
+ scripts.inject('') do |memo,script|
108
+ script = script.to_s
109
+ memo << %Q|<link href="/stylesheets/#{script=~/\.css$/ ? script : script+'.css' }" media="all" rel="Stylesheet" type="text/css"/>\n|
110
+ end
111
+ end
112
+
113
+ ##
114
+ # Prototype library helpers.
115
+ ##
40
116
 
41
- def insert_html(id, html, options = {})
117
+ # insert_html takes a position, a dom id and html to be inserted.
118
+ # insert_html(:before, 'my_dom_id', partial(:foo) )
119
+ def insert_html(position, id, html)
42
120
  position = options.fetch(:where, :before)
43
- "new Insertion.#{position.to_s.camel_case}('#{id}', '#{escape_js html}');"
121
+ "new Insertion.#{position.to_s.camel_case}('#{id}', '#{js html}');"
44
122
  end
45
123
 
46
- def replace_html(id, html, options = {})
47
- "Element.update('#{id}', '#{escape_js html}');"
124
+ # replace_html takes a dom id and html to replace the contents with
125
+ # replace_html('my_dom_id', partial(:foo))
126
+ def replace_html(id, html)
127
+ "Element.update('#{id}', '#{js html}');"
48
128
  end
49
129
 
130
+ # takes a dom id and hides the corresponding element.
50
131
  def hide(id)
51
132
  "$('#{id}').style.display = 'none';"
52
133
  end
53
134
 
135
+ # takes a dom id and shows the corresponding element.
54
136
  def show(id)
55
137
  "$('#{id}').style.display = 'block';"
56
138
  end
57
139
 
140
+ # takes a dom id and toggles it to either on or off depending
141
+ # on its current state.
58
142
  def toggle(id)
59
143
  "Element.toggle('#{id}');"
60
144
  end
@@ -0,0 +1,59 @@
1
+ module Merb
2
+ # thanks to Michael Fellinger
3
+ STATUS_CODEs = {
4
+ # 1xx Informational (Request received, continuing process.)
5
+ :continue => 100,
6
+ :switching_protocols => 101,
7
+
8
+ # 2xx Success (The action was successfully received, understood, and accepted.)
9
+ :ok => 200,
10
+ :created => 201,
11
+ :accepted => 202,
12
+ :non_authorative_information => 203,
13
+ :no_content => 204,
14
+ :resent_content => 205,
15
+ :partial_content => 206,
16
+ :multi_status => 207,
17
+
18
+ # 3xx Redirection (The client must take additional action to complete the request.)
19
+ :multiple_choices => 300,
20
+ :moved_permamently => 301,
21
+ :moved_temporarily => 302,
22
+ :found => 302,
23
+ :see_other => 303,
24
+ :not_modified => 304,
25
+ :use_proxy => 305,
26
+ :switch_proxy => 306,
27
+ :temporary_redirect => 307,
28
+
29
+ # 4xx Client Error (The request contains bad syntax or cannot be fulfilled.)
30
+ :bad_request => 400,
31
+ :unauthorized => 401,
32
+ :payment_required => 402,
33
+ :forbidden => 403,
34
+ :not_found => 404,
35
+ :method_not_allowed => 405,
36
+ :not_aceptable => 406,
37
+ :proxy_authentication_required => 407,
38
+ :request_timeout => 408,
39
+ :conflict => 409,
40
+ :gone => 410,
41
+ :length_required => 411,
42
+ :precondition_failed => 412,
43
+ :request_entity_too_large => 413,
44
+ :request_uri_too_long => 414,
45
+ :unsupported_media_type => 415,
46
+ :requested_range_not_satisfiable => 416,
47
+ :expectation_failed => 417,
48
+ :retry_with => 449,
49
+
50
+ # 5xx Server Error (The server failed to fulfill an apparently valid request.)
51
+ :internal_server_error => 500,
52
+ :not_implemented => 501,
53
+ :bad_gateway => 502,
54
+ :service_unavailable => 503,
55
+ :gateway_timeout => 504,
56
+ :http_version_not_supported => 505,
57
+ :bandwidth_limit_exceeded => 509, # (not official)
58
+ }
59
+ end
@@ -1,10 +1,10 @@
1
1
  module Merb
2
2
 
3
3
  module RenderMixin
4
-
4
+
5
5
  # shortcut to a template path based on name.
6
6
  def template_dir(loc)
7
- File.expand_path(Merb::Server.config[:merb_root] + "/dist/app/views/#{loc}")
7
+ File.expand_path(Merb::Server.config[:merb_root] / "/dist/app/views/#{loc}")
8
8
  end
9
9
 
10
10
  # returns the current method name. Used for
@@ -14,41 +14,87 @@ module Merb
14
14
  caller[depth] =~ /`(.*)'$/; $1
15
15
  end
16
16
 
17
+ # given html, js and xml this method returns the template
18
+ # extension from the :template_ext map froom your app's
19
+ # configuration. defaults to .herb, .jerb & .xerb
17
20
  def template_extension_for(ext)
18
21
  Merb::Server.config[:template_ext][ext]
19
22
  end
20
23
 
21
- # does a render with no layout. Also sets the
22
- # content type header to text/javascript
23
- def render_js(template=current_method_name(1), b=binding)
24
- headers['Content-Type'] = "text/javascript"
25
- template = Erubis::Eruby.new(IO.read( template_dir(self.class.name.snake_case) + "/#{template}.#{template_extension_for(:js)}" ))
26
- template.result(b)
24
+ # this returns a ViewContext object populated with all
25
+ # the instance variables in your controller. This is used
26
+ # as the view context object for the Erubis templates.
27
+ def create_view_context
28
+ ViewContext.new(self)
27
29
  end
28
30
 
29
- # set the @layout. Use this right before a render to
30
- # set the name of the layout to use minus the .rhtml
31
- def layout(l)
32
- @layout = l
31
+ # does a render with no layout. Also sets the
32
+ # content type header to text/javascript. Use
33
+ # this when you want to render a template with
34
+ # .jerb extension.
35
+ def render_js(template=current_method_name(1))
36
+ headers['Content-Type'] = "text/javascript"
37
+ template = new_eruby_obj(template_dir(self.class.name.snake_case) / "/#{template}.#{template_extension_for(:js)}")
38
+ template.evaluate(create_view_context)
33
39
  end
34
40
 
35
- # renders nothing but sets the status
41
+ # renders nothing but sets the status, defaults
42
+ # to 200
36
43
  def render_nothing(status=200)
37
44
  @status = status
38
45
  return "\n"
39
46
  end
40
47
 
41
48
  # renders the action without wrapping it in a layout.
42
- def render_no_layout(template=current_method_name(1), b=binding)
43
- template = Erubis::Eruby.new( IO.read( template_dir(self.class.name.snake_case) + "/#{template}.#{template_extension_for(:html)}" ) )
44
- template.result(b)
49
+ # call it without arguments if your template matches
50
+ # the name of the running action. Otherwise you can
51
+ # explicitely set the template name excluding the file
52
+ # extension
53
+ def render_no_layout(template=current_method_name(1))
54
+ template = new_eruby_obj(template_dir(self.class.name.snake_case) / "/#{template}.#{template_extension_for(:html)}")
55
+ template.evaluate(create_view_context)
45
56
  end
46
57
 
58
+ # This is merb's partial render method. You name your
59
+ # partials _partialname.herb, and then call it like
60
+ # partial(:partialname). If there is no '/' character
61
+ # in the argument passed in it will look for the partial
62
+ # in the view directory that corresponds to the current
63
+ # controller name. If you pass a string with a path in it
64
+ # you can render partials in other view directories. So
65
+ # if you create a views/shared directory then you can call
66
+ # partials that live there like partial('shared/foo')
47
67
  def partial(template)
48
- template = Erubis::Eruby.new( IO.read( template_dir(self.class.name.snake_case) + "/_#{template}.#{template_extension_for(:html)}" ) )
49
- template.result(binding)
68
+ if template =~ /\//
69
+ t = template.split('/')
70
+ template = t.pop
71
+ tmpl = new_eruby_obj(template_dir(t.join('/')) / "/_#{template}.#{template_extension_for(:html)}")
72
+ else
73
+ tmpl = new_eruby_obj(template_dir(self.class.name.snake_case) / "/_#{template}.#{template_extension_for(:html)}")
74
+ end
75
+ tmpl.evaluate(create_view_context)
50
76
  end
51
77
 
78
+ # This creates and returns a new Erubis object populated
79
+ # with the template from path. If there is no matching
80
+ # template then we rescue the Errno::ENOENT exception
81
+ # and raise a no template found message
82
+ def new_eruby_obj(path)
83
+ begin
84
+ Erubis::MEruby.new(IO.read(path))
85
+ rescue Errno::ENOENT
86
+ raise "No template found at path: #{path}"
87
+ end
88
+ end
89
+
90
+ # this is the xml builder render method. This method
91
+ # builds the Builder::XmlMarkup object for you and adds
92
+ # the xml headers and encoding. Then it evals your template
93
+ # in the context of the xml object. So your .xerb templates
94
+ # will look like this:
95
+ # xml.foo {|xml|
96
+ # xml.bar "baz"
97
+ # }
52
98
  def render_xml(template=current_method_name(1))
53
99
  xml = Builder::XmlMarkup.new :indent => 2
54
100
  xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
@@ -58,33 +104,61 @@ module Merb
58
104
  xml.target!
59
105
  end
60
106
 
61
- # renders a template based on the current action name
62
- # you can pass the name of a template if you want to
63
- # render a template with a different name then then
64
- # current action name. Wraps the rendered template in
65
- # the layout. Uses layout/application.rhtml unless
107
+ # This is the main render method that handles layouts.
108
+ # render will use layout/application.rhtml unless
66
109
  # there is a layout named after the current controller
67
- # or @layout has been set to another value.
68
- def render(template=current_method_name(1), b=binding)
110
+ # or if self.layout= has been set to another value in
111
+ # the current controller. You can over-ride this setting
112
+ # by passing an options hash with a :layout => 'layoutname'.
113
+ # if you with to not render a layout then pass :layout => :none
114
+ # the first argument to render is the template name. if you do
115
+ # not pass a template name, it will set the template to
116
+ # views/controller/action automatically.
117
+ # examples:
118
+ # class Test < Merb::Controller
119
+ # # renders views/test/foo.herb
120
+ # # in layout application.herb
121
+ # def foo
122
+ # # code
123
+ # render
124
+ # end
125
+ #
126
+ # # renders views/test/foo.herb
127
+ # # in layout application.herb
128
+ # def bar
129
+ # # code
130
+ # render :foo
131
+ # end
132
+ #
133
+ # # renders views/test/baz.herb
134
+ # # with no layout
135
+ # def baz
136
+ # # code
137
+ # render :layout => :none
138
+ # end
139
+ def render(opts={})
140
+ template = opts[:action] || params[:action]
69
141
  tmpl_ext = template_extension_for(:html)
70
- MERB_LOGGER.info("Rendering template: #{template_dir(template)}..#{tmpl_ext}")
142
+ MERB_LOGGER.info("Rendering template: #{template}.#{tmpl_ext}")
71
143
  name = self.class.name.snake_case
72
- template = Erubis::Eruby.new( IO.read( template_dir(name) + "/#{template}.#{tmpl_ext}" ) )
73
- layout_content = template.result(b)
74
- return layout_content if (@layout.to_s == 'none')
75
- if ['application', name].include?(@layout.to_s)
144
+ template = new_eruby_obj(template_dir(name) / "/#{template}.#{tmpl_ext}")
145
+ view_context = create_view_context
146
+ layout_content = template.evaluate(view_context)
147
+ self.layout = opts[:layout].to_sym if opts.has_key?(:layout)
148
+ return layout_content if (layout == :none)
149
+ if layout != :application
150
+ layout_choice = layout
151
+ else
76
152
  if File.exist?(template_dir("layout/#{name}.#{tmpl_ext}"))
77
- layout = name
153
+ layout_choice = name
78
154
  else
79
- layout = 'application'
80
- end
81
- else
82
- layout = @layout.to_s
83
- end
84
- MERB_LOGGER.info("With Layout: #{template_dir('layout')}/#{layout}.#{tmpl_ext}")
85
- @layout_content = layout_content
86
- layout_tmpl = Erubis::Eruby.new( IO.read( "#{template_dir('layout')}/#{layout}.#{tmpl_ext}" ) )
87
- layout_tmpl.result(b)
155
+ layout_choice = layout
156
+ end
157
+ end
158
+ MERB_LOGGER.info("With Layout: #{layout_choice}.#{tmpl_ext}")
159
+ view_context.instance_eval { throw_content(:layout) { layout_content } }
160
+ layout_tmpl = new_eruby_obj("#{template_dir('layout')}/#{layout_choice}.#{tmpl_ext}")
161
+ layout_tmpl.evaluate(view_context)
88
162
  end
89
163
 
90
164
  end