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
@@ -2,6 +2,7 @@ module Merb
2
2
 
3
3
  class AbstractController
4
4
  include Merb::RenderMixin
5
+ include Merb::GeneralControllerMixin
5
6
 
6
7
  class_inheritable_accessor :before_filters
7
8
  class_inheritable_accessor :after_filters
@@ -112,12 +113,20 @@ module Merb
112
113
  end
113
114
  return :filter_chain_completed
114
115
  end
115
-
116
- def finalize_session
116
+
117
+ # +finalize_session+ is called at the end of a request to finalize/store any data placed in the session.
118
+ # Mixins/Classes wishing to define a new session store must implement this method.
119
+ # See merb/lib/sessions/* for examples of built in session stores.
120
+
121
+ def finalize_session #:nodoc:
117
122
  # noop
118
123
  end
119
-
120
- def setup_session
124
+
125
+ # +setup_session+ is called at the beginning of a request to load the current session data.
126
+ # Mixins/Classes wishing to define a new session store must implement this method.
127
+ # See merb/lib/sessions/* for examples of built in session stores.
128
+
129
+ def setup_session #:nodoc:
121
130
  # noop
122
131
  end
123
132
 
@@ -175,21 +184,7 @@ module Merb
175
184
  # throw :halt, Proc.new {|c| Tidy.new(c.index) }
176
185
  #
177
186
  def self.before(filter, opts={})
178
- raise(ArgumentError,
179
- "You can specify either :only or :exclude but
180
- not both at the same time for the same filter."
181
- ) if opts.has_key?(:only) && opts.has_key?(:exclude)
182
-
183
- opts = shuffle_filters!(opts)
184
-
185
- case filter
186
- when Symbol, String, Proc
187
- (self.before_filters ||= []) << [filter, opts]
188
- else
189
- raise(ArgumentError,
190
- 'filters need to be either a Symbol, String or a Proc'
191
- )
192
- end
187
+ add_filter((self.before_filters ||= []), filter, opts)
193
188
  end
194
189
 
195
190
  # #after is a class method that allows you to specify after filters in your
@@ -199,24 +194,82 @@ module Merb
199
194
  # When you use a proc as a filter it needs to take one parameter. You can
200
195
  # gain access to the response body like so: after Proc.new {|c|
201
196
  # Tidy.new(c.body) }, :only => :index
197
+
202
198
  def self.after(filter, opts={})
199
+ add_filter((self.after_filters ||= []), filter, opts)
200
+ end
201
+
202
+ # Call #skip_before to remove an already declared (before) filter from your controller.
203
+ #
204
+ # Example:
205
+ # class Application < Merb::Controller
206
+ # before :require_login
207
+ # end
208
+ #
209
+ # class Login < Merb::Controller
210
+ # skip_before :require_login # Users shouldn't have to be logged in to log in
211
+ # end
212
+
213
+ def self.skip_before(filter)
214
+ skip_filter((self.before_filters || []), filter)
215
+ end
216
+
217
+ # Exactly like #skip_before, but for after filters
218
+
219
+ def self.skip_after(filter)
220
+ skip_filter((self.after_filters || []), filter)
221
+ end
222
+
223
+ def self.default_thrown_content
224
+ Hash.new{ |hash, key| hash[key] = "" }
225
+ end
226
+
227
+ # Set here to respond when rendering to cover the provides syntax of setting the content_type
228
+ def content_type_set?
229
+ false
230
+ end
231
+
232
+ def content_type
233
+ params[:format] || :html
234
+ end
235
+
236
+ private
237
+
238
+ def self.add_filter(filters, filter, opts={})
203
239
  raise(ArgumentError,
204
240
  "You can specify either :only or :exclude but
205
241
  not both at the same time for the same filter."
206
242
  ) if opts.has_key?(:only) && opts.has_key?(:exclude)
207
-
243
+
208
244
  opts = shuffle_filters!(opts)
209
-
245
+
210
246
  case filter
211
247
  when Symbol, Proc, String
