fortitude 0.9.4 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.fix_bundler_for_jruby_17 +26 -0
  3. data/.gitignore +1 -0
  4. data/.travis.yml +33 -40
  5. data/CHANGES.md +44 -0
  6. data/CONTRIBUTORS.md +26 -0
  7. data/Rakefile +1 -1
  8. data/ext/com/fortituderuby/ext/fortitude/FortitudeNativeLibrary.java +45 -33
  9. data/ext/fortitude_native_ext/fortitude_native_ext.c +23 -23
  10. data/fortitude.gemspec +25 -8
  11. data/lib/fortitude/erector.rb +26 -18
  12. data/lib/fortitude/errors.rb +15 -4
  13. data/lib/fortitude/extensions/fortitude_ruby_ext.rb +35 -10
  14. data/lib/fortitude/rails/helpers.rb +59 -2
  15. data/lib/fortitude/rails/railtie.rb +238 -157
  16. data/lib/fortitude/rails/renderer.rb +15 -0
  17. data/lib/fortitude/rails/rendering_methods.rb +46 -33
  18. data/lib/fortitude/rails/template_handler.rb +49 -18
  19. data/lib/fortitude/rails/yielded_object_outputter.rb +3 -2
  20. data/lib/fortitude/rendering_context.rb +14 -5
  21. data/lib/fortitude/support/method_overriding.rb +90 -0
  22. data/lib/fortitude/support/staticized_method.rb +12 -0
  23. data/lib/fortitude/version.rb +1 -1
  24. data/lib/fortitude/widget/content.rb +4 -2
  25. data/lib/fortitude/widget/files.rb +17 -11
  26. data/lib/fortitude/widget/helpers.rb +7 -1
  27. data/lib/fortitude/widget/integration.rb +4 -0
  28. data/lib/fortitude/widget/localization.rb +63 -4
  29. data/lib/fortitude/widget/rendering.rb +17 -10
  30. data/lib/fortitude_jruby_native_ext.jar +0 -0
  31. data/spec/helpers/fortitude_rails_helpers.rb +26 -4
  32. data/spec/rails/capture_system_spec.rb +1 -1
  33. data/spec/rails/class_loading_system_spec.rb +16 -2
  34. data/spec/rails/complex_helpers_system_spec.rb +29 -0
  35. data/spec/rails/data_passing_system_spec.rb +2 -2
  36. data/spec/rails/development_mode_system_spec.rb +1 -1
  37. data/spec/rails/erector_coexistence_system_spec.rb +1 -1
  38. data/spec/rails/helpers_system_spec.rb +20 -2
  39. data/spec/rails/layouts_system_spec.rb +1 -1
  40. data/spec/rails/rendering_system_spec.rb +4 -4
  41. data/spec/rails/rules_system_spec.rb +2 -2
  42. data/spec/rails/templates/class_loading_system_spec/app/views/some_namespace/some_other_namespace/placeholder.rb +5 -0
  43. data/spec/rails/templates/complex_helpers_system_spec/app/controllers/complex_helpers_system_spec_controller.rb +4 -0
  44. data/spec/rails/templates/complex_helpers_system_spec/app/views/complex_helpers_system_spec/label_block_test.rb +9 -0
  45. data/spec/rails/templates/helpers_system_spec/app/controllers/helpers_system_spec_controller.rb +4 -0
  46. data/spec/rails/templates/helpers_system_spec/app/controllers/home_controller.rb +9 -0
  47. data/spec/rails/templates/helpers_system_spec/app/views/helpers_system_spec/rails_helpers_without_automatic_helper_access.rb +37 -0
  48. data/spec/rails/templates/helpers_system_spec/app/views/helpers_system_spec/url_helpers_without_automatic_helper_access.rb +45 -0
  49. data/spec/rails/templates/helpers_system_spec/config/initializers/host.rb +1 -0
  50. data/spec/rails/templates/helpers_system_spec/config/routes.rb +7 -0
  51. data/spec/rails/templates/static_method_system_spec/app/views/static_method_system_spec/localization.rb +1 -1
  52. data/spec/rails/templates/view_paths_system_spec/app/controllers/view_paths_system_spec_controller.rb +15 -0
  53. data/spec/rails/templates/view_paths_system_spec/config/application.rb +30 -0
  54. data/spec/rails/templates/view_paths_system_spec/view_path_one/baseone/basetwo/base_class_one.rb +5 -0
  55. data/spec/rails/templates/view_paths_system_spec/view_path_one/view_paths_system_spec/added_view_path.html.rb +5 -0
  56. data/spec/rails/templates/view_paths_system_spec/view_path_one/view_paths_system_spec/autoloading_from_added_view_path.html.rb +5 -0
  57. data/spec/rails/templates/view_paths_system_spec/view_path_two/view_paths_system_spec/added_view_path_from_controller.html.rb +5 -0
  58. data/spec/rails/templates/view_paths_system_spec/view_path_two/view_paths_system_spec/added_view_path_from_controller_with_impossible_to_guess_name.html.rb +14 -0
  59. data/spec/rails/view_paths_system_spec.rb +19 -0
  60. data/spec/system/escaping_system_spec.rb +10 -2
  61. data/spec/system/helpers_system_spec.rb +37 -6
  62. data/spec/system/inline_system_spec.rb +19 -0
  63. data/spec/system/static_method_system_spec.rb +16 -0
  64. data/spec/system/tag_rendering_system_spec.rb +4 -4
  65. data/spec/system/widget_class_from_spec.rb +39 -0
  66. data/spec/system/yield_system_spec.rb +53 -1
  67. metadata +54 -22
data/fortitude.gemspec CHANGED
@@ -25,25 +25,42 @@ Gem::Specification.new do |s|
25
25
  s.extensions << "ext/fortitude_native_ext/extconf.rb"
