merb 0.2.0 → 0.3.0

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.
@@ -12,10 +12,18 @@ module Merb
12
12
  # to your controller via params. It also parses the ?query=string and
13
13
  # puts that into params as well.
14
14
  class Controller
15
-
16
- trait :layout => :application
17
- trait :session_id_key => :_session_id
18
-
15
+
16
+ class_inheritable_accessor :_layout,
17
+ :_session_id_key,
18
+ :_template_extensions,
19
+ :_template_root,
20
+ :_layout_root
21
+ self._layout = :application
22
+ self._session_id_key = :_session_id
23
+ self._template_extensions = { }
24
+ self._template_root = File.expand_path(MERB_ROOT / "dist/app/views")
25
+ self._layout_root = File.expand_path(MERB_ROOT / "dist/app/views/layout")
26
+
19
27
  include Merb::ControllerMixin
20
28
  include Merb::RenderMixin
21
29
  include Merb::ResponderMixin
@@ -23,22 +31,22 @@ module Merb
23
31
  attr_accessor :status, :body, :request
24
32
 
25
33
  MULTIPART_REGEXP = /\Amultipart\/form-data.*boundary=\"?([^\";,]+)/n.freeze
26
-
34
+
27
35
  # parses the http request into params, headers and cookies
28
36
  # that you can use in your controller classes. Also handles
29
37
  # file uploads by writing a tempfile and passing a reference
30
38
  # in params.
31
39
  def initialize(request, env, args, response)
32
40
  @env = MerbHash[env.to_hash]
33
- @status, @method, @response, @headers = 200, (env['REQUEST_METHOD']||'GET').downcase.to_sym, response,
34
- {'Content-Type' =>'text/html'}
35
- cookies = query_parse(@env['HTTP_COOKIE'], ';,')
36
- querystring = query_parse(@env['QUERY_STRING'])
41
+ @status, @method, @response, @headers = 200, (env[Mongrel::Const::REQUEST_METHOD]||Mongrel::Const::GET).downcase.to_sym, response,
42
+ Mongrel::Const::CONTENT_TYPE_TEXT_HTML_HASH
43
+ cookies = query_parse(@env[Mongrel::Const::HTTP_COOKIE], ';,')
44
+ querystring = query_parse(@env[Mongrel::Const::QUERY_STRING])
37
45
 
38
- if MULTIPART_REGEXP =~ @env['CONTENT_TYPE'] && @method == :post
46
+ if MULTIPART_REGEXP =~ @env[Mongrel::Const::UPCASE_CONTENT_TYPE] && @method == :post
39
47
  querystring.update(parse_multipart(request, $1))
40
48
  elsif @method == :post
41
- if ['application/json', 'text/x-json'].include?(@env['CONTENT_TYPE'])
49
+ if [Mongrel::Const::APPLICATION_JSON, Mongrel::Const::TEXT_JSON].include?(@env[Mongrel::Const::UPCASE_CONTENT_TYPE])
42
50
  MERB_LOGGER.info("JSON Request")
43
51
  json = JSON.parse(request.read || "") || {}
44
52
  json = MerbHash.new(json) if json.is_a? Hash
@@ -49,7 +57,7 @@ module Merb
49
57
  end
50
58
 
51
59
  @cookies, @params = cookies, querystring.update(args)
52
- @cookies[ancestral_trait[:session_id_key]] = @params[ancestral_trait[:session_id_key]] if @params.key?(ancestral_trait[:session_id_key])
60
+ @cookies[_session_id_key] = @params[_session_id_key] if @params.key?(_session_id_key)
53
61
 
54
62
  allow = [:post, :put, :delete]
55
63
  allow << :get if MERB_ENV == 'development'
@@ -81,7 +89,7 @@ module Merb
81
89
  end
82
90
  call_filters(after_filters)
83
91
  finalize_session if respond_to?:finalize_session
84
- MERB_LOGGER.info("Time spent in #{action} action: #{Time.now - start} seconds")
92
+ MERB_LOGGER.info("Time spent in #{self.class}##{action} action: #{Time.now - start} seconds")
85
93
  end
86
94
 
87
95
  # override this method on your controller classes to specialize
@@ -126,15 +134,13 @@ module Merb
126
134
  @response
127
135
  end
128
136
 