212
- (self.after_filters ||= []) << [filter, opts]
248
+ if existing_filter = filters.find {|f| f.first.to_s[filter.to_s]}
249
+ existing_filter.last.replace(opts)
250
+ else
251
+ filters << [filter, opts]
252
+ end
213
253
  else
214
254
  raise(ArgumentError,
215
- 'After filters need to be either a Symbol, String or a Proc'
255
+ 'Filters need to be either a Symbol, String or a Proc'
216
256
  )
217
257
  end
218
258
  end
219
259
 
260
+ def self.skip_filter(filters, filter)
261
+ raise(ArgumentError,
262
+ 'You can only skip filters that have a String or Symbol name.'
263
+ ) unless [Symbol, String].include? filter.class
264
+
265
+ MERB_LOGGER.warn("Filter #{filter} was not found in your filter chain."
266
+ ) unless filters.reject! {|f| f.first.to_s[filter.to_s] }
267
+ end
268
+
269
+ # Ensures that the passed in hash values are always arrays.
270
+ #
271
+ # shuffle_filters!(:only => :new) #=> {:only => [:new]}
272
+
220
273
  def self.shuffle_filters!(opts={})
221
274
  if opts[:only] && opts[:only].is_a?(Symbol)
222
275
  opts[:only] = [opts[:only]]
@@ -227,19 +280,6 @@ module Merb
227
280
  return opts
228
281
  end
229
282
 
230
- def self.default_thrown_content
231
- Hash.new{ |hash, key| hash[key] = "" }
232
- end
233
-
234
- # Set here to respond when rendering to cover the provides syntax of setting the content_type
235
- def content_type_set?
236
- false
237
- end
238
-
239
-
240
- def content_type
241
- params[:format] || :html
242
- end
243
283
  end
244
284
 
245
285
  end
@@ -0,0 +1,20 @@
1
+ module Merb
2
+ module Caching
3
+ module MemcachedStore
4
+
5
+
6
+ def get(name)
7
+ ::Cache.get("fragment:#{name}")
8
+ end
9
+
10
+ def put(name, content = nil)
11
+ ::Cache.put("fragment:#{name}", content)
12
+ content
13
+ end
14
+
15
+ def expire_fragment(name)
16
+ ::Cache.delete(name)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -25,10 +25,8 @@ module Merb
25
25
  MULTIPART_REGEXP = /\Amultipart\/form-data.*boundary=\"?([^\";,]+)/n.freeze
26
26
  HTTP_COOKIE = 'HTTP_COOKIE'.freeze
27
27
  QUERY_STRING = 'QUERY_STRING'.freeze
28
- APPLICATION_JSON = 'application/json'.freeze
29
- TEXT_JSON = 'text/x-json'.freeze
30
- APPLICATION_XML = 'application/xml'.freeze
31
- TEXT_XML = 'text/xml'.freeze
28
+ JSON_MIME_TYPE_REGEXP = %r{^application/json|^text/x-json}.freeze
29
+ XML_MIME_TYPE_REGEXP = %r{^application/xml|^text/xml}.freeze
32
30
  FORM_URL_ENCODED_REGEXP = %r{^application/x-www-form-urlencoded}.freeze
33
31
  UPCASE_CONTENT_TYPE = 'CONTENT_TYPE'.freeze
34
32
  CONTENT_TYPE = "Content-Type".freeze
@@ -6,17 +6,49 @@ module Merb
6
6
  # stream it into a tempfile and pass in the filename and tempfile object
7
7
  # to your controller via params. It also parses the ?query=string and
8
8
  # puts that into params as well.
