merb 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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