129
- trait :template_extensions => { }
130
- trait :template_root => File.expand_path(MERB_ROOT / "dist/app/views")
131
- trait :layout_root => File.expand_path(MERB_ROOT / "dist/app/views/layout")
137
+
132
138
  # lookup the trait[:template_extensions] for the extname of the filename
133
139
  # you pass. Answers with the engine that matches the extension, Template::Erubis
134
140
  # is used if none matches.
135
141
  def engine_for(file)
136
142
  extension = File.extname(file)[1..-1]
137
- ancestral_trait[:template_extensions][extension]
143
+ _template_extensions[extension]
138
144
  rescue
139
145
  ::Merb::Template::Erubis
140
146
  end
@@ -143,7 +149,7 @@ module Merb
143
149
  # a list of extensions that will be looked up on #render of an action.
144
150
  def self.register_engine(engine, *extensions)
145
151
  [extensions].flatten.uniq.each do |ext|
146
- trait[:template_extensions][ext] = engine
152
+ _template_extensions[ext] = engine
147
153
  end
148
154
  end
149
155
 
@@ -154,8 +160,8 @@ module Merb
154
160
  # from its superclasses. Since @@class variables are almost
155
161
  # global vars within an inheritance tree, we use
156
162
  # @class_instance_variables instead
157
- shared_accessor :before_filters
158
- shared_accessor :after_filters
163
+ class_inheritable_accessor :before_filters
164
+ class_inheritable_accessor :after_filters
159
165
 
160
166
  # calls a filter chain according to rules.
161
167
  def call_filters(filter_set)
@@ -5,6 +5,10 @@ module Merb
5
5
 
6
6
  attr_accessor :path_prefix
7
7
 
8
+ def use_mutex=(val)
9
+ @@use_mutex = val
10
+ end
11
+
8
12
  @@mutex = Mutex.new
9
13
  @@use_mutex = ::Merb::Server.use_mutex
10
14
  # This is where we grab the incoming request PATH_INFO
@@ -45,7 +49,7 @@ module Merb
45
49
  def route_path(path)
46
50
  path = path.sub(/\/+/, '/').sub(/\?.*$/, '')
47
51
  path = path[0..-2] if (path[-1] == ?/) && path.size > 1
48
- Merb::RouteMatcher.new.route_request(path)
52
+ Merb::RouteMatcher.route_request(path)
49
53
  end
50
54
 
51
55
  # take a controller class name string and reload or require
@@ -57,14 +61,17 @@ module Merb
57
61
  raise "Bad controller! #{controller_name.snake_case}"
58
62
  end unless $TESTING
59
63
  begin
60
- controller_name.import
64
+ unless MERB_ENV == 'production'
65
+ Object.send(:remove_const, controller_name.camel_case.intern) rescue nil
66
+ load(controller_name.snake_case + '.rb')
67
+ end
61
68
  return Object.const_get( controller_name.camel_case ).new(req, env, params, res)
62
69
  rescue RuntimeError
63
70
  warn "Error getting instance of '#{controller_name.camel_case}': #{$!}"
64
71
  raise $!
65
72
  end
66
73
  end
67
-
74
+
68
75
  end # end class << self
69
76
 
70
77
  end
@@ -43,7 +43,12 @@ module Merb
43
43
 
44
44
  backtrace.map! do |line|
