merb 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/README +138 -56
  2. data/Rakefile +23 -8
  3. data/app_generators/merb/templates/Rakefile +13 -0
  4. data/app_generators/merb/templates/app/helpers/global_helper.rb +1 -1
  5. data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +12 -3
  6. data/app_generators/merb/templates/config/merb.yml +14 -1
  7. data/app_generators/merb/templates/spec/spec_helper.rb +6 -0
  8. data/app_generators/merb/templates/test/test_helper.rb +1 -0
  9. data/lib/merb.rb +27 -7
  10. data/lib/merb/abstract_controller.rb +76 -36
  11. data/lib/merb/caching/store/memcache.rb +20 -0
  12. data/lib/merb/constants.rb +2 -4
  13. data/lib/merb/controller.rb +44 -2
  14. data/lib/merb/core_ext/get_args.rb +23 -4
  15. data/lib/merb/core_ext/hash.rb +16 -11
  16. data/lib/merb/core_ext/inflections.rb +1 -1
  17. data/lib/merb/core_ext/kernel.rb +106 -26
  18. data/lib/merb/core_ext/numeric.rb +1 -1
  19. data/lib/merb/core_ext/string.rb +10 -13
  20. data/lib/merb/dispatcher.rb +2 -2
  21. data/lib/merb/exceptions.rb +3 -1
  22. data/lib/merb/logger.rb +15 -6
  23. data/lib/merb/mail_controller.rb +18 -2
  24. data/lib/merb/mailer.rb +1 -1
  25. data/lib/merb/mixins/controller.rb +64 -228
  26. data/lib/merb/mixins/erubis_capture.rb +1 -1
  27. data/lib/merb/mixins/general_controller.rb +258 -0
  28. data/lib/merb/mixins/render.rb +45 -24
  29. data/lib/merb/mixins/responder.rb +89 -18
  30. data/lib/merb/mixins/view_context.rb +32 -5
  31. data/lib/merb/mixins/web_controller.rb +8 -1
  32. data/lib/merb/mongrel_handler.rb +27 -17
  33. data/lib/merb/part_controller.rb +10 -0
  34. data/lib/merb/request.rb +34 -14
  35. data/lib/merb/router.rb +77 -45
  36. data/lib/merb/server.rb +116 -72
  37. data/lib/merb/session/cookie_store.rb +14 -22
  38. data/lib/merb/session/mem_cache_session.rb +2 -2
  39. data/lib/merb/session/memory_session.rb +12 -1
  40. data/lib/merb/template/erubis.rb +31 -0
  41. data/lib/merb/template/haml.rb +4 -14
  42. data/lib/merb/template/xml_builder.rb +1 -1
  43. data/lib/merb/test/helper.rb +90 -18
  44. data/lib/merb/test/rspec.rb +145 -74
  45. data/lib/merb/version.rb +11 -0
  46. data/lib/merb/view_context.rb +3 -6
  47. data/lib/patch +69 -0
  48. data/lib/tasks/merb.rake +1 -1
  49. data/spec/fixtures/config/environments/environment_config_test.yml +1 -0
  50. data/spec/fixtures/controllers/render_spec_controllers.rb +63 -4
  51. data/spec/fixtures/views/examples/template_throw_content_without_block.html.erb +3 -0
  52. data/spec/fixtures/views/partials/_erubis.html.erb +1 -1
  53. data/spec/merb/abstract_controller_spec.rb +1 -0
  54. data/spec/merb/controller_filters_spec.rb +68 -3
  55. data/spec/merb/controller_spec.rb +35 -68
  56. data/spec/merb/cookie_store_spec.rb +7 -20
  57. data/spec/merb/core_ext_spec.rb +35 -1
  58. data/spec/merb/dispatch_spec.rb +8 -2
  59. data/spec/merb/generator_spec.rb +12 -4
  60. data/spec/merb/mail_controller_spec.rb +33 -0
  61. data/spec/merb/part_controller_spec.rb +33 -1
  62. data/spec/merb/render_spec.rb +74 -0
  63. data/spec/merb/request_spec.rb +43 -0
  64. data/spec/merb/responder_spec.rb +1 -0
  65. data/spec/merb/router_spec.rb +118 -13
  66. data/spec/merb/server_spec.rb +19 -0
  67. data/spec/merb/view_context_spec.rb +31 -3
  68. data/spec/spec_helper.rb +8 -0
  69. data/spec/spec_helpers/url_shared_behaviour.rb +112 -0
  70. metadata +124 -87
