merb-core 0.9.5 → 0.9.6

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 (78) hide show
  1. data/CHANGELOG +925 -0
  2. data/CONTRIBUTORS +93 -0
  3. data/PUBLIC_CHANGELOG +85 -0
  4. data/Rakefile +18 -28
  5. data/bin/merb +34 -5
  6. data/lib/merb-core/autoload.rb +2 -3
  7. data/lib/merb-core/bootloader.rb +60 -66
  8. data/lib/merb-core/config.rb +7 -1
  9. data/lib/merb-core/controller/abstract_controller.rb +35 -21
  10. data/lib/merb-core/controller/merb_controller.rb +15 -42
  11. data/lib/merb-core/controller/mixins/authentication.rb +42 -6
  12. data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
  13. data/lib/merb-core/controller/mixins/render.rb +3 -3
  14. data/lib/merb-core/core_ext/kernel.rb +6 -19
  15. data/lib/merb-core/dispatch/cookies.rb +96 -80
  16. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +2 -0
  17. data/lib/merb-core/dispatch/request.rb +18 -16
  18. data/lib/merb-core/dispatch/router/route.rb +6 -0
  19. data/lib/merb-core/dispatch/router.rb +4 -1
  20. data/lib/merb-core/dispatch/session/container.rb +64 -0
  21. data/lib/merb-core/dispatch/session/cookie.rb +91 -101
  22. data/lib/merb-core/dispatch/session/memcached.rb +38 -174
  23. data/lib/merb-core/dispatch/session/memory.rb +62 -208
  24. data/lib/merb-core/dispatch/session/store_container.rb +145 -0
  25. data/lib/merb-core/dispatch/session.rb +174 -48
  26. data/lib/merb-core/rack/middleware/conditional_get.rb +14 -8
  27. data/lib/merb-core/rack/middleware/csrf.rb +73 -0
  28. data/lib/merb-core/rack.rb +1 -0
  29. data/lib/merb-core/script.rb +112 -0
  30. data/lib/merb-core/server.rb +2 -0
  31. data/lib/merb-core/tasks/merb_rake_helper.rb +25 -0
  32. data/lib/merb-core/test/helpers/request_helper.rb +40 -3
  33. data/lib/merb-core/test/run_specs.rb +4 -3
  34. data/lib/merb-core/vendor/facets/inflect.rb +7 -10
  35. data/lib/merb-core/version.rb +1 -1
  36. data/lib/merb-core.rb +11 -40
  37. data/spec/private/core_ext/kernel_spec.rb +0 -11
  38. data/spec/private/dispatch/fixture/log/merb_test.log +893 -0
  39. data/spec/private/router/fixture/log/merb_test.log +12 -1728
  40. data/spec/private/router/route_spec.rb +4 -0
  41. data/spec/private/router/router_spec.rb +8 -0
  42. data/spec/private/vendor/facets/plural_spec.rb +1 -1
  43. data/spec/private/vendor/facets/singular_spec.rb +1 -1
  44. data/spec/public/abstract_controller/controllers/display.rb +8 -2
  45. data/spec/public/abstract_controller/controllers/filters.rb +18 -0
  46. data/spec/public/abstract_controller/display_spec.rb +6 -2
  47. data/spec/public/abstract_controller/filter_spec.rb +4 -0
  48. data/spec/public/controller/authentication_spec.rb +114 -43
  49. data/spec/public/controller/base_spec.rb +8 -0
  50. data/spec/public/controller/conditional_get_spec.rb +100 -0
  51. data/spec/public/controller/config/init.rb +1 -1
  52. data/spec/public/controller/controllers/authentication.rb +29 -0
  53. data/spec/public/controller/controllers/base.rb +13 -0
  54. data/spec/public/controller/controllers/conditional_get.rb +35 -0
  55. data/spec/public/controller/controllers/cookies.rb +10 -1
  56. data/spec/public/controller/cookies_spec.rb +38 -9
  57. data/spec/public/controller/spec_helper.rb +1 -0
  58. data/spec/public/controller/url_spec.rb +70 -1
  59. data/spec/public/directory_structure/directory/log/merb_test.log +461 -0
  60. data/spec/public/rack/conditinal_get_middleware_spec.rb +77 -89
  61. data/spec/public/rack/csrf_middleware_spec.rb +70 -0
  62. data/spec/public/reloading/directory/log/merb_test.log +52 -0
  63. data/spec/public/request/request_spec.rb +19 -1
  64. data/spec/public/router/fixation_spec.rb +26 -4
  65. data/spec/public/router/fixture/log/merb_test.log +234 -30332
  66. data/spec/public/session/controllers/sessions.rb +52 -0
  67. data/spec/public/session/cookie_session_spec.rb +73 -0
  68. data/spec/public/session/memcached_session_spec.rb +31 -0
  69. data/spec/public/session/memory_session_spec.rb +28 -0
  70. data/spec/public/session/multiple_sessions_spec.rb +74 -0
  71. data/spec/public/session/no_session_spec.rb +12 -0
  72. data/spec/public/session/session_spec.rb +91 -0
  73. data/spec/public/test/controllers/spec_helper_controller.rb +2 -1
  74. data/spec/public/test/request_helper_spec.rb +15 -0
  75. data/spec/spec_helper.rb +2 -2
  76. metadata +23 -5
  77. data/spec/private/dispatch/cookies_spec.rb +0 -219
  78. data/spec/private/dispatch/session_mixin_spec.rb +0 -47
