merb 0.4.1 → 0.4.2

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 (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