@@ -4,7 +4,13 @@ require 'ostruct'
4
4
  require 'fileutils'
5
5
  require 'yaml'
6
6
 
7
+ require File.join(File.dirname(__FILE__), 'erubis_ext')
8
+ require File.join(File.dirname(__FILE__), 'version')
9
+
7
10
  module Merb
11
+
12
+ module GlobalHelper
13
+ end
8
14
 
9
15
  class Config
10
16
  class << self
@@ -13,24 +19,28 @@ module Merb
13
19
  :host => "0.0.0.0",
14
20
  :port => "4000",
15
21
  :reloader => true,
16
- :merb_root => Dir.pwd,
17
22
  :cache_templates => false,
23
+ :merb_root => Dir.pwd,
18
24
  :use_mutex => true,
19
25
  :session_id_cookie_only => true,
20
- :query_string_whitelist => []
26
+ :query_string_whitelist => [],
27
+ :mongrel_x_sendfile => true
21
28
  }
22
29
  end
23
30
 
24
- def setup
31
+ def setup(global_merb_yml = nil)
25
32
  if FileTest.exist? "#{defaults[:merb_root]}/framework"
26
33
  $LOAD_PATH.unshift( "#{defaults[:merb_root]}/framework" )
27
34
  end
28
- merb_yml = "#{defaults[:merb_root]}/config/merb.yml"
29
- if File.exists?(merb_yml)
30
- require 'merb/erubis_ext'
31
- defaults.merge(Erubis.load_yaml_file(merb_yml))
35
+ global_merb_yml ||= "#{defaults[:merb_root]}/config/merb.yml"
36
+ apply_configuration_from_file defaults, global_merb_yml
37
+ end
38
+
39
+ def apply_configuration_from_file(configuration, file)
40
+ if File.exists?(file)
41
+ configuration.merge(Erubis.load_yaml_file(file))
32
42
  else
33
- defaults
43
+ configuration
34
44
  end
35
45
  end
36
46
  end
@@ -40,12 +50,17 @@ module Merb
40
50
 
41
51
  class << self
42
52
 
43
- def merb_config
44
- options = Merb::Config.setup
45
-
53
+ def merb_config(argv = ARGV)
54
+ # Our primary configuration hash for the length of this method
55
+ options = {}
56
+
57
+ # Environment variables always win
58
+ options[:environment] = ENV['MERB_ENV']
59
+
60
+ # Build a parser for the command line arguements
46
61
  opts = OptionParser.new do |opts|
47
- opts.version = "0.4.0"
48
- opts.release = "svn"
62
+ opts.version = Merb::VERSION
63
+ opts.release = Merb::RELEASE
49
64
 
50
65
  opts.banner = "Usage: merb [fdcepghmisluMG] [argument]"
51
66
  opts.define_head "Merb Mongrel+ Erb. Lightweight replacement for ActionPack."
@@ -78,7 +93,13 @@ module Merb
78
93
  end
79
94
 
80
95
  opts.on("-h", "--host HOSTNAME", "Host to bind to (default is all IP's).") do |host|
81
- options[:host] = host
96
+ if host
97
+ options[:host] = host
98
+ else
99
+ # If no host was given, assume they meant they wanted help.
100
+ puts opts
101
+ exit
102
+ end
82
103
  end
83
104
 
84
105
  opts.on("-m", "--merb-root MERB_ROOT", "The path to the MERB_ROOT for the app you want to run (default is current working dir).") do |merb_root|
@@ -100,7 +121,7 @@ module Merb
100
121
  end