@@ -27,7 +27,7 @@
27
27
  # before :some_filter
28
28
  # before :authenticate, :exclude => [:login, :signup]
29
29
  # before :has_role, :with => ["Admin"], :exclude => [:index, :show]
30
- # before Proc.new {|c| c.some_method }, :only => :foo
30
+ # before Proc.new { some_method }, :only => :foo
31
31
  # before :authorize, :unless => :logged_in?
32
32
  #
33
33
  # You can use either <code>:only => :actionname</code> or
@@ -64,8 +64,8 @@
64
64
  # If the second arg is a Proc, it will be called and its return
65
65
  # value will be what is rendered to the browser:
66
66
  #
67
- # throw :halt, proc {|c| c.access_denied }
68
- # throw :halt, proc {|c| Tidy.new(c.index) }
67
+ # throw :halt, proc { access_denied }
68
+ # throw :halt, proc { Tidy.new(c.index) }
69
69
  #
70
70
  # ===== Filter Options (.before, .after, .add_filter, .if, .unless)
71
71
  # :only<Symbol, Array[Symbol]>::
@@ -98,6 +98,7 @@ class Merb::AbstractController
98
98
 
99
99
  class_inheritable_accessor :_layout, :_template_root, :template_roots
100
100
  class_inheritable_accessor :_before_filters, :_after_filters
101
+ class_inheritable_accessor :_before_dispatch_callbacks, :_after_dispatch_callbacks
101
102
 
102
103
  cattr_accessor :_abstract_subclasses
103
104
 
@@ -113,6 +114,7 @@ class Merb::AbstractController
113
114
  FILTER_OPTIONS = [:only, :exclude, :if, :unless, :with]
114
115
 
115
116
  self._before_filters, self._after_filters = [], []
117
+ self._before_dispatch_callbacks, self._after_dispatch_callbacks = [], []
116
118
 
117
119
  #---
118
120
  # We're using abstract_subclasses so that Merb::Controller can have its
@@ -217,7 +219,7 @@ class Merb::AbstractController
217
219
  include Object.full_const_get("#{helper_module_name}") rescue nil
218
220
  HERE
219
221
  super
220
- end
222
+ end
221
223
  end
222
224
 
223
225
  # ==== Parameters
@@ -228,7 +230,7 @@ class Merb::AbstractController
228
230
  @_template_stack = []
229
231
  end
230
232
 
231
- # This will dispatch the request, calling setup_session and finalize_session
233
+ # This will dispatch the request, calling internal before/after dispatch_callbacks
232
234
  #
233
235
  # ==== Parameters
234
236
  # action<~to_s>::
@@ -238,7 +240,7 @@ class Merb::AbstractController
238
240
  # ==== Raises
239
241
  # MerbControllerError:: Invalid body content caught.
240
242
  def _dispatch(action)
241
- setup_session
243
+ self._before_dispatch_callbacks.each { |cb| cb.call(self) }
242
244
  self.action_name = action
243
245
 
244
246
  caught = catch(:halt) do
@@ -253,14 +255,16 @@ class Merb::AbstractController
253
255
  when String then caught