26
26
  end
27
27
 
28
- activesupport_spec = if RUBY_VERSION =~ /^1\.8\./
29
- [ ">= 3.0", "< 4.0" ]
30
- else
31
- [ ">= 3.0" ]
28
+ activesupport_spec = [ ">= 3.0" ]
29
+ ref_spec = [ ">= 1.0.5" ]
30
+ rake_spec = [ ">= 1.0" ]
31
+ json_spec = [ ">= 1.0" ]
32
+
33
+ if RUBY_VERSION =~ /^1\.8\./
34
+ activesupport_spec << "< 4.0"
35
+ ref_spec << "< 2.0.0"
36
+ rake_spec << "< 11.0.0"
37
+ json_spec << "< 2.0.0"
38
+ elsif RUBY_VERSION =~ /^1\.9\./
39
+ activesupport_spec << "< 5.0"
40
+ json_spec << "< 2.0.0"
41
+ elsif RUBY_VERSION =~ /^2\.[01]\./
42
+ activesupport_spec << "< 5.0"
32
43
  end
33
44
 
34
45
  s.add_dependency "activesupport", *activesupport_spec
35
- s.add_dependency "ref", ">= 1.0.5"
46
+ s.add_dependency "ref", *ref_spec
47
+
48
+ s.add_development_dependency "rake", *rake_spec
49
+ s.add_development_dependency "json", *json_spec
36
50
 
37
51
  s.add_development_dependency "bundler", "~> 1.5"
38
- s.add_development_dependency "rake"
39
52
  s.add_development_dependency "rspec", "~> 2.99"
40
53
  s.add_development_dependency "rake-compiler"
41
- s.add_development_dependency "json"
42
54
  s.add_development_dependency "tilt", "~> 2.0"
43
- s.add_development_dependency "oop_rails_server", ">= 0.0.7"
55
+ s.add_development_dependency "oop_rails_server", ">= 0.0.22"
44
56
 
45
57
  # This is because i18n >= 0.7 is incompatible with Ruby 1.8.x.
46
58
  if RUBY_VERSION =~ /^1\.8\./
47
59
  s.add_development_dependency "i18n", "~> 0.6.0", "< 0.7.0"
48
60
  end
61
+
62
+ # This is because 'tins' >= 1.7.0 is incompatible with Ruby < 2.0.0.
63
+ if RUBY_VERSION =~ /^1\./
64
+ s.add_development_dependency "tins", "< 1.7.0"
65
+ end
49
66
  end
@@ -1,3 +1,5 @@
1
+ require 'fortitude/support/method_overriding'
2
+
1
3
  module Fortitude
2
4
  module Erector
3
5
  class << self
@@ -31,6 +33,10 @@ module Fortitude
31
33
  return is_erector_widget_class?(widget_class.superclass)
32
34
  end
33
35
 
36
+ def erector_widget_base_class_if_available
37
+ ::Erector::Widget if is_erector_available?
38
+ end
39
+
34
40
  def is_erector_widget?(widget)
35
41
  is_erector_widget_class?(widget.class)
36
42
  end
@@ -48,27 +54,29 @@ module Fortitude
48
54
  private
49
55
  attr_reader :erector_output
50
56
  end
51
- end
52
- end
53
57
 
54
- if ::Fortitude::Erector.is_erector_available?
55
- ::Erector::AbstractWidget.class_eval do
56
- def widget_with_fortitude(target, assigns = {}, options = {}, &block)
57
- if (target.kind_of?(::Class) && target < ::Fortitude::Widget)
58
- target = target.new(assigns)
59
- end
58
+ module ErectorAbstractWidgetOverrides
59
+ def widget_uniwith_fortitude(original_method, target, assigns = {}, options = {}, &block)
60
+ if (target.kind_of?(::Class) && target < ::Fortitude::Widget)
61
+ target = target.new(assigns)
62
+ end
60
63
 
61
- if target.kind_of?(::Fortitude::Widget)
62
- rendering_context = ::Fortitude::RenderingContext.new(
63
- :delegate_object => parent,
64
- :output_buffer_holder => ::Fortitude::Erector::ErectorOutputBufferHolder.new(output),
65
- :helpers_object => helpers)
66
- return target.render_to(rendering_context, &block)
67
- else
68
- return widget_without_fortitude(target, assigns, options, &block)
64
+ if target.kind_of?(::Fortitude::Widget)
65
+ rendering_context = ::Fortitude::RenderingContext.new(
66
+ :delegate_object => parent,
67
+ :output_buffer_holder => ::Fortitude::Erector::ErectorOutputBufferHolder.new(output),
68
+ :helpers_object => helpers)
69
+ return target.render_to(rendering_context, &block)
70
+ else
71
+ return original_method.call(target, assigns, options, &block)
72
+ end
69
73
  end
70
74
  end
71
-
72
- alias_method_chain :widget, :fortitude
73
75
  end
74
76
  end
77
+
78
+ if ::Fortitude::Erector.is_erector_available?
79
+ ::Fortitude::MethodOverriding.override_methods(
80
+ ::Erector::AbstractWidget, ::Fortitude::Erector::ErectorAbstractWidgetOverrides, :fortitude,
81
+ [ :widget ])
82
+ end
@@ -153,16 +153,27 @@ you simply need to convert these to "p :class => :some_class" or
153
153
  class NoBlockToYieldTo < Base
154
154
  attr_reader :widget
155
155
 