101
122
 
102
123
  opts.on("-e", "--environment STRING", "Run merb in the correct mode(development, production, testing)") do |env|
103
- options[:environment] = env
124
+ options[:environment] ||= env
104
125
  end
105
126
 
106
127
  opts.on("-r", "--script-runner ['RUBY CODE'| FULL_SCRIPT_PATH]",
@@ -147,48 +168,34 @@ module Merb
147
168
  opts.on("-?", "-H", "--help", "Show this help message") do
148
169
  puts opts
149
170
  exit
150
- end
171
+ end
151
172
  end
152
173
 
153
- # Special case of looking for help
154
- if ARGV == ["-h"]
155
- puts opts
156
- exit
157
- end
158
- opts.parse!(@@merb_raw_opts)
174
+ # Parse what we have on the command line
175
+ opts.parse!(argv)
176
+
159
177
  # merb <argument> is same as merb -g <argument>
160
- if ARGV.size == 1
161
- options[:generate] = File.expand_path(ARGV.last)
162
- end
163
-
164
- # Added by: Rogelio J. Samour 2007-01-23
165
- # We need to reload the options that exist in the App's merb.yml
166
- # This is needed when one calls merb NOT from the merb_app ROOT
167
- # like so: merb --merb-config /path/to/config/merb.yml -m /path/to/merb/app
168
- # or if we add :merb_root: /path/to/merb/app in the merb.yml we can now only call it
169
- # like so: merb --merb-config /path/to/config/merb.yml
170
- if options[:merb_config]
171
- # Check and see if an environment has been set through the CLI
172
- # if so, save it in a temp. In this case, if merb.yml has an :environment:
173
- # set... it will be overwritten by the CLI. [Rogelio J. Samour] 2007-06-07
174
- if options[:environment]
175
- temp_env = options[:environment]
176
- end
177
- options.merge!(Erubis.load_yaml_file("#{options[:merb_config]}"))
178
- if temp_env
179
- options[:environment] = temp_env
180
- end
181
- end
182
-
183
- if ENV['MERB_ENV']
184
- # ENV['MERB_ENV'] has precedence over all
185
- options[:environment] = ENV['MERB_ENV']
178
+ if argv.size == 1
179
+ options[:generate] = File.expand_path(argv.last)
186
180
  end
181
+
182
+ # If we run merb with no arguments and we are not inside a merb root
183
+ # show the help message
184
+ if (argv.size == 0) && !File.exists?("#{options[:merb_root] || Merb::Config.defaults[:merb_root]}/config/merb_init.rb") && !$TESTING
185
+ puts "You are not in the root of a merb application...\n"
186
+ puts opts
187
+ exit
188
+ end
189
+ # Load up the configuration from file, but keep the command line
190
+ # options that may have been chosen. Also, pass-through if we have
191
+ # a new merb_config path.
192
+ options = Merb::Config.setup(options[:merb_config]).merge(options)
187
193
 
188
194
  # Finally, if all else fails... set the environment to 'development'
189
- if options[:environment].nil?
190
- options[:environment] = 'development'
191
- end
195
+ options[:environment] ||= 'development'
196
+
197
+ environment_merb_yml = "#{options[:merb_root]}/config/environments/#{options[:environment]}.yml"
198
+ options = Merb::Config.apply_configuration_from_file options, environment_merb_yml
192
199
 
193
200
  @@merb_opts = options
194
201
  end
@@ -213,12 +220,24 @@ module Merb
213
220
  if ["", "false"].include?(session_store)
214
221
  puts "Not Using Sessions"
215
222
  elsif reg = types[session_store]
223
+ if session_store == "cookie"
224
+ unless @@merb_opts[:session_secret_key] && (@@merb_opts[:session_secret_key].length >= 16)
225
+ puts("You must specify a session_secret_key in your merb.yml, and it must be at least 16 characters\nbailing out...")
226
+ exit!
227
+ end
228
+ Merb::Controller.session_secret_key = @@merb_opts[:session_secret_key]
229
+ end
216
230
  require reg[:file]
217
231
  include ::Merb::SessionMixin
218
232
  puts reg[:description]
219
233
  else
220
234
  puts "Session store not found, '#{Merb::Server.config[:session_store]}'."
221
235
  puts "Defaulting to CookieStore Sessions"
236
+ unless @@merb_opts[:session_secret_key] && (@@merb_opts[:session_secret_key].length >= 16)
237
+ puts("You must specify a session_secret_key in your merb.yml, and it must be at least 16 characters\nbailing out...")
238
+ exit!
239
+ end
240
+ Merb::Controller.session_secret_key = @@merb_opts[:session_secret_key]
222
241
  require types['cookie'][:file]
223
242
  include ::Merb::SessionMixin
224
243
  puts "(plugin not installed?)"
@@ -274,21 +293,22 @@ module Merb
274
293
  end if defined?(ParseTreeArray)
275
294
  end
276
295
 
277
- def load_controller_template_path_cache
278
-
279
- # This gets all templates set in the controllers template roots
296
+ def template_paths(type = "*")
297
+ # This gets all templates set in the controllers template roots
280
298
  template_paths = Merb::AbstractController._abstract_subclasses.map do |klass|
281
299
  Object.full_const_get(klass)._template_root
282
300
  end.uniq.map do |path|
283
- Dir["#{path}/**/*"]
301
+ Dir["#{path}/**/#{type}"]
284
302
  end
285
303
 
286
304
  # This gets the templates that might be created outside controllers
287
305
  # template roots. eg app/views/shared/*
288
- template_paths << Dir["#{MERB_ROOT}/app/views/**/*"]
289
-
290
- template_paths = template_paths.flatten.compact.uniq
306
+ template_paths << Dir["#{MERB_ROOT}/app/views/**/*"] if type == "*"
291
307
 
308
+ template_paths.flatten.compact.uniq || []
309
+ end
310
+
311
+ def load_controller_template_path_cache
292
312
  Merb::AbstractController.reset_template_path_cache!
293
313
 
294
314
  template_paths.each do |template|
@@ -296,12 +316,22 @@ module Merb
296
316
  end
297
317
  end
298
318
 
319
+ def load_erubis_inline_helpers
320
+ partials = template_paths("_*.erb")
321
+
322
+ partials.each do |partial|
323
+ eruby = Erubis::Eruby.new(File.read(partial))
324
+ eruby.def_method(Merb::GlobalHelper, partial.gsub(/[^\.a-zA-Z0-9]/, "__").gsub(/\./, "_"), partial)
325
+ end
326
+ end
327
+
299
328
  def load_application
300
329
  MERB_PATHS.each do |glob|
301
330
  Dir[MERB_ROOT + glob].each { |m| require m }
302
331
  end
303
332
  load_action_arguments
304
333
  load_controller_template_path_cache
334
+ load_erubis_inline_helpers
305
335
  @app_loaded = true
306
336
  (@after_app_blocks || []).each { |b| b.call }
307
337
  end
@@ -317,9 +347,11 @@ module Merb
317
347
 
318
348
  def reload
319
349
  return if !@@merb_opts[:reloader] || !Object.const_defined?(:MERB_PATHS)
350
+
320
351
  # First we collect all files in the project (this will also grab newly added files)
321
352
  project_files = MERB_PATHS.map { |path| Dir[@@merb_opts[:merb_root] + path] }.flatten.uniq
322
- project_mtime = max_mtime project_files # Latest changed time of all project files
353
+ erb_partials = template_paths("_*.erb").map { |path| Dir[path] }.flatten.uniq
354
+ project_mtime = max_mtime(project_files + erb_partials) # Latest changed time of all project files
323
355
 
324
356
  return if @mtime.nil? || @mtime >= project_mtime # Only continue if a file has changed
325
357
 
@@ -346,7 +378,6 @@ module Merb
346
378
  load(file)
347
379
  loaded_classes = Merb::Controller._subclasses - old_subclasses
348
380
  load_action_arguments(loaded_classes)
349
- load_controller_template_path_cache
350
381
  rescue Exception => e
351
382
  puts "Error reloading file #{file}: #{e}"
352
383
  MERB_LOGGER.warn " Error: #{e}"
@@ -357,13 +388,16 @@ module Merb
357
388
  # load file and puts "loaded file: #{file}"
358
389
  end
359
390
  end
360
-
391
+
392
+ # Rebuild the glob cache and erubis inline helpers
393
+ load_controller_template_path_cache
394
+ load_erubis_inline_helpers
395
+
361
396
  @mtime = project_mtime # As the last action, update the current @mtime
362
397
  end
363
398
 
364
399
  def run
365
- @@merb_raw_opts = ARGV
366
- merb_config
400
+ load_config
367
401
 
368
402
  if @@merb_opts[:generate] #|| @@merb_opts.size == 1
369
403
  require 'merb/generators/merb_app/merb_app'
@@ -424,7 +458,7 @@ module Merb
424
458
  Thread.new do
425
459
  loop do
426
460
  sleep( @@merb_opts[:reloader_time] )
427
- reload
461
+ reload if app_loaded?
428
462
  end
429
463
  Thread.exit
430
464
  end
@@ -434,13 +468,18 @@ module Merb
434
468
  initialize_merb
435
469
  _merb = Class.new do
436
470
  def self.show_routes(all_opts = false)
437
- puts "== show_routes(all_opts = #{all_opts}) =="
438
- puts "RouteMatcher:"
439
- Merb::Router.matcher.routes.each {|route,opts| puts " #{route}" + (all_opts ? " " + opts.inspect : "") }
440
- puts
441
- puts "RouteGenerator:"
442
- # Sort alphabetically by the url part of the route for easier reading.
443
- Merb::Router.generator.paths.sort {|a,b| a.last <=> b.last }.each {|p| puts " " + p.inspect}
471
+ seen = []
472
+ unless Merb::Router.named_routes.empty?
473
+ puts "Named Routes"
474
+ Merb::Router.named_routes.each do |name,route|
475
+ puts " #{name}: #{route}"
476
+ seen << route
477
+ end
478
+ end
479
+ puts "Anonymous Routes"
480
+ (Merb::Router.routes - seen).each do |route|
481
+ puts " #{route}"
482
+ end
444
483
  nil
445
484
  end
446
485
 
@@ -580,7 +619,7 @@ module Merb
580
619
  mconfig = Mongrel::Configurator.new(mconf_hash) do
581
620
  listener do
582
621
  uri( "/", :handler => MerbUploadHandler.new(@@merb_opts), :in_front => true) if @@merb_opts[:upload_path_match]
583
- uri "/", :handler => MerbHandler.new(@@merb_opts[:merb_root]+'/public')
622
+ uri "/", :handler => MerbHandler.new(@@merb_opts[:merb_root]+'/public', @@merb_opts[:mongrel_x_sendfile])
584
623
  uri "/favicon.ico", :handler => Mongrel::Error404Handler.new("")
585
624
  end
586
625
  MerbHandler.path_prefix = @@merb_opts[:path_prefix]
@@ -593,6 +632,11 @@ module Merb
593
632
 
594
633
  def config
595
634
  @@merb_opts
635
+ end
636
+
637
+ def load_config
638
+ @@merb_raw_opts = ARGV
639
+ merb_config
596
640
  end
597
641
 
598
642
  end # class << self
@@ -5,19 +5,19 @@ require 'openssl' # to generate the HMAC message digest
5
5
 
6
6
  module Merb
7
7
 
8
- module SessionMixin
8
+ module SessionMixin #:nodoc:
9
9
  def setup_session
10
- MERB_LOGGER.info("Setting Cookie Store Sessions")
11
- unless secret = Merb::Server.config[:session_secret_key]
12
- raise 'You must set :session_secret_key in config/merb.yml for cookie sessions'
13
- end
14
- request.session = Merb::CookieStore.new(cookies[_session_id_key], secret)
10
+ MERB_LOGGER.info("Setting Up Cookie Store Sessions")
11
+ request.session = Merb::CookieStore.new(cookies[_session_id_key], session_secret_key)
12
+ @original_session = request.session.read_cookie
15
13
  end
16
14
 
17
- def finalize_session
15
+ def finalize_session
18
16
  MERB_LOGGER.info("Finalize Cookie Store Session")
19
- if request.session.modified?
20
- set_cookie(_session_id_key, request.session.read_cookie, _session_expiry)
17
+ new_session = request.session.read_cookie
18
+
19
+ if @original_session != new_session
20
+ set_cookie(_session_id_key, new_session, _session_expiry)
21
21
  end
22
22
  end
23
23
 
@@ -33,12 +33,12 @@ module Merb
33
33
  # TamperedWithCookie is raised if the data integrity check fails.
34
34
  #
35
35
  # A message digest is included with the cookie to ensure data integrity:
36
- # a user cannot alter his user_id without knowing the secret key included in
37
- # the hash.
36
+ # a user cannot alter session data without knowing the secret key included
37
+ # in the hash.
38
38
  #
39
39
  # To use Cookie Sessions, set in config/merb.yml
40
40
  # :session_secret_key - your secret digest key
41
- # :cookie_store_session - to true
41
+ # :session_store: cookie
42
42
  class CookieStore
43
43
  # TODO (maybe):
44
44
  # include request ip address
@@ -62,7 +62,6 @@ module Merb
62
62
  end
63
63
  @secret = secret
64
64
  @data = unmarshal(cookie) || Hash.new
65
- @modified = false
66
65
  end
67
66
 
68
67
  # return a cookie value. raises CookieOverflow if session contains too
@@ -75,15 +74,9 @@ module Merb
75
74
  end
76
75
  end
77
76
 
78
- # returns true if the session has been modified since initialization
79
- def modified?; @modified; end
80
-
81
77
  # assigns a key value pair
82
78
  def []=(k, v)
83
- unless @data[k]==v
84
- @modified = true
85
- @data[k] = v
86
- end
79
+ @data[k] = v
87
80
  end
88
81
 
89
82
  def [](k)
@@ -92,8 +85,7 @@ module Merb
92
85
 
93
86
  # delete a key
94
87
  def delete(key)
95
- if @data.has_key(key)
96
- @modified = true
88
+ if @data.has_key?(key)
97
89
  @data.delete(key)
98
90
  end
99
91
  end
@@ -2,7 +2,7 @@ require 'memcache_util'
2
2
 
3
3
  module Merb
4
4
 
5
- module SessionMixin
5
+ module SessionMixin #:nodoc:
6
6
 
7
7
  def setup_session
8
8
  MERB_LOGGER.info("Setting up session")
@@ -68,7 +68,7 @@ module Merb
68
68
  # No cookie...make a new session_id
69
69
  session = generate
70
70
  end
71
- if session.is_a?( MemCacheSession )
71
+ if session.is_a?(MemCacheSession)
72
72
  [session, session.session_id]
73
73
  else
74
74
  # recreate using the rails session as the data
@@ -1,6 +1,6 @@
1
1
  module Merb
2
2
 
3
- module SessionMixin
3
+ module SessionMixin #:nodoc:
4
4
 
5
5
  def setup_session
6
6
  MERB_LOGGER.info("Setting up session")
@@ -19,6 +19,17 @@ module Merb
19
19
  end
20
20
  end
21
21
 
22
+ ##
23
+ # Sessions stored in memory.
24
+ #
25
+ # And a setting in +merb.yml+:
26
+ #
27
+ # :session_store: mem_cache
28
+ # :memory_session_ttl: 3600 (in seconds, one hour)
29
+ #
30
+ # Sessions will remain in memory until the server is stopped or the time
31
+ # as set in :memory_session_ttl expires.
32
+
22
33
  class MemorySession
23
34
 
24
35
  attr_accessor :session_id