254
256
  when nil then _filters_halted
255
257
  when Symbol then __send__(caught)
256
- when Proc then caught.call(self)
258
+ when Proc then self.instance_eval(&caught)
257
259
  else
258
260
  raise ArgumentError, "Threw :halt, #{caught}. Expected String, nil, Symbol, Proc."
259
261
  end
260
262
  start = Time.now
261
263
  _call_filters(_after_filters)
262
264
  @_benchmarks[:after_filters_time] = Time.now - start if _after_filters
263
- finalize_session
265
+
266
+ self._after_dispatch_callbacks.each { |cb| cb.call(self) }
267
+
264
268
  @body
265
269
  end
266
270
 
@@ -298,7 +302,7 @@ class Merb::AbstractController
298
302
  else
299
303
  send(filter)
300
304
  end
301
- when Proc then self.instance_eval(&filter)
305
+ when Proc then self.instance_eval(&filter)
302
306
  end
303
307
  end
304
308
  end
@@ -360,7 +364,7 @@ class Merb::AbstractController
360
364
  def _evaluate_condition(condition)
361
365
  case condition
362
366
  when Symbol : self.send(condition)
363
- when Proc : condition.call(self)
367
+ when Proc : self.instance_eval(&condition)
364
368
  else
365
369
  raise ArgumentError,
366
370
  'Filter condtions need to be either a Symbol or a Proc'
@@ -412,14 +416,6 @@ class Merb::AbstractController
412
416
  #---
413
417
  # Defaults that can be overridden by plugins, other mixins, or subclasses
414
418
  def _filters_halted() "<html><body><h1>Filter Chain Halted!</h1></body></html>" end
415
-
416
- # Method stub for setting up the session. This will be overriden by session
417
- # modules.
418
- def setup_session() end
419
-
420
- # Method stub for finalizing up the session. This will be overriden by
421
- # session modules.
422
- def finalize_session() end
423
419
 
424
420
  # ==== Parameters
425
421
  # name<~to_sym, Hash>:: The name of the URL to generate.
@@ -453,11 +449,24 @@ class Merb::AbstractController
453
449
  # ==== Returns
454
450
  # String:: The generated url with protocol + hostname + URL.
455
451
  #
452
+ # ==== Options
453
+ #
454
+ # :protocol and :host options are special: use them to explicitly
455
+ # specify protocol and host of resulting url. If you omit them,
456
+ # protocol and host of request are used.
457
+ #
456
458
  # ==== Alternatives
457
459
  # If a hash is used as the first argument, a default route will be
458
460
  # generated based on it and rparams.
459
461
  def absolute_url(name, rparams={})
460
- request.protocol + request.host + url(name, rparams)
462
+ # FIXME: arrgh, why request.protocol returns http://?
463
+ # :// is not part of protocol name
464
+ protocol = rparams.delete(:protocol)
465
+ protocol << "://" if protocol
466
+
467
+ (protocol || request.protocol) +
468
+ (rparams.delete(:host) || request.host) +
469
+ url(name, rparams)
461
470
  end
462
471
 
463
472
  # Calls the capture method for the selected template engine.
@@ -506,9 +515,14 @@ class Merb::AbstractController
506
515
  end
507
516
 
508
517
  opts = normalize_filters!(opts)
509
-
518
+
510
519
  case filter
511
- when Symbol, Proc, String
520
+ when Proc
521
+ # filters with procs created via class methods have identical signature
522
+ # regardless if they handle content differently or not. So procs just
523
+ # get appended
524
+ filters << [filter, opts]
525
+ when Symbol, String
512
526
  if existing_filter = filters.find {|f| f.first.to_s[filter.to_s]}
513
527
  filters[ filters.index(existing_filter) ] = [filter, opts]
514
528
  else
@@ -1,24 +1,19 @@
1
1
  class Merb::Controller < Merb::AbstractController
2
2
 
3
- class_inheritable_accessor :_hidden_actions, :_shown_actions,
4
- :_session_id_key, :_session_secret_key, :_session_expiry, :_session_cookie_domain
3
+ class_inheritable_accessor :_hidden_actions, :_shown_actions
5
4
 
6
5
  self._hidden_actions ||= []
