merb 0.2.0 → 0.3.0

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