9
+ #
10
+ # == Sessions
11
+ #
12
+ # Session data can be accessed through the +session+ hash:
13
+ #
14
+ # session[:user_id] = @user.id
15
+ #
16
+ # Session data is available until the user's cookie gets deleted/expires,
17
+ # or until your specific session store expires the data.
18
+ #
19
+ # === Session Store
20
+ #
21
+ # The session store is set in MERB_ROOT/config/merb.yml :
22
+ #
23
+ # :session_store: your_store
24
+ #
25
+ # Out of the box merb supports three session stores
26
+ #
27
+ # cookie:: All data (max 4kb) stored directly in cookie. Data integrity is checked on each request to prevent tampering. (Merb::CookieStore)
28
+ # memory:: Data stored in a class in memory. (Merb::MemorySession)
29
+ # mem_cache:: Data stored in mem_cache. (Merb::MemCacheSession)
30
+ #
31
+ # See the documentation on each session store for more information.
32
+ #
33
+ # You can also use a session store provided by a plugin. For instance, if you have DataMapper you can set
34
+ #
35
+ # :session_store: datamapper
36
+ #
37
+ # In this case session data will be stored in the database, as defined by the merb_datamapper plugin.
38
+ # Similar functionality exists for +activerecord+ and +sequel+ currently.
39
+
9
40
  class Controller < AbstractController
10
41
  class_inheritable_accessor :_session_id_key, :_session_expiry
11
- cattr_accessor :_subclasses
42
+ cattr_accessor :_subclasses, :session_secret_key
12
43
  self._subclasses = []
13
-
44
+ self.session_secret_key = nil
14
45
  self._session_id_key = '_session_id'
15
46
  self._session_expiry = Time.now + Merb::Const::WEEK * 2
16
47
 
17
48
  include Merb::ControllerMixin
18
49
  include Merb::ResponderMixin
19
50
  include Merb::ControllerExceptions
51
+ include Merb::Caching::Actions
20
52
 
21
53
  class << self
22
54
  def inherited(klass)
@@ -173,6 +205,16 @@ module Merb
173
205
  res.size == 1 ? res[0] : res
174
206
  end
175
207
 
208
+ private
209
+
210
+ # This method is here to overwrite the one in the general_controller mixin
211
+ # The method ensures that when a url is generated with a hash, it contains a controller
212
+ def get_controller_for_url_generation(options)
213
+ controller = options[:controller] || params[:controller]
214
+ controller = params[:controller] if controller == :current
215
+ controller
216
+ end
217
+
176
218
  end
177
219
 
178
220
  end
@@ -1,7 +1,7 @@
1
1
  begin
2
2
  require 'ruby2ruby'
3
-
4
- class ParseTreeArray < Array
3
+
4
+ class ParseTreeArray < Array #:nodoc:
5
5
  def self.translate(*args)
6
6
  self.new(ParseTree.translate(*args))
7
7
  end
@@ -36,7 +36,26 @@ begin
36
36
 
37
37
  end
38
38
 
39
+ # Used in mapping controller arguments to the params hash.
40
+ # NOTE: You must have the 'ruby2ruby' gem installed for this to work.
41
+ # Example:
42
+ # (In PostsController)
43
+ # def show(id) #=> id is the same as params[:id]
44
+
39
45
  module GetArgs
46
+
47
+ # Returns an array of method arguments and their default values
48
+ # Example:
49
+ # class Example
50
+ # def hello(one,two="two",three)
51
+ # end
52
+ #
53
+ # def goodbye
54
+ # end
55
+ # end
56
+ #
57
+ # Example.instance_method(:hello).get_args #=> [[:one], [:two, "two"], [:three, "three"]]
58
+ # Example.instance_method(:goodbye).get_args #=> nil
40
59
  def get_args
41
60
  klass, meth = self.to_s.split(/ /).to_a[1][0..-2].split("#")
42
61
  # Remove stupidity for #<Method: Class(Object)#foo>
@@ -45,11 +64,11 @@ begin
45
64
  end
46
65
  end
47
66
 
48
- class UnboundMethod
67
+ class UnboundMethod #:nodoc:
49
68
  include GetArgs
50
69
  end
51
70
 
52
- class Method
71
+ class Method #:nodoc:
53
72
  include GetArgs
54
73
  end
55
74
  rescue LoadError
@@ -21,7 +21,7 @@ class Hash
21
21
  #
22
22
  # == Examples
23
23
  #
24
- # ===Standard
24
+ # === Standard
25
25
  # <user gender='m'>
26
26
  # <age type='integer'>35</age>
27
27
  # <name>Home Simpson</name>