7
- self._shown_actions ||= []
8
-
6
+ self._shown_actions ||= []
7
+
9
8
  cattr_accessor :_subclasses
10
9
  self._subclasses = Set.new
11
10
 
12
11
  def self.subclasses_list() _subclasses end
13
12
 
14
- self._session_secret_key = nil
15
- self._session_id_key = Merb::Config[:session_id_key] || '_session_id'
16
- self._session_expiry = Merb::Config[:session_expiry] || Merb::Const::WEEK * 2
17
- self._session_cookie_domain = Merb::Config[:session_cookie_domain]
18
-
19
13
  include Merb::ResponderMixin
20
14
  include Merb::ControllerMixin
21
15
  include Merb::AuthenticationMixin
16
+ include Merb::ConditionalGetMixin
22
17
 
23
18
  class << self
24
19
 
@@ -102,7 +97,7 @@ class Merb::Controller < Merb::AbstractController
102
97
  end
103
98
 
104
99
  private
105
-
100
+
106
101
  # All methods that are callable as actions.
107
102
  #
108
103
  # ==== Returns
@@ -140,15 +135,15 @@ class Merb::Controller < Merb::AbstractController
140
135
  def _template_location(context, type, controller)
141
136
  _conditionally_append_extension(controller ? "#{controller}/#{context}" : "#{context}", type)
142
137
  end
143
-
144
- # The location to look for a template and mime-type. This is overridden
145
- # from AbstractController, which defines a version of this that does not
138
+
139
+ # The location to look for a template and mime-type. This is overridden
140
+ # from AbstractController, which defines a version of this that does not
146
141
  # involve mime-types.
147
142
  #
148
143
  # ==== Parameters
149
- # template<String>::
144
+ # template<String>::
150
145
  # The absolute path to a template - without mime and template extension.
151
- # The mime-type extension is optional - it will be appended from the
146
+ # The mime-type extension is optional - it will be appended from the
152
147
  # current content type if it hasn't been added already.
153
148
  # type<~to_s>::
154
149
  # The mime-type of the template that will be rendered. Defaults to nil.
@@ -163,11 +158,8 @@ class Merb::Controller < Merb::AbstractController
163
158
  # Sets the variables that came in through the dispatch as available to
164
159
  # the controller.
165
160
  #
166
- # This method uses the :session_id_cookie_only and :query_string_whitelist
167
- # configuration options. See CONFIG for more details.
168
- #
169
161
  # ==== Parameters
170
- # request<Merb::Request>:: The Merb::Request that came in from Mongrel.
162
+ # request<Merb::Request>:: The Merb::Request that came in from Rack.
171
163
  # status<Integer>:: An integer code for the status. Defaults to 200.
172
164
  # headers<Hash{header => value}>::
173
165
  # A hash of headers to start the controller with. These headers can be
@@ -204,7 +196,7 @@ class Merb::Controller < Merb::AbstractController
204
196
  end
205
197
 
206
198
  attr_reader :request, :headers
207
-
199
+
208
200
  def status
209
201
  @_status
210
202
  end
@@ -227,20 +219,6 @@ class Merb::Controller < Merb::AbstractController
227
219
  # Hash:: The parameters from the request object
228
220
  def params() request.params end
229
221
 
230
- # ==== Returns
231
- # Merb::Cookies::
232
- # A new Merb::Cookies instance representing the cookies that came in
233
- # from the request object
234
- #
235
- # ==== Notes
236
- # Headers are passed into the cookie object so that you can do:
237
- # cookies[:foo] = "bar"
238
- def cookies() @_cookies ||= _setup_cookies end
239
-
240
- # ==== Returns
241
- # Hash:: The session that was extracted from the request object.
242
- def session() request.session end
243
-
244
222
  # The results of the controller's render, to be returned to Rack.
245
223
  #
246
224
  # ==== Returns
@@ -249,19 +227,14 @@ class Merb::Controller < Merb::AbstractController
249
227
  def rack_response
250
228
  [status, headers, body]
251
229
  end
252
-
230
+
253
231
  # Hide any methods that may have been exposed as actions before.
254
232
  hide_action(*_callable_methods)
255
-
233
+
256
234
  private
257
235
 
258
236
  # If not already added, add the proper mime extension to the template path.
259
237
  def _conditionally_append_extension(template, type)