45
45
  file, lineno, meth = line.scan(/(.*?):(\d+)(?::in `(.*?)')?/).first
46
- lines = __caller_lines__(file, lineno, 5)
46
+
47
+ # When a backtrace entry doesn't have a filepath as the first field
48
+ # (e.g., "(haml):12" or "(eval):41:in `_haml_render'"), it'll cause
49
+ # a second exception to be raised, masking the first. This traps it.
50
+ lines = (__caller_lines__(file, lineno, 5) rescue [])
51
+
47
52
  [ lines, lines.object_id.abs, file, lineno, meth ]
48
53
  end
49
54
 
@@ -62,7 +62,7 @@ class MerbHandler < Mongrel::HttpHandler
62
62
  @files.process(request,response)
63
63
  else
64
64
  begin
65
- # dLet Merb:Dispatcher find the route and call the filter chain and action
65
+ # Let Merb:Dispatcher find the route and call the filter chain and action
66
66
  controller = nil
67
67
  controller, action = Merb::Dispatcher.handle(request, response)
68
68
 
@@ -1,7 +1,8 @@
1
- require 'net/smtp'
1
+
2
2
 
3
3
  begin
4
4
  require 'mailfactory'
5
+ require 'net/smtp'
5
6
  rescue LoadError
6
7
  puts "You need to install the mailfactory gem to use Merb::Mailer"
7
8
  MERB_LOGGER.warn "You need to install the mailfactory gem to use Merb::Mailer"
@@ -10,7 +11,7 @@ end
10
11
  module Merb
11
12
  class Mailer
12
13
 
13
- shared_accessor :config
14
+ class_inheritable_accessor :config
14
15
 
15
16
  def sendmail
16
17
  sendmail = IO.popen("sendmail #{@mail.to}", 'w+')
@@ -50,6 +50,62 @@ module Merb
50
50
  @env['REQUEST_URI']
51
51
  end
52
52
 
53
+ def user_agent
54
+ @env['HTTP_USER_AGENT']
55
+ end
56
+
57
+ def server_name
58
+ @env['SERVER_NAME']
59
+ end
60
+
61
+ def accept_encoding
62
+ @env['HTTP_ACCEPT_ENCODING']
63
+ end
64
+
65
+ def script_name
66
+ @env['SCRIPT_NAME']
67
+ end
68
+
69
+ def cache_control
70
+ @env['HTTP_CACHE_CONTROL']
71
+ end
72
+
73
+ def accept_language
74
+ @env['HTTP_ACCEPT_LANGUAGE']
75
+ end
76
+
77
+ def host
78
+ @env['HTTP_HOST']
79
+ end
80
+
81
+ def server_software
82
+ @env['SERVER_SOFTWARE']
83
+ end
84
+
85
+ def keep_alive
86
+ @env['HTTP_KEEP_ALIVE']
87
+ end
88
+
89
+ def accept_charset
90
+ @env['HTTP_ACCEPT_CHARSET']
91
+ end
92
+
93
+ def version
94
+ @env['HTTP_VERSION']
95
+ end
96
+
97
+ def gateway
98
+ @env['GATEWAY_INTERFACE']
99
+ end
100
+
101
+ def accept
102
+ @env['HTTP_ACCEPT']
103
+ end
104
+
105
+ def connection
106
+ @env['HTTP_CONNECTION']
107
+ end
108
+
53
109
  def query_string
54
110
  @env['QUERY_STRING']
55
111
  end
@@ -1,9 +1,5 @@
1
1
  module Merb
2
- begin
3
- require 'active_support'
4
- rescue
5
- MERB_LOGGER.warn "You must have ActiveSupport installed to use merb restful routing\nNormal routing works fine without"
6
- end
2
+
7
3
  # Merb::RouteMatcher is the request routing mapper for the merb framework.
8
4
  # You can define placeholder parts of the url with the :symbol notation.
9
5
  # so r.add '/foo/:bar/baz/:id', :class => 'Bar', :method => 'foo'
@@ -28,17 +24,7 @@ module Merb
28
24
  yield self
29
25
  compile_router
30
26
  end
31
-
32
- # init @sections for route segment recognition
33
- def initialize
34
- @sections = Hash.new
35
- end
36
-
37
- # all defined routes in their raw form.
38
- def routes
39
- @@routes
40
- end
41
-
27
+
42
28
  # the final compiled lambda that gets used
43
29
  # as the body of the route_request method.
44
30
  def self.compiled_statement
@@ -63,28 +49,20 @@ module Merb
63
49
  # against each of the compiled routes in turn.
64
50
  # first route that matches wins.
65
51
  def self.compile_router
66
- router_lambda = @@routes.inject("lambda{|path| \n case path\n") { |m,r|
52
+ router_lambda = @@routes.inject("lambda{|path| \n sections={}\n case path\n") { |m,r|
67
53
  m << compile(r)
68
54
  } <<" else\n return {:controller=>'Noroutefound', :action=>'noroute'}\n end\n}"
69
55
  @@compiled_statement = router_lambda
70
- define_method(:route_request, &eval(router_lambda))
56
+ meta_def(:route_request, &eval(router_lambda))
71
57
  end
72
58
 
73
59
  # compile each individual route into a when /.../
74
60
  # component of the case statement. Takes /:sections
75
61
  # of the route def that start with : and turns them
76
62
  # into placeholders for whatever urls match against
77
- # the route in question. Special case for the default
78
- # /:controller/:action/:id route.
63
+ # the route in question.
79
64
  def self.compile(route)
80
65
  raise ArgumentError unless String === route[0]
81
- if route[0] == '/:controller/:action/:id'
82
- return ' when /\A\/([^\/;.,?]+)(?:\/?\Z|\/([^\/;.,?]+)\/?)(?:\/?\Z|\/([^\/;.,?]+)\/?)\Z/
83
- @sections[:controller] = $1
84
- @sections[:action] = $2 || \'index\'
85
- @sections[:id] = $3 if $3
86
- return @sections'<<"\n"
87
- end
88
66
  code, count = '', 0
89
67
  while route[0] =~ @@section_regexp
90
68
  route[0] = route[0].dup
@@ -96,13 +74,13 @@ module Merb
96
74
  else
97
75
  route[0].sub!(@@section_regexp, "([^\/,?]+)")
98
76
  end
99
- code << " @sections[:#{name}] = $#{count}\n"
77
+ code << " sections[:#{name}] = $#{count}\n"
100
78
  end
101
79
  @@compiled_regexen << Regexp.new(route[0])
102
80
  index = @@compiled_regexen.size - 1
103
81
  condition = " when @@compiled_regexen[#{index}] "
104
82
  statement = "#{condition}\n#{code}"
105
- statement << " return #{route[1].inspect}.update(@sections)\n"
83
+ statement << " return #{route[1].inspect}.update(sections)\n"
106
84
  statement
107
85
  end
108
86
 
@@ -145,7 +123,7 @@ module Merb
145
123
  if block_given?
146
124
  procs = []
147
125
  yield Resource.new(res, procs, opts)
148
- procs.reverse.each {|p| p.call}
126
+ procs.reverse.each &:call
149
127
  else
150
128
  generate_resources_routes(res,opts)
151
129
  end
@@ -157,7 +135,7 @@ module Merb
157
135
  if block_given?
158
136
  procs = []
159
137
  yield Resource.new(res, procs, opts)
160
- procs.reverse.each {|p| p.call}
138
+ procs.reverse.each &:call
161
139
  else
162
140
  generate_singleton_routes(res,opts)
163
141
  end
@@ -165,19 +143,19 @@ module Merb
165
143
 
166
144
  def self.generate_resources_routes(res,opt)
167
145
  with_options :controller => res.to_s, :rest => true do |r|
168
- r.add "#{opt[:prefix]}/#{res}/:id[;]edit", :allowed => {:get => 'edit'}
169
- r.add "#{opt[:prefix]}/#{res}/new[;]:action", :allowed => {:get => 'new', :post => 'new', :put => 'new', :delete => 'new'}
146
+ r.add "#{opt[:prefix]}/#{res}/:id[;/]+edit", :allowed => {:get => 'edit'}
147
+ r.add "#{opt[:prefix]}/#{res}/new[;/]+:action", :allowed => {:get => 'new', :post => 'new', :put => 'new', :delete => 'new'}
170
148
  r.add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'}
171
149
  if mem = opt[:member]
172
150
  mem.keys.sort_by{|x| "#{x}"}.each {|action|
173
- allowed = mem[action].inject({}) {|h, verb| h[verb] = "#{action}" ; h}
174
- r.add "#{opt[:prefix]}/#{res}/:id[;]#{action}", :allowed => allowed
151
+ allowed = mem[action].injecting({}) {|h, verb| h[verb] = "#{action}"}
152
+ r.add "#{opt[:prefix]}/#{res}/:id[;/]+#{action}", :allowed => allowed
175
153
  }
176
154
  end
177
155
  if coll = opt[:collection]
178
156
  coll.keys.sort_by{|x| "#{x}"}.each {|action|
179
- allowed = coll[action].inject({}) {|h, verb| h[verb] = "#{action}" ; h}
180
- r.add "#{opt[:prefix]}/#{res}[;]#{action}", :allowed => allowed
157
+ allowed = coll[action].injecting({}) {|h, verb| h[verb] = "#{action}"}
158
+ r.add "#{opt[:prefix]}/#{res}[;/]+#{action}", :allowed => allowed
181
159
  }
182
160
  end
183
161
  r.add "#{opt[:prefix]}/#{res}/:id\\.:format", :allowed => {:get => 'show', :put => 'update', :delete => 'destroy'}
@@ -189,13 +167,21 @@ module Merb
189
167
 
190
168
  def self.generate_singleton_routes(res,opt)
191
169
  with_options :controller => res.to_s, :rest => true do |r|
192
- r.add "#{opt[:prefix]}/#{res}[;]edit", :allowed => {:get => 'edit'}
170
+ r.add "#{opt[:prefix]}/#{res}[;/]+edit", :allowed => {:get => 'edit'}
193
171
  r.add "#{opt[:prefix]}/#{res}\\.:format", :allowed => {:get => 'show'}
194
172
  r.add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'}
195
173
  r.add "#{opt[:prefix]}/#{res}/?", :allowed => {:get => 'show', :post => 'create', :put => 'update', :delete => 'destroy'}
196
174
  end
197
175
  end
198
176
 
177
+ def self.default_routes
178
+ add "/:controller/:action/:id\\.:format"
179
+ add "/:controller/:action/:id"
180
+ add "/:controller/:action\\.:format"
181
+ add "/:controller/:action"
182
+ add "/:controller\\.:format", :action => 'index'
183
+ add "/:controller", :action => 'index'
184
+ end
199
185
  end
200
186
 
201
187
  end
@@ -11,12 +11,13 @@ module Merb
11
11
  buf = ""
12
12
  content_length = @env['CONTENT_LENGTH'].to_i
13
13
  input = request
14
+ input.binmode if defined? input.binmode
14
15
  boundary_size = boundary.size + EOL.size
15
16
  bufsize = 16384
16
17
  content_length -= boundary_size
17
18
  status = input.read(boundary_size)
18
19
  raise EOFError, "bad content body" unless status == boundary + EOL
19
- rx = /(?:#{EOL})?#{Regexp.quote(boundary)}(#{EOL}|--)/
20
+ rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
20
21
 
21
22
  loop {
22
23
  head = nil
@@ -32,7 +33,7 @@ module Merb
32
33
  content_type = head[CONTENT_TYPE_REGEX, 1]
33
34
  name = head[NAME_REGEX, 1]
34
35
 
35
- if filename
36
+ if filename && !filename.empty?
36
37
  body = Tempfile.new(:Merb)
37
38
  body.binmode if defined? body.binmode
38
39
  end
@@ -58,9 +59,14 @@ module Merb
58
59
  content_length = -1 if $1 == "--"
59
60
  end
60
61
 
61
- if filename
62
+ if filename && !filename.empty?
62
63
  body.rewind
63
- data = {:filename => filename, :type => content_type, :tempfile => body}
64
+ data = {
65
+ :filename => File.basename(filename),
66
+ :content_type => content_type,
67
+ :tempfile => body,
68
+ :size => File.size(body)
69
+ }
64
70
  else
65
71
  data = body
66
72
  end
@@ -90,7 +96,7 @@ module Merb
90
96
  # request into the params hash. So for example:
91
97
  # /foo?bar=nik&post[title]=heya&post[body]=whatever
92
98
  # parses into:
93
- # {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever}}
99
+ # {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever'}}
94
100
  def query_parse(qs, d = '&;')
95
101
  m = proc {|_,o,n|o.u(n,&m)rescue([*o]<<n)}
96
102
  (qs||'').split(/[#{d}] */n).inject(MerbHash[]) { |h,p|
@@ -161,6 +167,29 @@ module Merb
161
167
  return
162
168
  end
163
169
 
170
+ # stream_file( { :filename => file_name,
171
+ # :type => content_type,
172
+ # :content_length => content_length }) do
173
+ # @response.send_status(opts[:content_length])
174
+ # @response.send_header
175
+ # AWS::S3::S3Object.stream(user.folder_name + "-" + user_file.unique_id, bucket_name) do |chunk|
176
+ # @response.write chunk
177
+ # end
178
+ # end
179
+ def stream_file(opts={}, &stream)
180
+ opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
181
+ disposition = opts[:disposition].dup || 'attachment'
182
+ disposition << %(; filename="#{opts[:filename]}")
183
+ @response.headers.update(
184
+ 'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
185
+ 'Content-Disposition' => disposition,
186
+ 'Content-Transfer-Encoding' => 'binary',
187
+ 'CONTENT-LENGTH' => opts[:content_length]
188
+ )
189
+ stream
190
+ end
191
+
192
+
164
193
  # This uses nginx X-Accel-Redirect header to send
165
194
  # a file directly from nginx. See the nginx wiki:
166
195
  # http://wiki.codemongers.com/NginxXSendfile