156
- def initialize(widget)
157
- super(%{You're trying to call 'yield' (or 'yield_from_widget', or the Erector-compatibility method 'call_block')
156
+ def initialize(widget, local_jump_exception = nil)
157
+ @local_jump_exception = local_jump_exception
158
+ @widget = widget
159
+
160
+ message = %{You're trying to call 'yield' (or 'yield_from_widget', or the Erector-compatibility method 'call_block')
158
161
  from the widget #{widget}; however, there is nothing to yield to. Fortitude
159
162
  looks for something to yield to in this order:
160
163
  1. A block passed to a yield at render time directly (usually via the 'widget' call);
161
164
  2. A block passed to the constructor of the widget;
162
165
  3. The layout the widget is being rendered in.
163
166
  None of these exist here, and so calling 'yield', 'yield_from_widget', or 'call_block' is an
164
- undefined operation.})
165
- @widget = widget
167
+ undefined operation.}
168
+
169
+ if @local_jump_exception
170
+ message += %{
171
+
172
+ This was caused by a #{@local_jump_exception.class.name}, which is: #{@local_jump_exception}:\n
173
+ #{@local_jump_exception.backtrace.join("\n ")}}
174
+ end
175
+
176
+ super(message)
166
177
  end
167
178
  end
168
179
  end
@@ -2,10 +2,35 @@ require 'erb'
2
2
 
3
3
  ::String.class_eval do
4
4
  def fortitude_append_escaped_string(output)
5
+ _fortitude_append_escaped_string(output, false)
6
+ end
7
+
8
+ TABLE_FOR_ESCAPE_ATTRIBUTE_VALUE__ = {
9
+ '&' => '&amp;',
10
+ '"' => '&quot;'
11
+ }
12
+
13
+ PROC_FOR_ESCAPE_ATTRIBUTE_VALUE__ = Proc.new do |match|
14
+ TABLE_FOR_ESCAPE_ATTRIBUTE_VALUE__[match]
15
+ end
16
+
17
+ if RUBY_VERSION =~ /^1\.8\./
18
+ def _fortitude_append_escaped_string_for_value(output)
19
+ output.original_concat(self.gsub(/[&\"]/, &PROC_FOR_ESCAPE_ATTRIBUTE_VALUE__))
20
+ end
21
+ else
22
+ def _fortitude_append_escaped_string_for_value(output)
23
+ output.original_concat(self.gsub(/[&\"]/, TABLE_FOR_ESCAPE_ATTRIBUTE_VALUE__))
24
+ end
25
+ end
26
+
27
+ def _fortitude_append_escaped_string(output, for_attribute_value)
5
28
  raise ArgumentError, "You can only append to a String" unless output.kind_of?(String)
6
29
 
7
30
  if html_safe?
8
31
  output.original_concat(self)
32
+ elsif for_attribute_value
33
+ _fortitude_append_escaped_string_for_value(output)
9
34
  else
10
35
  output.original_concat(ERB::Util.html_escape(self))
11
36
  end
@@ -33,8 +58,8 @@ end
33
58
  each do |key, value|
34
59
  if value.kind_of?(Hash)
35
60
  new_prefix = case prefix
36
- when String then fortitude_append_to(key, prefix.dup)
37
- when nil then fortitude_append_to(key, "".html_safe)
61
+ when String then fortitude_append_to(key, prefix.dup, false)
62
+ when nil then fortitude_append_to(key, "".html_safe, false)
38
63
  else raise ArgumentError, "You can only use a String as a prefix"
39
64
  end
40
65
 
@@ -52,19 +77,19 @@ end
52
77
  else raise ArgumentError, "You can only use a String as a prefix"
53
78
  end
54
79
 
55
- fortitude_append_to(key, target)
80
+ fortitude_append_to(key, target, false)
56
81
 
57
82
  if value == true
58
83
  if allows_bare_attributes
59
84
  # nothing here
60
85
  else
61
86
  target.original_concat(::Hash::FORTITUDE_EQUALS_QUOTE)
62
- fortitude_append_to(key, target)
87
+ fortitude_append_to(key, target, false)
63
88
  target.original_concat(::Hash::FORTITUDE_QUOTE)
64
89
  end
65
90
  else
66
91
  target.original_concat(::Hash::FORTITUDE_EQUALS_QUOTE)
67
- fortitude_append_to(value, target)
92
+ fortitude_append_to(value, target, true)
68
93
  target.original_concat(::Hash::FORTITUDE_QUOTE)
69
94
  end
70
95
  end
@@ -75,17 +100,17 @@ end
75
100
  end
76
101
 
77
102
  private
78
- def fortitude_append_to(object, output)
103
+ def fortitude_append_to(object, output, for_attribute_value)
79
104
  case object
80
- when String then object.fortitude_append_escaped_string(output)
81
- when Symbol then object.to_s.fortitude_append_escaped_string(output)
105
+ when String then object._fortitude_append_escaped_string(output, for_attribute_value)
106
+ when Symbol then object.to_s._fortitude_append_escaped_string(output, for_attribute_value)
82
107
  when Array then object.each_with_index do |o,i|
83
108
  output.original_concat(" ") if i > 0
84
- fortitude_append_to(o, output)
109
+ fortitude_append_to(o, output, for_attribute_value)
85
110
  end
86
111
  when nil then nil
87
112
  when Integer then output.original_concat(object.to_s)
88
- else object.to_s.fortitude_append_escaped_string(output)
113
+ else object.to_s._fortitude_append_escaped_string(output, for_attribute_value)
89
114
  end
90
115
  end
91
116
  end
@@ -4,20 +4,77 @@ module Fortitude
4
4
  class << self
5
5
  def helper(name, options = { })
6
6
  @helpers ||= { }
7
- @helpers[name] = options
7
+ @helpers[normalize_helper_name(name)] = options
8
8
  end
9
9
 
10
10
  def helper_options(name)
11
- @helpers[name.to_s.strip.downcase.to_sym]
11
+ @helpers[normalize_helper_name(name)]
12
12
  end
13
13
 
14
14
  def apply_refined_helpers_to!(o)
15
+ o.send(:include, ::Rails.application.routes.url_helpers)
15
16
  @helpers.each do |name, options|
16
17
  o.helper(name, options)
17
18
  end
18
19
  end
20
+
21
+ ALL_BUILTIN_HELPER_MODULES = {
22
+ ActionView::Helpers => %w{
23
+ ActiveModelHelper
24
+ ActiveModelInstanceTag
25
+ AssetTagHelper
26
+ AssetUrlHelper
27
+ AtomFeedHelper
28
+ CacheHelper
29
+ CaptureHelper
30
+ CsrfHelper
31
+ DateHelper
32
+ DebugHelper
33
+ FormHelper
34
+ FormOptionsHelper
35
+ FormTagHelper
36
+ JavaScriptHelper
37
+ NumberHelper
38
+ OutputSafetyHelper
39
+ RecordTagHelper
40
+ SanitizeHelper
41
+ TagHelper
42
+ TextHelper
43
+ TranslationHelper
44
+ UrlHelper
45
+ }
46
+ }
47
+
48
+ def declare_all_builtin_rails_helpers!
49
+ ALL_BUILTIN_HELPER_MODULES.each do |base_module, constant_names|
50
+ constant_names.each do |constant_name|
51
+ if base_module.const_defined?(constant_name)
52
+ helper_module = base_module.const_get(constant_name)
53
+ helper_module.public_instance_methods.each do |helper_method_name|
54
+ # This is because ActionView::Helpers::FormTagHelper exposes #embed_authenticity_token_in_remote_forms=
55
+ # as a public instance method. This seems like it should not be included as a helper.
56
+ # next if helper_method_name.to_s == 'embed_authenticity_token_in_remote_forms='
57
+ helper helper_method_name
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ helper :default_url_options
64
+ end
65
+
66
+ private
67
+ def normalize_helper_name(name)
68
+ name.to_s.strip.downcase.to_sym
69
+ end
19
70
  end
20
71
 
72
+ # This gives us all built-in Rails helpers, whether they're refined or not; our re-declarations of helpers,
73
+ # below, will override any of these. We need to grab all built-in Rails helpers because we want them all
74
+ # formally declared -- that is, even if +automatic_helper_access+ is set to +false+, built-in Rails helpers
75
+ # should still work properly.
76
+ declare_all_builtin_rails_helpers!
77
+
21
78
  # tags/
22
79
  # active_model_helper
23
80
  # asset_tag_helper
@@ -1,5 +1,7 @@
1
1
  require 'fortitude/rendering_context'
2
2
  require 'fortitude/rails/fortitude_rails_helpers'
3
+ require 'fortitude/support/method_overriding'
4
+ require 'thread'
3
5
 
4
6
  if defined?(ActiveSupport)
5
7
  ActiveSupport.on_load(:before_initialize) do
@@ -23,6 +25,33 @@ end
23
25
  module Fortitude
24
26
  module Rails
25
27
  class Railtie < ::Rails::Railtie
28
+ class << self
29
+ def _fortitude_view_roots
30
+ @_fortitude_view_roots_mutex.synchronize do
31
+ raise "@_fortitude_view_roots has not yet been set" unless @_fortitude_view_roots
32
+ @_fortitude_view_roots
33
+ end
34
+ end
35
+
36
+ def _fortitude_view_roots=(x)
37
+ @_fortitude_view_roots_mutex.synchronize do
38
+ if @_fortitude_view_roots_locked
39
+ raise "@_fortitude_view_roots was locked, and cannot be changed. It was locked at:\n #{@_fortitude_view_roots_locked}"
40
+ end
41
+
42
+ @_fortitude_view_roots = x
43
+ end
44
+ end
45
+
46
+ def _lock_fortitude_view_roots!
47
+ @_fortitude_view_roots_mutex.synchronize do
48
+ @_fortitude_view_roots_locked ||= caller.join("\n")
49
+ end
50
+ end
51
+ end
52
+
53
+ @_fortitude_view_roots_mutex = Mutex.new
54
+
26
55
  config.after_initialize do
27
56
  if Fortitude.refine_rails_helpers
28
57
  require 'fortitude/rails/helpers'
@@ -45,14 +74,14 @@ module Fortitude
45
74
  # Why so hard?
46
75
  #
47
76
  # We're trying to do something that ActiveSupport::Dependencies -- which is what Rails uses for
48
- # class autoloading -- doesn't really support. We want app/views to be on the autoload path,
77
+ # class autoloading -- doesn't really support. We want all view paths to be on the autoload path,
49
78
  # because there are now Ruby classes living there. (It usually isn't just because all that's there
50
79
  # are template source files, not actual Ruby code.) That isn't an issue, though -- adding it
51
80
  # is trivial (just do
52
- # <tt>ActiveSupport::Dependencies.autoload_paths << File.join(Rails.root, 'app/views')</tt>).
81
+ # <tt>ActiveSupport::Dependencies.autoload_paths += ::Rails.application.paths['app/views'].expanded</tt>).
53
82
  #
54
- # The real issue is that we want the class <tt>app/views/foo/bar.rb</tt> to define a class called
55
- # <tt>Views::Foo::Bar</tt>, not just plain <tt>Foo::Bar</tt>. This is what's different from what
83
+ # The real issue is that we want the class (<em>e.g.</em>) <tt>app/views/foo/bar.rb</tt> to define a class
84
+ # called <tt>Views::Foo::Bar</tt>, not just plain <tt>Foo::Bar</tt>. This is what's different from what
56
85
  # ActiveSupport::Dependencies normally supports; it expects the filesystem path underneath the
57
86
  # root to be exactly identical to the fully-qualified class name.
58
87
  #
@@ -62,208 +91,260 @@ module Fortitude
62
91
  # potential for conflicts is enormous.
63
92
  #
64
93
  # As such, we have this code. We'll walk through it step-by-step; note that at the end we *do*
65
- # add app/views/ to the autoload path, so all this code is doing is just dealing with the fact that
94
+ # add all view paths to the autoload path, so all this code is doing is just dealing with the fact that
66
95
  # the fully-qualified classname (<tt>Views::Foo::Bar</tt>) has one extra component on the front of it
67
96
  # (<tt>Views::</tt>) when compared to the subpath (<tt>foo/bar.rb</tt>) underneath what's on the autoload
68
97
  # path (<tt>app/views</tt>).
69
98
 
70
- # Go compute our views root.
71
- views_root = File.expand_path(File.join(::Rails.root, 'app', 'views'))
99
+ # Go compute our view roots.
100
+ #
101
+ # Rails 3.0.x doesn't define #expanded on ::Rails::Paths::Path; it also has a different way of getting at
102
+ # the view paths (<tt>::Rails.application.paths.app.views</tt>, rather than
103
+ # <tt>::Rails.application.paths['app/views']</tt>). So, if we're on Rails 3.0.x, we simply inline the
104
+ # equivalent code here.
105
+ view_roots = if ::Rails.version =~ /^3\.0\./
106
+ paths = ::Rails.application.paths.app.views
107
+
108
+ result = []
72
109
 
73
- # Now, do all this work inside ::ActiveSupport::Dependencies...
74
- ::ActiveSupport::Dependencies.module_eval do
75
- @@_fortitude_views_root = views_root
110
+ paths.each do |p|
111
+ root_path = p.instance_variable_get("@root")
112
+ root = if root_path then root_path.path else ::Rails.root end
113
+ glob = p.instance_variable_get("@glob")
76
114
 
77
- def self._fortitude_views_root
78
- @@_fortitude_views_root
79
- end
115
+ path = File.expand_path(p, root)
80
116
 
81
- # This is the method that gets called to auto-generate namespacing empty
82
- # modules (_e.g._, the toplevel <tt>Views::</tt> module) for directories
83
- # under an autoload path.
84
- #
85
- # The original method says:
86
- #
87
- # "Does the provided path_suffix correspond to an autoloadable module?
88
- # Instead of returning a boolean, the autoload base for this module is
89
- # returned."
90
- #
91
- # So, we just need to strip off the leading +views/+ from the +path_suffix+,
92
- # and see if that maps to a directory underneath <tt>app/views/</tt>; if so,
93
- # we'll return the path to <tt>.../app/views/</tt>. Otherwise, we just
94
- # delegate back to the superclass method.
95
- def autoloadable_module_with_fortitude?(path_suffix)
96
- if path_suffix =~ %r{^views(/.*)?$}i
97
- # If we got here, then we were passed a subpath of views/....
98
- subpath = $1
99
-
100
- if subpath.blank? || File.directory?(File.join(@@_fortitude_views_root, subpath))
101
- return @@_fortitude_views_root
117
+ if glob && File.directory?(path)
118
+ Dir.chdir(path) do
119
+ result.concat(Dir.glob(glob).map { |file| File.join path, file }.sort)
102
120
  end
103
- end
104
-
105
- with_fortitude_views_removed_from_autoload_path do
106
- autoloadable_module_without_fortitude?(path_suffix)
121
+ else
122
+ result << path
107
123
  end
108
124
  end
109
125
 
110
- alias_method_chain :autoloadable_module?, :fortitude
111
-
112
- # When we delegate back to original methods, we want them to act as if
113
- # <tt>app/views/</tt> is _not_ on the autoload path. In order to be thread-safe
114
- # about that, we couple this method with our override of the writer side of the
115
- # <tt>mattr_accessor :autoload_paths</tt>, which simply prefers the thread-local
116
- # that we set to the actual underlying variable.
117
- def with_fortitude_views_removed_from_autoload_path
118
- begin
119
- Thread.current[:_fortitude_autoload_paths_override] = autoload_paths - [ @@_fortitude_views_root ]
120
- yield
121
- ensure
122
- Thread.current[:_fortitude_autoload_paths_override] = nil
126
+ result.uniq!
127
+ result
128
+ else
129
+ ::Rails.application.paths['app/views'].expanded
130
+ end
131
+
132
+ ::Fortitude::Rails::Railtie._fortitude_view_roots = view_roots
133
+
134
+ module ActiveSupportDependenciesOverrides
135
+ class << self
136
+ # When we delegate back to original methods, we want them to act as if
137
+ # all view roots are _not_ on the autoload path. In order to be thread-safe
138
+ # about that, we couple this method with our override of the writer side of the
139
+ # <tt>mattr_accessor :autoload_paths</tt>, which simply prefers the thread-local
140
+ # that we set to the actual underlying variable.
141
+ def with_fortitude_views_removed_from_autoload_path
142
+ begin
143
+ Thread.current[:_fortitude_autoload_paths_override] =
144
+ ::ActiveSupport::Dependencies.autoload_paths -
145
+ ::Fortitude::Rails::Railtie._fortitude_view_roots
146
+
147
+ yield
148
+ ensure
149
+ Thread.current[:_fortitude_autoload_paths_override] = nil
150
+ end
123
151
  end
124
152
  end
125
153
 
126
- # The use of 'class_eval' here may seem funny, and I think it is, but, without it,
127
- # the +@@autoload_paths+ gets interpreted as a class variable for this *Railtie*,
128
- # rather than for ::ActiveSupport::Dependencies. (Why is that? Got me...)
129
- class_eval <<-EOS
130
- def self.autoload_paths
131
- Thread.current[:_fortitude_autoload_paths_override] || @@autoload_paths
154
+ module Common
155
+ def autoload_paths_uniwith_fortitude(original_method)
156
+ Thread.current[:_fortitude_autoload_paths_override] || original_method.call
132
157
  end
133
- EOS
134
158
 
135
- # The original method says:
136
- #
137
- # "Search for a file in autoload_paths matching the provided suffix."
138
- #
139
- # So, we just look to see if the given +path_suffix+ is specifying something like
140
- # <tt>views/foo/bar</tt> or the fully-qualified version thereof; if so, we glue it together properly,
141
- # removing the initial <tt>views/</tt> first. (Otherwise, the mechanism would expect
142
- # <tt>Views::Foo::Bar</tt> to show up in <tt>app/views/views/foo/bar</tt> (yes, a double
143
- # +views+), since <tt>app/views</tt> is on the autoload path.)
144
- def search_for_file_with_fortitude(path_suffix)
145
- # Remove any ".rb" extension, if present...
146
- new_path_suffix = path_suffix.sub(/(\.rb)?$/, "")
147
-
148
- found_subpath = if new_path_suffix =~ %r{^views(/.*)$}i
149
- $1
150
- elsif new_path_suffix =~ %r{^#{Regexp.escape(@@_fortitude_views_root)}(/.*)$}i
151
- $1
159
+ # This is the method that gets called to auto-generate namespacing empty
160
+ # modules (_e.g._, the toplevel <tt>Views::</tt> module) for directories
161
+ # under an autoload path.
162
+ #
163
+ # The original method says:
164
+ #
165
+ # "Does the provided path_suffix correspond to an autoloadable module?
166
+ # Instead of returning a boolean, the autoload base for this module is
167
+ # returned."
168
+ #
169
+ # So, we just need to strip off the leading +views/+ from the +path_suffix+,
170
+ # and see if that maps to a directory underneath one of our view roots; if so,
171
+ # we'll return the path to that view root. Otherwise, we just
172
+ # delegate back to the superclass method.
173
+ def autoloadable_module_uniwith_fortitude?(original_method, path_suffix)
174
+ if path_suffix =~ %r{^(views)(/.*)?$}i
175
+ # If we got here, then we were passed a subpath of views/....
176
+ prefix = $1
177
+ subpath = $2
178
+
179
+ if subpath.blank?
180
+ ::Fortitude::Rails::Railtie._fortitude_view_roots.each do |view_root|
181
+ return view_root if File.basename(view_root).strip.downcase == prefix.strip.downcase
182
+ end
183
+ else
184
+ ::Fortitude::Rails::Railtie._fortitude_view_roots.each do |view_root|
185
+ return view_root if File.directory?(File.join(view_root, subpath))
186
+ end
187
+ end
188
+ end
189
+
190
+ ActiveSupportDependenciesOverrides.with_fortitude_views_removed_from_autoload_path do
191
+ original_method.call(path_suffix)
192
+ end
152
193
  end
153
194
 
154
- if found_subpath
155
- full_path = File.join(@@_fortitude_views_root, "#{found_subpath}")
156
- directory = File.dirname(full_path)
195
+ # The original method says:
196
+ #
197
+ # "Search for a file in autoload_paths matching the provided suffix."
198
+ #
199
+ # So, we just look to see if the given +path_suffix+ is specifying something like
200
+ # <tt>views/foo/bar</tt> or the fully-qualified version thereof; if so, we glue it together properly,
201
+ # removing the initial <tt>views/</tt> first. (Otherwise, the mechanism would expect
202
+ # <tt>Views::Foo::Bar</tt> to show up in <tt>app/views/views/foo/bar</tt> (yes, a double
203
+ # +views+), since <tt>app/views</tt> is on the autoload path.)
204
+ def search_for_file_uniwith_fortitude(original_method, path_suffix)
205
+ # Remove any ".rb" extension, if present...
206
+ new_path_suffix = path_suffix.sub(/(\.rb)?$/, "")
207
+
208
+ found_subpath = nil
209
+ if new_path_suffix =~ %r{^views(/.*)$}i
210
+ found_subpath = $1
211
+ else
212
+ ::Fortitude::Rails::Railtie._fortitude_view_roots.each do |view_root|
213
+ if new_path_suffix =~ %r{^#{Regexp.escape(view_root)}(/.*)$}i
214
+ found_subpath = $1
215
+ break
216
+ end
217
+ end
218
+ end
157
219
 
158
- if File.directory?(directory)
159
- filename = File.basename(full_path)
220
+ if found_subpath
221
+ ::Fortitude::Rails::Railtie._fortitude_view_roots.each do |view_root|
222
+ full_path = File.join(view_root, "#{found_subpath}")
223
+ directory = File.dirname(full_path)
160
224
 
161
- regexp1 = /^_?#{Regexp.escape(filename)}\./
162
- regexp2 = /\.rb$/i
163
- applicable_entries = Dir.entries(directory).select do |entry|
164
- ((entry == filename) || (entry =~ regexp1 && entry =~ regexp2)) && File.file?(File.join(directory, entry))
165
- end
166
- return nil if applicable_entries.length == 0
225
+ if File.directory?(directory)
226
+ filename = File.basename(full_path)
227
+
228
+ regexp1 = /^_?#{Regexp.escape(filename)}\./
229
+ regexp2 = /\.rb$/i
230
+ applicable_entries = Dir.entries(directory).select do |entry|
231
+ ((entry == filename) || (entry =~ regexp1 && entry =~ regexp2)) && File.file?(File.join(directory, entry))
232
+ end
233
+
234
+ return nil if applicable_entries.length == 0
167
235
 
168
- # Prefer those without an underscore
169
- without_underscore = applicable_entries.select { |e| e !~ /^_/ }
170
- applicable_entries = without_underscore if without_underscore.length > 0
236
+ # Prefer those without an underscore
237
+ without_underscore = applicable_entries.select { |e| e !~ /^_/ }
238
+ applicable_entries = without_underscore if without_underscore.length > 0
171
239
 
172
- entry_to_use = applicable_entries.sort_by { |e| e.length }.reverse.first
173
- return File.join(directory, entry_to_use)
240
+ entry_to_use = applicable_entries.sort_by { |e| e.length }.reverse.first
241
+ return File.join(directory, entry_to_use)
242
+ end
243
+ end
174
244
  end
175
- end
176
245
 
177
- # Make sure that we remove the views autoload path before letting the rest of
178
- # the dependency mechanism go searching for files, or else <tt>app/views/foo/bar.rb</tt>
179
- # *will* be found when looking for just <tt>::Foo::Bar</tt>.
180
- with_fortitude_views_removed_from_autoload_path { search_for_file_without_fortitude(path_suffix) }
246
+ # Make sure that we remove the views autoload path before letting the rest of
247
+ # the dependency mechanism go searching for files, or else <tt>app/views/foo/bar.rb</tt>
248
+ # *will* be found when looking for just <tt>::Foo::Bar</tt>.
249
+ ActiveSupportDependenciesOverrides.with_fortitude_views_removed_from_autoload_path do
250
+ original_method.call(path_suffix)
251
+ end
252
+ end
181
253
  end
182
-
183
- alias_method_chain :search_for_file, :fortitude
184
254
  end
185
255
 
186
- # Two important comments here:
187
- #
188
- # 1: We also need to patch ::Rails::Engine.eager_load! so that it loads classes under app/views/. However, we
189
- # can't just add it to the normal eager load paths, because that will allow people to do "require 'foo/bar'"
190
- # and have it match app/views/foo/bar.rb, which we don't want. So, instead, we load these classes ourselves.
191
- # Note that we ALSO have to do things slightly differently than Rails does it, because we need to skip loading
192
- # 'foo.rb' if 'foo.html.rb' exists -- and because we have to require the fully-qualified pathname, since
193
- # app/views is not actually on the load path.
194
- #
195
- # 2: I (ageweke) added this very late in the path of Fortitude development, after trying to use Fortitude in a
196
- # deployment (production) environment in which widgets just weren't getting loaded at all. Yet there's something
197
- # I don't understand: clearly, without this code, widgets will not be eager-loaded (which is probably not a
198
- # great thing for performance reasons)...but I think they still should get auto-loaded and hence actually work
199
- # just fine. But they don't in that environment (you'll get errors like "uninitialized constant Views::Base").
200
- # Since I understand what's going on and have the fix for it here, that's fine...except that I can't seem to
201
- # write a spec for it, because I don't know how to actually *make* it fail. If anybody comes along later and
202
- # knows what would make it fail (and I double-checked, and we don't have autoloading disabled in production or
203
- # anything like that), let me know, so that I can write a spec for this. Thanks!
204
- ::Rails::Engine.class_eval do
205
- def eager_load_with_fortitude!
206
- eager_load_without_fortitude!
256
+ ::Fortitude::MethodOverriding.override_methods(
257
+ ::ActiveSupport::Dependencies, ActiveSupportDependenciesOverrides::Common, :fortitude,
258
+ [ :search_for_file, :autoloadable_module?, :autoload_paths ])
259
+
260
+ eigenclass = ::ActiveSupport::Dependencies.module_eval "class << self; self; end"
261
+ ::Fortitude::MethodOverriding.override_methods(
262
+ eigenclass, ActiveSupportDependenciesOverrides::Common, :fortitude,
263
+ [ :autoload_paths ])
264
+
265
+ module RailsEngineOverrides
266
+ # Two important comments here:
267
+ #
268
+ # 1: We also need to patch ::Rails::Engine.eager_load! so that it loads classes under all view roots. However, we
269
+ # can't just add them to the normal eager load paths, because that will allow people to do "require 'foo/bar'"
270
+ # and have it match app/views/foo/bar.rb, which we don't want. So, instead, we load these classes ourselves.
271
+ # Note that we ALSO have to do things slightly differently than Rails does it, because we need to skip loading
272
+ # 'foo.rb' if 'foo.html.rb' exists -- and because we have to require the fully-qualified pathname, since
273
+ # app/views is not actually on the load path.
274
+ #
275
+ # 2: I (ageweke) added this very late in the path of Fortitude development, after trying to use Fortitude in a
276
+ # deployment (production) environment in which widgets just weren't getting loaded at all. Yet there's something
277
+ # I don't understand: clearly, without this code, widgets will not be eager-loaded (which is probably not a
278
+ # great thing for performance reasons)...but I think they still should get auto-loaded and hence actually work
279
+ # just fine. But they don't in that environment (you'll get errors like "uninitialized constant Views::Base").
280
+ # Since I understand what's going on and have the fix for it here, that's fine...except that I can't seem to
281
+ # write a spec for it, because I don't know how to actually *make* it fail. If anybody comes along later and
282
+ # knows what would make it fail (and I double-checked, and we don't have autoloading disabled in production or
283
+ # anything like that), let me know, so that I can write a spec for this. Thanks!
284
+ def eager_load_uniwith_fortitude!(original_method)
285
+ original_method.call
207
286
  eager_load_fortitude_views!
208
287
  end
209
288
 
210
289
  def eager_load_fortitude_views!
211
- load_path = ::ActiveSupport::Dependencies._fortitude_views_root
212
- all_files = Dir.glob("#{load_path}/**/*.rb")
213
- matcher = /\A#{Regexp.escape(load_path.to_s)}\/(.*)\.rb\Z/
290
+ ::Fortitude::Rails::Railtie._fortitude_view_roots.each do |load_path|
291
+ all_files = Dir.glob("#{load_path}/**/*.rb")
292
+ matcher = /\A#{Regexp.escape(load_path.to_s)}\/(.*)\.rb\Z/
214
293
 
215
- all_files.sort.each do |full_path|
216
- filename = File.basename(full_path, ".rb")
217
- directory = File.dirname(full_path)
294
+ all_files.sort.each do |full_path|
295
+ filename = File.basename(full_path, ".rb")
296
+ directory = File.dirname(full_path)
218
297
 
219
- longer_name_regex = /^#{Regexp.escape(filename)}\..+\.rb$/i
220
- longer_name = Dir.entries(directory).detect { |e| e =~ longer_name_regex }
298
+ longer_name_regex = /^#{Regexp.escape(filename)}\..+\.rb$/i
299
+ longer_name = Dir.entries(directory).detect { |e| e =~ longer_name_regex }
221
300
 
222
- unless longer_name
223
- require_dependency File.join('views', full_path.sub(matcher, '\1'))
301
+ unless longer_name
302
+ require_dependency File.join('views', full_path.sub(matcher, '\1'))
303
+ end
224
304
  end
225
305
  end
226
306
  end
227
-
228
- alias_method_chain :eager_load!, :fortitude
229
307
  end
230
308
 
231
- # And, finally, this is where we add our root to the set of autoload paths.
232
- ::ActiveSupport::Dependencies.autoload_paths << views_root
233
- # app.config.eager_load_paths << views_root
309
+ ::Fortitude::MethodOverriding.override_methods(
310
+ ::Rails::Engine, RailsEngineOverrides, :fortitude, [ :eager_load! ])
234
311
 
235
- # This is our support for partials. Fortitude doesn't really have a distinction between
236
- # partials and "full" templates -- everything is just a widget, which is much more elegant --
237
- # but we still want you to be able to render a widget <tt>Views::Foo::Bar</tt> by saying
238
- # <tt>render :partial => 'foo/bar'</tt> (from ERb, although you can do it from Fortitude if
239
- # you want for some reason, too).
240
- #
241
- # Normally, ActionView only looks for partials in files starting with an underscore. We
242
- # do want to allow this, too (in the above case, if you define the widget in the file
243
- # <tt>app/views/foo/_bar.rb</tt>, it will still work fine); however, we also want to allow
244
- # you to define it in a file that does _not_ start with an underscore ('cause these are
245
- # Ruby classes, and that's just plain weird).
246
- #
247
- # So, we patch #find_templates: if it's looking for a partial, doesn't find one, and is
248
- # searching Fortitude templates (the +.rb+ handler), then we try again, turning off the
249
- # +partial+ flag, and return that instead.
250
- ::ActionView::PathResolver.class_eval do
251
- def find_templates_with_fortitude(name, prefix, partial, details)
252
- templates = find_templates_without_fortitude(name, prefix, partial, details)
312
+ # And, finally, this is where we add our view roots to the set of autoload paths.
313
+ ::ActiveSupport::Dependencies.autoload_paths += view_roots
314
+
315
+ module ActionViewPathResolverOverrides
316
+ # This is our support for partials. Fortitude doesn't really have a distinction between
317
+ # partials and "full" templates -- everything is just a widget, which is much more elegant --
318
+ # but we still want you to be able to render a widget <tt>Views::Foo::Bar</tt> by saying
319
+ # <tt>render :partial => 'foo/bar'</tt> (from ERb, although you can do it from Fortitude if
320
+ # you want for some reason, too).
321
+ #
322
+ # Normally, ActionView only looks for partials in files starting with an underscore. We
323
+ # do want to allow this, too (in the above case, if you define the widget in the file
324
+ # <tt>app/views/foo/_bar.rb</tt>, it will still work fine); however, we also want to allow
325
+ # you to define it in a file that does _not_ start with an underscore ('cause these are
326
+ # Ruby classes, and that's just plain weird).
327
+ #
328
+ # So, we patch #find_templates: if it's looking for a partial, doesn't find one, and is
329
+ # searching Fortitude templates (the +.rb+ handler), then we try again, turning off the
330
+ # +partial+ flag, and return that instead.
331
+ def find_templates_uniwith_fortitude(original_method, name, prefix, partial, details, *args)
332
+ templates = original_method.call(name, prefix, partial, details, *args)
253
333
  if partial && templates.empty? && details[:handlers] && details[:handlers].include?(:rb)
254
- templates = find_templates_without_fortitude(name, prefix, false, details.merge(:handlers => [ :rb ]))
334
+ templates = original_method.call(name, prefix, false, details.merge(:handlers => [ :rb ]), *args)
255
335
  end
256
336
  templates
257
337
  end
258
-
259
- alias_method_chain :find_templates, :fortitude
260
338
  end
261
339
 
340
+ ::Fortitude::MethodOverriding.override_methods(
341
+ ::ActionView::PathResolver, ActionViewPathResolverOverrides, :fortitude, [ :find_templates ])
342
+
262
343
  require "fortitude/rails/template_handler"
263
344
  require "fortitude/rails/rendering_methods"
264
345
 
265
- ::ActionController::Base.send(:include, ::Fortitude::Rails::RenderingMethods)
266
- ::ActionMailer::Base.send(:include, ::Fortitude::Rails::RenderingMethods)
346
+ ::Fortitude::Rails::RenderingMethods.include_into!(::ActionController::Base)
347
+ ::Fortitude::Rails::RenderingMethods.include_into!(::ActionMailer::Base)
267
348
  end
268
349
  end
269
350
  end