260
238
  type && !template.match(/\.#{type.to_s.escape_regexp}$/) ? "#{template}.#{type}" : template
261
239
  end
262
-
263
- # Create a default cookie jar, and pre-set a fixation cookie if fixation is enabled.
264
- def _setup_cookies
265
- ::Merb::Cookies.new(request.cookies, @headers)
266
- end
267
- end
240
+ end
@@ -45,10 +45,27 @@ module Merb::AuthenticationMixin
45
45
  #
46
46
  # end
47
47
  #
48
+ # If you need to request basic authentication inside an action you need to use the request! method.
49
+ #
50
+ # ====Example
51
+ #
52
+ # class Sessions < Application
53
+ #
54
+ # def new
55
+ # case content_type
56
+ # when :html
57
+ # render
58
+ # else
59
+ # basic_authentication.request!
60
+ # end
61
+ # end
62
+ #
63
+ # end
64
+ #
48
65
  #---
49
66
  # @public
50
67
  def basic_authentication(realm = "Application", &authenticator)
51
- BasicAuthentication.new(self, realm, &authenticator)
68
+ @_basic_authentication ||= BasicAuthentication.new(self, realm, &authenticator)
52
69
  end
53
70
 
54
71
  class BasicAuthentication
@@ -58,24 +75,43 @@ module Merb::AuthenticationMixin
58
75
  def initialize(controller, realm = "Application", &authenticator)
59
76
  @controller = controller
60
77
  @realm = realm
78
+ @auth = Rack::Auth::Basic::Request.new(@controller.request.env)
61
79
  authenticate_or_request(&authenticator) if authenticator
62
80
  end
63
81
 
64
82
  def authenticate(&authenticator)
65
- auth = Rack::Auth::Basic::Request.new(@controller.request.env)
66
-
67
- if auth.provided? and auth.basic?
68
- authenticator.call(*auth.credentials)
83
+ if @auth.provided? and @auth.basic?
84
+ authenticator.call(*@auth.credentials)
69
85
  else
70
86
  false
71
87
  end
72
88
  end
73
89
 
74
90
  def request
75
- @controller.headers['WWW-Authenticate'] = 'Basic realm="%s"' % @realm
91
+ request!
76
92
  throw :halt, @controller.render("HTTP Basic: Access denied.\n", :status => Unauthorized.status, :layout => false)
77
93
  end
78
94
 
95
+ # This is a special case for use outside a before filter. Use this if you need to
96
+ # request basic authenticaiton as part of an action
97
+ def request!
98
+ @controller.status = Unauthorized.status
99
+ @controller.headers['WWW-Authenticate'] = 'Basic realm="%s"' % @realm
100
+ end
101
+
102
+ # Checks to see if there has been any basic authentication credentials provided
103
+ def provided?
104
+ @auth.provided?
105
+ end
106
+
107
+ def username
108
+ provided? ? @auth.credentials.first : nil
109
+ end
110
+
111
+ def password
112
+ provided? ? @auth.credentials.last : nil
113
+ end
114
+
79
115
  protected
80
116
 
81
117
  def authenticate_or_request(&authenticator)
@@ -0,0 +1,83 @@
1
+ # Provides conditional get support in Merb core.
2
+ # Conditional get support is intentionally
3
+ # simple and does not do fancy stuff like making
4
+ # ETag value from Ruby objects for you.
5
+ #
6
+ # The most interesting method for end user is
7
+ # +request_fresh?+ that is used after setting of
8
+ # last modification time or ETag:
9
+ #
10
+ # ==== Example
11
+ #
12
+ # def show
13
+ # self.etag = Digest::SHA1.hexdigest(calculate_cache_key(params))
14
+ #
15
+ # if request_fresh?
16
+ # self.status = 304
17
+ # return ''
18
+ # else
19
+ # @product = Product.get(params[:id])
20
+ # display @product
21
+ # end
22
+ # end
23
+ module Merb::ConditionalGetMixin
24
+
25
+ # Sets ETag response header by calling
26
+ # #to_s on the argument.
27
+ #
28
+ # ==== Parameters
29
+ # tag<~to_s>::
30
+ # value of ETag header enclosed in double quotes
31
+ # as required by the RFC
32
+ def etag=(tag)
33
+ headers[Merb::Const::ETAG] = %("#{tag}")
34
+ end
35
+
36
+ # ==== Returns
37
+ # <String>::
38
+ # Value of ETag response header or nil if it's not set.
39
+ def etag
40
+ headers[Merb::Const::ETAG]
41
+ end
42
+
43
+ # ==== Returns
44
+ # <Boolean>::
45
+ # true if ETag response header equals If-None-Match request header,
46
+ # false otherwise
47
+ def etag_matches?(tag = self.etag)
48
+ tag == self.request.if_none_match
49
+ end
50
+
51
+ # Sets Last-Modified response header.
52
+ #
53
+ # ==== Parameters
54
+ # tag<Time>::
55
+ # resource modification timestamp converted into format
56
+ # required by the RFC
57
+ def last_modified=(time)
58
+ headers[Merb::Const::LAST_MODIFIED] = time.httpdate
59
+ end
60
+
61
+ # ==== Returns
62
+ # <String>::
63
+ # Value of Last-Modified response header or nil if it's not set.
64
+ def last_modified
65
+ Time.rfc2822(headers[Merb::Const::LAST_MODIFIED])
66
+ end
67
+
68
+ # ==== Returns
69
+ # <Boolean>::
70
+ # true if Last-Modified response header is < than
71
+ # If-Modified-Since request header value, false otherwise.
72
+ def not_modified?(time = self.last_modified)
73
+ request.if_modified_since && time && time <= request.if_modified_since
74
+ end
75
+
76
+ # ==== Returns
77
+ # <Boolean>::
78
+ # true if either ETag matches or entity is not modified,
79
+ # so request is fresh; false otherwise
80
+ def request_fresh?
81
+ etag_matches?(self.etag) || not_modified?(self.last_modified)
82
+ end
83
+ end
@@ -41,7 +41,7 @@ module Merb::RenderMixin
41
41
  # ==== Returns
42
42
  # Hash:: The default render options.
43
43
  def layout(layout)
44
- self.default_render_options.update(:layout => (layout ? layout : false))
44
+ self.default_render_options.update(:layout => (layout || false))
45
45
  end
46
46
 
47
47
  # Enable the default layout logic - reset the layout option.
@@ -95,7 +95,7 @@ module Merb::RenderMixin
95
95
  thing ||= action_name.to_sym
96
96
 
97
97
  # Content negotiation
98
- opts[:format] ? (self.content_type = opts[:format]) : content_type
98
+ self.content_type = opts[:format] if opts[:format]
99
99
 
100
100
  # Handle options (:status)
101
101
  _handle_options!(opts)
@@ -189,7 +189,7 @@ module Merb::RenderMixin
189
189
  # explicitly passed in the opts.
190
190
  #
191
191
  def display(object, thing = nil, opts = {})
192
- template_opt = opts.delete(:template)
192
+ template_opt = thing.is_a?(Hash) ? thing.delete(:template) : opts.delete(:template)
193
193
 
194
194
  case thing
195
195
  # display @object, "path/to/foo" means display @object, nil, :template => "path/to/foo"
@@ -37,27 +37,14 @@ module Kernel
37
37
  # If the gem cannot be found, the method will attempt to require the string
38
38
  # as a library.
39
39
  def load_dependency(name, *ver)
40
- try_framework = Merb.frozen?
41
40
  begin
42
- # If this is a piece of merb, and we're frozen, try to require
43
- # first, so we can pick it up from framework/,
44
- # otherwise try activating the gem
45
- if name =~ /^merb/ && try_framework
46
- require name
47
- else
48
- gem(name, *ver) if ver
49
- require name
50
- Merb.logger.info!("loading gem '#{name}' ...")
51
- end
41
+ gem(name, *ver) if ver
42
+ require name
43
+ Merb.logger.info!("loading gem '#{name}' ...")
52
44
  rescue LoadError
53
- if try_framework
54
- try_framework = false
55
- retry
56
- else
57
- Merb.logger.info!("loading gem '#{name}' ...")
58
- # Failed requiring as a gem, let's try loading with a normal require.
59
- require name
60
- end
45
+ Merb.logger.info!("loading gem '#{name}' ...")
46
+ # Failed requiring as a gem, let's try loading with a normal require.
47
+ require name
61
48
  end
62
49
  end
63
50