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