@@ -76,6 +76,8 @@ class Hash
76
76
  end
77
77
 
78
78
  # convert this hash to a query string param
79
+ # {:name => "Bob", :address => {:street => '111 Ruby Ave.', :city => 'Ruby Central', :phones => ['111-111-1111', '222-222-2222']}}
80
+ # #=> "name=Bob&address[city]=Ruby Central&address[phones]=111-111-1111222-222-2222&address[street]=111 Ruby Ave."
79
81
  def to_params
80
82
  result = ''
81
83
  stack = []
@@ -97,24 +99,24 @@ class Hash
97
99
  end
98
100
 
99
101
  # lets through the keys in the argument
100
- # >> {:one => 1, :two => 2, :three => 3}.pass(:one)
101
- # => {:one=>1}
102
+ # $ {:one => 1, :two => 2, :three => 3}.pass(:one)
103
+ # #=> {:one=>1}
102
104
  def pass(*allowed)
103
105
  self.reject { |k,v| ! allowed.include?(k) }
104
106
  end
105
107
  alias only pass
106
108
 
107
109
  # blocks the keys in the arguments
108
- # >> {:one => 1, :two => 2, :three => 3}.block(:one)
109
- # => {:two=>2, :three=>3}
110
+ # $ {:one => 1, :two => 2, :three => 3}.block(:one)
111
+ # #=> {:two=>2, :three=>3}
110
112
  def block(*rejected)
111
113
  self.reject { |k,v| rejected.include?(k) }
112
114
  end
113
115
  alias except block
114
116
 
115
117
  # Converts the hash into xml attributes
116
- # { :one => "ONE", "two"=>"TWO" }.to_xml_attributes
117
- # #=> 'one="ONE" two="TWO"'
118
+ # { :one => "ONE", "two"=>"TWO" }.to_xml_attributes
119
+ # #=> 'one="ONE" two="TWO"'
118
120
  def to_xml_attributes
119
121
  map do |k,v|
120
122
  "#{k.to_s.camelize.sub(/^(.{1,1})/){|m| m.downcase}}=\"#{v}\""
@@ -128,10 +130,10 @@ class Hash
128
130
  # or create the key and add this as the first class
129
131
  #
130
132
  # Example
131
- # @hash[:class] #=> nil
132
- # @hash.add_html_class!(:selected) #=> @hash[:class] == "selected"
133
+ # @hash[:class] #=> nil
134
+ # @hash.add_html_class!(:selected) #=> @hash[:class] == "selected"
133
135
  #
134
- # @hash.add_html_class!("class1 class2") #=> @hash[:class] == "selected class1 class2"
136
+ # @hash.add_html_class!("class1 class2") #=> @hash[:class] == "selected class1 class2"
135
137
  def add_html_class!(html_class)
136
138
  if self[:class]
137
139
  self[:class] = "#{self[:class]} #{html_class}"
@@ -154,6 +156,9 @@ class Hash
154
156
  self
155
157
  end
156
158
 
159
+ # Converts every key to an uppercase string (non-recursive.)
160
+ # {:name => "Bob", "age" => 12, "nick" => "Bobinator"}.environmentize_keys!
161
+ # #=> {"NAME"=>"Bob", "NICK"=>"Bobinator", "AGE"=>12}
157
162
  def environmentize_keys!
158
163
  self.each do |key, value|
159
164
  self[key.to_s.upcase] = delete(key)
@@ -161,7 +166,7 @@ class Hash
161
166
  self
162
167
  end
163
168
 
164
- def method_missing(m,*a)
169
+ def method_missing(m,*a) #:nodoc:
165
170
  m.to_s =~ /=$/ ? self[$`]=a[0] : a==[] ? self[m] : raise(NoMethodError,"#{m}")
166
171
  end
167
172
 
@@ -51,7 +51,7 @@ Inflector.inflections do |inflect|
51
51
  inflect.uncountable(%w(equipment information rice money species series fish sheep))
52
52
  end
53
53
 
54
- module Inflections
54
+ module Inflections #:nodoc:
55
55
 
56
56
  def pluralize
57
57
  Inflector.pluralize(self)
@@ -1,39 +1,79 @@
1
1
  module Kernel
2
- # Example:
3
- # acquire 'foo/bar/*'
4
- # requires all files inside foo/bar - recursive
5
- # can take multiple parameters
6
- def acquire(*files)
7
- files.each do |file|
8
- require file if %w(rb so).any?{|f| File.file?("#{file}.#{f}")}
9
- $:.each do |path|
10
- Dir[File.join(path, file, '*.rb')].each do |file|
11
- require file
12
- end
2
+
3
+ # Loads both gem and library dependencies that are passed in as arguments.
4
+ # Each argument can be:
5
+ # String - single dependency
6
+ # Hash - name => version
7
+ # Array - string dependencies
8
+
9
+ def dependencies(*args)
10
+ args.each do |arg|
11
+ case arg
12
+ when String : dependency(arg)
13
+ when Hash : arg.each { |r,v| dependency(r, v) }
14
+ when Array : arg.each { |r| dependency(r) }
13
15
  end
14
16
  end
15
17
  end
16
18
 
19
+ # Loads the given string as a gem.
20
+ # An optional second parameter of a version string can be specified and is passed to rubygems.
21
+ # If rubygems cannot find the gem it requires the string as a library.
22
+
23
+ def dependency(name, *ver)
24
+ begin
25
+ Gem.activate(name, true, *ver)
26
+ message = "#{Time.now.httpdate}: loading gem '#{name}' from #{__app_file_trace__.first} ..."
27
+ puts(message)
28
+ MERB_LOGGER.info(message)
29
+ rescue LoadError
30
+ # Failed requiring as a gem, let's try loading with a normal require.
31
+ requires(name)
32
+ end
33
+ end
34
+
35
+ # Requires the library string passed in.
36
+ # If the library fails to load then it will display a helpful message.
37
+
38
+ def requires(library)
39
+ # TODO: Extract messages out into a messages file. This will also be the first step towards internationalization.
40
+ # TODO: adjust this message once logging refactor is complete.
41
+ require(library)
42
+ message = "#{Time.now.httpdate}: loading library '#{library}' from #{__app_file_trace__.first} ..."
43
+ puts(message)
44
+ MERB_LOGGER.info(message)
45
+ rescue LoadError
46
+ # TODO: adjust the two messages below to use merb's logger.error/info once logging refactor is complete.
47
+ message = "#{Time.now.httpdate}: <e> Could not find '#{library}' as either a library or gem, loaded from #{__app_file_trace__.first}.\n"
48
+ puts(message)
49
+ MERB_LOGGER.error(message)
50
+
51
+ # Print a helpful message
52
+ message = "#{Time.now.httpdate}: <i> Please be sure that if '#{library}': \n"
53
+ message << "#{Time.now.httpdate}: <i> * is a normal ruby library (file), be sure that the path of the library it is present in the $LOAD_PATH via $LOAD_PATH.unshift(\"/path/to/#{library}\") \n"
54
+ message << "#{Time.now.httpdate}: <i> * is included within a gem, be sure that you are specifying the gem as a dependency \n"
55
+ puts(message)
56
+ MERB_LOGGER.error(message)
57
+ exit() # Missing library/gem must be addressed.
58
+ end
59
+
17
60
  # does a basic require, and prints the message passed as an optional
18
61
  # second parameter if an error occurs.
62
+
19
63
  def rescue_require(sym, message = nil)
20
64
  require sym
21
65
  rescue LoadError, RuntimeError
22
66
  puts message if message
23
67
  end
24
68
 
25
- def dependencies(*args)
26
- args.each do |arg|
27
- case arg
28
- when String : dependency(arg)
29
- when Hash : arg.each { |r,v| dependency(r, v) }
30
- end
31
- end
32
- end
33
-
34
- def dependency(gem, *ver)
35
- Gem.activate(gem, true, *ver)
36
- end
69
+ # Used in MERB_ROOT/dependencies.yml
70
+ # Tells merb which ORM (Object Relational Mapper) you wish to use.
71
+ # Currently merb has plugins to support ActiveRecord, DataMapper, and Sequel.
72
+ #
73
+ # Example
74
+ # $ sudo gem install merb_datamapper # or merb_activerecord or merb_sequel
75
+ # use_orm :datamapper # this line goes in dependencies.yml
76
+ # $ ruby script/generate model MyModel # will use the appropriate generator for your ORM
37
77
 
38
78
  def use_orm(orm)
39
79
  raise "Don't call use_orm more than once" unless
@@ -45,6 +85,15 @@ module Kernel
45
85
  Kernel.dependency(orm_plugin)
46
86
  end
47
87
 
88
+ # Used in MERB_ROOT/dependencies.yml
89
+ # Tells merb which testing framework to use.
90
+ # Currently merb supports rspec and test_unit for testing
91
+ #
92
+ # Example
93
+ # $ sudo gem install rspec
94
+ # use_test :rspec # this line goes in dependencies.yml (or use_test :test_unit)
95
+ # $ ruby script/generate controller MyController # will use the appropriate generator for tests
96
+
48
97
  def use_test(test_framework)
49
98
  test_framework = test_framework.to_sym
50
99
  raise "use_test only supports :rspec and :test_unit currently" unless
@@ -54,8 +103,20 @@ module Kernel
54
103
  Merb::GENERATOR_SCOPE.push(test_framework)
55
104
  end
56
105
 
106
+ # Returns an array with a stack trace of the application's files.
107
+
108
+ def __app_file_trace__
109
+ caller.select do |call|
110
+ call.include?(MERB_ROOT) && !call.include?(MERB_ROOT + "/framework")
111
+ end.map do |call|
112
+ file, line = call.scan(Regexp.new("#{MERB_ROOT}/(.*):(.*)")).first
113
+ "#{file}:#{line}"
114
+ end
115
+ end
116
+
57
117
  # Gives you back the file, line and method of the caller number i
58
- # Example:
118
+ #
119
+ # Example
59
120
  # __caller_info__(1) # -> ['/usr/lib/ruby/1.8/irb/workspace.rb', '52', 'irb_binding']
60
121
 
61
122
  def __caller_info__(i = 1)
@@ -75,7 +136,7 @@ module Kernel
75
136
  # ...,
76
137
  # ...
77
138
  # ]
78
- # Example:
139
+ # Example
79
140
  # __caller_lines__('/usr/lib/ruby/1.8/debug.rb', 122, 2) # ->
80
141
  # [
81
142
  # [ 120, " def check_suspend", false ],
@@ -108,6 +169,19 @@ module Kernel
108
169
  area
109
170
  end
110
171
 
172
+ # Requires ruby-prof (<tt>sudo gem install ruby-prof</tt>)
173
+ # Takes a block and profiles the results of running the block 100 times.
174
+ # The resulting profile is written out to MERB_ROOT/log/#{name}.html.
175
+ # <tt>min</tt> specifies the minimum percentage of the total time a method must take for it to be included in the result.
176
+ #
177
+ # Example
178
+ # __profile__("MyProfile", 5) do
179
+ # 30.times { rand(10)**rand(10) }
180
+ # puts "Profile run"
181
+ # end
182
+ # Assuming that the total time taken for #puts calls was less than 5% of the total time to run, #puts won't appear
183
+ # in the profilel report.
184
+
111
185
  def __profile__(name, min=1)
112
186
  require 'ruby-prof' unless defined?(RubyProf)
113
187
  return_result = ''
@@ -124,8 +198,14 @@ module Kernel
124
198
  end
125
199
 
126
200
  # Extracts an options hash if it is the last item in the args array
201
+ # Used internally in methods that take *args
202
+ #
203
+ # Example
204
+ # def render(*args,&blk)
205
+ # opts = extract_options_from_args!(args) || {}
206
+
127
207
  def extract_options_from_args!(args)
128
- args.pop if args.last.is_a?( Hash )
208
+ args.pop if Hash === args.last
129
209
  end
130
210
 
131
211
  end