Skip to content
Browse files

Merge commit 'mainstream/master'

Conflicts:

	actionpack/lib/action_controller/base.rb
	railties/lib/rails_generator/commands.rb
  • Loading branch information...
2 parents ef67bd4 + 99cc85b commit 0cfa3574d599f3bc134cd13fa00d8f22809dd67b @lifo lifo committed
Showing with 1,243 additions and 1,203 deletions.
  1. +1 −1 actionmailer/lib/action_mailer/base.rb
  2. +1 −2 actionmailer/test/abstract_unit.rb
  3. +39 −0 actionpack/CHANGELOG
  4. +27 −76 actionpack/lib/action_controller/assertions/response_assertions.rb
  5. +40 −28 actionpack/lib/action_controller/base.rb
  6. +12 −11 actionpack/lib/action_controller/caching/actions.rb
  7. +229 −197 actionpack/lib/action_controller/filters.rb
  8. +1 −1 actionpack/lib/action_controller/layout.rb
  9. +5 −1 actionpack/lib/action_controller/mime_responds.rb
  10. +43 −39 actionpack/lib/action_controller/mime_type.rb
  11. +33 −13 actionpack/lib/action_controller/request.rb
  12. +4 −0 actionpack/lib/action_controller/routing.rb
  13. +3 −16 actionpack/lib/action_controller/test_process.rb
  14. +4 −4 actionpack/lib/action_view.rb
  15. +67 −51 actionpack/lib/action_view/base.rb
  16. +42 −14 actionpack/lib/action_view/helpers/asset_tag_helper.rb
  17. +1 −1 actionpack/lib/action_view/helpers/cache_helper.rb
  18. +1 −2 actionpack/lib/action_view/helpers/capture_helper.rb
  19. +5 −2 actionpack/lib/action_view/helpers/date_helper.rb
  20. +6 −3 actionpack/lib/action_view/helpers/prototype_helper.rb
  21. +1 −1 actionpack/lib/action_view/helpers/text_helper.rb
  22. +3 −5 actionpack/lib/action_view/helpers/url_helper.rb
  23. +10 −7 actionpack/lib/action_view/inline_template.rb
  24. +0 −69 actionpack/lib/action_view/partial_template.rb
  25. +36 −22 actionpack/lib/action_view/partials.rb
  26. +96 −0 actionpack/lib/action_view/paths.rb
  27. +79 −0 actionpack/lib/action_view/renderable.rb
  28. +19 −0 actionpack/lib/action_view/renderable_partial.rb
  29. +0 −29 actionpack/lib/action_view/renderer.rb
  30. +84 −51 actionpack/lib/action_view/template.rb
  31. +2 −2 actionpack/lib/action_view/template_error.rb
  32. +0 −88 actionpack/lib/action_view/template_file.rb
  33. +1 −9 actionpack/lib/action_view/template_handler.rb
  34. +4 −9 actionpack/lib/action_view/template_handlers/builder.rb
  35. +2 −116 actionpack/lib/action_view/template_handlers/compilable.rb
  36. +2 −6 actionpack/lib/action_view/template_handlers/rjs.rb
  37. +0 −103 actionpack/lib/action_view/view_load_paths.rb
  38. +3 −1 actionpack/test/abstract_unit.rb
  39. +9 −8 actionpack/test/activerecord/render_partial_with_record_identification_test.rb
  40. +4 −30 actionpack/test/controller/action_pack_assertions_test.rb
  41. +0 −2 actionpack/test/controller/addresses_render_test.rb
  42. +14 −12 actionpack/test/controller/caching_test.rb
  43. +0 −2 actionpack/test/controller/capture_test.rb
  44. +1 −1 actionpack/test/controller/components_test.rb
  45. +14 −2 actionpack/test/controller/content_type_test.rb
  46. +0 −2 actionpack/test/controller/deprecation/deprecated_base_methods_test.rb
  47. +4 −3 actionpack/test/controller/layout_test.rb
  48. +5 −2 actionpack/test/controller/mime_responds_test.rb
  49. +5 −3 actionpack/test/controller/new_render_test.rb
  50. +8 −16 actionpack/test/controller/redirect_test.rb
  51. +0 −3 actionpack/test/controller/render_test.rb
  52. +1 −1 actionpack/test/controller/request_test.rb
  53. +15 −17 actionpack/test/controller/resources_test.rb
  54. +0 −2 actionpack/test/controller/send_file_test.rb
  55. +0 −18 actionpack/test/controller/test_test.rb
  56. +0 −16 actionpack/test/controller/view_paths_test.rb
  57. +1 −0 actionpack/test/fixtures/developers/_developer.erb
  58. +1 −0 actionpack/test/fixtures/fun/games/_game.erb
  59. +1 −0 actionpack/test/fixtures/fun/serious/games/_game.erb
  60. +1 −0 actionpack/test/fixtures/projects/_project.erb
  61. +1 −0 actionpack/test/fixtures/public/javascripts/subdir/subdir.js
  62. +1 −0 actionpack/test/fixtures/public/stylesheets/subdir/subdir.css
  63. +1 −0 actionpack/test/fixtures/replies/_reply.erb
  64. +1 −0 actionpack/test/fixtures/test/hyphen-ated.erb
  65. +43 −0 actionpack/test/template/asset_tag_helper_test.rb
  66. +15 −0 actionpack/test/template/date_helper_test.rb
  67. +2 −2 actionpack/test/template/prototype_helper_test.rb
  68. +9 −10 actionpack/test/template/render_test.rb
  69. +11 −9 actionpack/test/template/url_helper_test.rb
  70. +5 −0 activerecord/CHANGELOG
  71. +7 −5 activerecord/lib/active_record/associations.rb
  72. +1 −1 activerecord/lib/active_record/associations/association_collection.rb
  73. +10 −2 activerecord/lib/active_record/associations/has_many_association.rb
  74. +10 −1 activerecord/lib/active_record/associations/has_one_association.rb
  75. +1 −1 activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
  76. +5 −2 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
  77. +2 −1 activerecord/lib/active_record/named_scope.rb
  78. +11 −7 activerecord/lib/active_record/validations.rb
  79. +4 −0 activerecord/test/cases/associations/has_many_associations_test.rb
  80. +7 −0 activerecord/test/cases/associations/has_one_associations_test.rb
  81. +26 −0 activerecord/test/cases/named_scope_test.rb
  82. +3 −3 activerecord/test/cases/reflection_test.rb
  83. +18 −5 activerecord/test/cases/validations_test.rb
  84. +1 −0 activerecord/test/fixtures/companies.yml
  85. +3 −0 activerecord/test/models/company.rb
  86. +2 −0 activerecord/test/models/topic.rb
  87. +1 −0 activerecord/test/schema/schema.rb
  88. +2 −0 activesupport/CHANGELOG
  89. +1 −1 activesupport/lib/active_support/core_ext/module/introspection.rb
  90. +1 −1 activesupport/lib/active_support/core_ext/object/instance_variables.rb
  91. +0 −1 activesupport/lib/active_support/core_ext/test.rb
  92. +2 −2 activesupport/lib/active_support/dependencies.rb
  93. +1 −5 activesupport/lib/active_support/test_case.rb
  94. +6 −0 activesupport/lib/active_support/testing/core_ext/test.rb
  95. +4 −3 activesupport/lib/active_support/{ → testing}/core_ext/test/unit/assertions.rb
  96. +2 −0 railties/configs/routes.rb
  97. +1 −1 railties/lib/console_with_helpers.rb
  98. +1 −1 railties/lib/initializer.rb
  99. +1 −1 railties/lib/rails/plugin/locator.rb
  100. +9 −16 railties/lib/rails_generator/commands.rb
  101. +1 −1 railties/lib/rails_generator/lookup.rb
  102. +1 −1 railties/lib/tasks/misc.rake
  103. +19 −0 railties/test/generators/rails_controller_generator_test.rb
View
2 actionmailer/lib/action_mailer/base.rb
@@ -426,7 +426,7 @@ def register_template_extension(extension)
end
def template_root=(root)
- write_inheritable_attribute(:template_root, ActionView::ViewLoadPaths.new(Array(root)))
+ write_inheritable_attribute(:template_root, ActionView::PathSet.new(Array(root)))
end
end
View
3 actionmailer/test/abstract_unit.rb
@@ -32,8 +32,7 @@ def self.start(*args)
# Wrap tests that use Mocha and skip if unavailable.
def uses_mocha(test_name)
- gem 'mocha', ">=0.5"
- require 'stubba'
+ gem 'mocha', ">=0.9.0"
yield
rescue Gem::LoadError
$stderr.puts "Skipping #{test_name} tests (Mocha >= 0.5 is required). `gem install mocha` and try again."
View
39 actionpack/CHANGELOG
@@ -1,5 +1,34 @@
*Edge*
+* Set config.action_view.warn_cache_misses = true to receive a warning if you perform an action that results in an expensive disk operation that could be cached [Josh Peek]
+
+* Refactor template preloading. New abstractions include Renderable mixins and a refactored Template class [Josh Peek]
+
+* Changed ActionView::TemplateHandler#render API method signature to render(template, local_assigns = {}) [Josh Peek]
+
+* Changed PrototypeHelper#submit_to_remote to PrototypeHelper#button_to_remote to stay consistent with link_to_remote (submit_to_remote still works as an alias) #8994 [clemens]
+
+* Add :recursive option to javascript_include_tag and stylesheet_link_tag to be used along with :all. #480 [Damian Janowski]
+
+* Allow users to disable the use of the Accept header [Michael Koziarski]
+
+ The accept header is poorly implemented by browsers and causes strange
+ errors when used on public sites where crawlers make requests too. You
+ can use formatted urls (e.g. /people/1.xml) to support API clients in a
+ much simpler way.
+
+ To disable the header you need to set:
+
+ config.action_controller.use_accept_header = false
+
+* Do not stat template files in production mode before rendering. You will no longer be able to modify templates in production mode without restarting the server [Josh Peek]
+
+* Deprecated TemplateHandler line offset [Josh Peek]
+
+* Allow caches_action to accept cache store options. #416. [José Valim]. Example:
+
+ caches_action :index, :redirected, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour
+
* Remove define_javascript_functions, javascript_include_tag and friends are far superior. [Michael Koziarski]
* Deprecate :use_full_path render option. The supplying the option no longer has an effect [Josh Peek]
@@ -14,6 +43,16 @@
* Made ActionView::Base#render_file private [Josh Peek]
+* Refactor and simplify the implementation of assert_redirected_to. Arguments are now normalised relative to the controller being tested, not the root of the application. [Michael Koziarski]
+
+ This could cause some erroneous test failures if you were redirecting between controllers
+ in different namespaces and wrote your assertions relative to the root of the application.
+
+* Remove follow_redirect from controller functional tests.
+
+ If you want to follow redirects you can use integration tests. The functional test
+ version was only useful if you were using redirect_to :id=>...
+
* Fix polymorphic_url with singleton resources. #461 [Tammer Saleh]
* Replaced TemplateFinder abstraction with ViewLoadPaths [Josh Peek]
View
103 actionpack/lib/action_controller/assertions/response_assertions.rb
@@ -56,74 +56,24 @@ def assert_response(type, message = nil)
# # assert that the redirection was to the named route login_url
# assert_redirected_to login_url
#
+ # # assert that the redirection was to the url for @customer
+ # assert_redirected_to @customer
+ #
def assert_redirected_to(options = {}, message=nil)
clean_backtrace do
assert_response(:redirect, message)
return true if options == @response.redirected_to
- ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
-
- begin
- url = {}
- original = { :expected => options, :actual => @response.redirected_to.is_a?(Symbol) ? @response.redirected_to : @response.redirected_to.dup }
- original.each do |key, value|
- if value.is_a?(Symbol)
- value = @controller.respond_to?(value, true) ? @controller.send(value) : @controller.send("hash_for_#{value}_url")
- end
-
- unless value.is_a?(Hash)
- request = case value
- when NilClass then nil
- when /^\w+:\/\// then recognized_request_for(%r{^(\w+://.*?(/|$|\?))(.*)$} =~ value ? $3 : nil)
- else recognized_request_for(value)
- end
- value = request.path_parameters if request
- end
-
- if value.is_a?(Hash) # stringify 2 levels of hash keys
- if name = value.delete(:use_route)
- route = ActionController::Routing::Routes.named_routes[name]
- value.update(route.parameter_shell)
- end
-
- value.stringify_keys!
- value.values.select { |v| v.is_a?(Hash) }.collect { |v| v.stringify_keys! }
- if key == :expected && value['controller'] == @controller.controller_name && original[:actual].is_a?(Hash)
- original[:actual].stringify_keys!
- value.delete('controller') if original[:actual]['controller'].nil? || original[:actual]['controller'] == value['controller']
- end
- end
-
- if value.respond_to?(:[]) && value['controller']
- value['controller'] = value['controller'].to_s
- if key == :actual && value['controller'].first != '/' && !value['controller'].include?('/')
- new_controller_path = ActionController::Routing.controller_relative_to(value['controller'], @controller.class.controller_path)
- value['controller'] = new_controller_path if value['controller'] != new_controller_path && ActionController::Routing.possible_controllers.include?(new_controller_path) && @response.redirected_to.is_a?(Hash)
- end
- value['controller'] = value['controller'][1..-1] if value['controller'].first == '/' # strip leading hash
- end
- url[key] = value
- end
-
- @response_diff = url[:actual].diff(url[:expected]) if url[:actual]
- msg = build_message(message, "expected a redirect to <?>, found one to <?>, a difference of <?> ", url[:expected], url[:actual], @response_diff)
-
- assert_block(msg) do
- url[:expected].keys.all? do |k|
- if k == :controller then url[:expected][k] == ActionController::Routing.controller_relative_to(url[:actual][k], @controller.class.controller_path)
- else parameterize(url[:expected][k]) == parameterize(url[:actual][k])
- end
- end
- end
- rescue ActionController::RoutingError # routing failed us, so match the strings only.
- msg = build_message(message, "expected a redirect to <?>, found one to <?>", options, @response.redirect_url)
- url_regexp = %r{^(\w+://.*?(/|$|\?))(.*)$}
- eurl, epath, url, path = [options, @response.redirect_url].collect do |url|
- u, p = (url_regexp =~ url) ? [$1, $3] : [nil, url]
- [u, (p.first == '/') ? p : '/' + p]
- end.flatten
+
+ # Support partial arguments for hash redirections
+ if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
+ return true if options.all? {|(key, value)| @response.redirected_to[key] == value}
+ end
+
+ redirected_to_after_normalisation = normalize_argument_to_redirection(@response.redirected_to)
+ options_after_normalisation = normalize_argument_to_redirection(options)
- assert_equal(eurl, url, msg) if eurl && url
- assert_equal(epath, path, msg) if epath && path
+ if redirected_to_after_normalisation != options_after_normalisation
+ flunk "Expected response to be a redirect to <#{options_after_normalisation}> but was a redirect to <#{redirected_to_after_normalisation}>"
end
end
end
@@ -143,30 +93,31 @@ def assert_template(expected = nil, message=nil)
if expected.nil?
!@response.rendered_with_file?
else
- expected == rendered
+ rendered.match(expected)
end
end
end
end
private
- # Recognizes the route for a given path.
- def recognized_request_for(path, request_method = nil)
- path = "/#{path}" unless path.first == '/'
-
- # Assume given controller
- request = ActionController::TestRequest.new({}, {}, nil)
- request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
- request.path = path
-
- ActionController::Routing::Routes.recognize(request)
- request
- end
# Proxy to to_param if the object will respond to it.
def parameterize(value)
value.respond_to?(:to_param) ? value.to_param : value
end
+
+ def normalize_argument_to_redirection(fragment)
+ after_routing = @controller.url_for(fragment)
+ if after_routing =~ %r{^\w+://.*}
+ after_routing
+ else
+ # FIXME - this should probably get removed.
+ if after_routing.first != '/'
+ after_routing = '/' + after_routing
+ end
+ @request.protocol + @request.host_with_port + after_routing
+ end
+ end
end
end
end
View
68 actionpack/lib/action_controller/base.rb
@@ -340,7 +340,17 @@ class Base
cattr_accessor :optimise_named_routes
self.optimise_named_routes = true
- # Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
+ # Indicates whether the response format should be determined by examining the Accept HTTP header,
+ # or by using the simpler params + ajax rules.
+ #
+ # If this is set to +true+ (the default) then +respond_to+ and +Request#format+ will take the Accept
+ # header into account. If it is set to false then the request format will be determined solely
+ # by examining params[:format]. If params format is missing, the format will be either HTML or
+ # Javascript depending on whether the request is an AJAX request.
+ cattr_accessor :use_accept_header
+ self.use_accept_header = true
+
+ # Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode.
class_inheritable_accessor :allow_forgery_protection
self.allow_forgery_protection = true
@@ -402,7 +412,7 @@ def controller_path
# More methods can be hidden using <tt>hide_actions</tt>.
def hidden_actions
unless read_inheritable_attribute(:hidden_actions)
- write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map(&:to_s))
+ write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map { |m| m.to_s })
end
read_inheritable_attribute(:hidden_actions)
@@ -410,18 +420,18 @@ def hidden_actions
# Hide each of the given methods from being callable as actions.
def hide_action(*names)
- write_inheritable_attribute(:hidden_actions, hidden_actions | names.map(&:to_s))
+ write_inheritable_attribute(:hidden_actions, hidden_actions | names.map { |name| name.to_s })
end
- ## View load paths determine the bases from which template references can be made. So a call to
- ## render("test/template") will be looked up in the view load paths array and the closest match will be
- ## returned.
+ # View load paths determine the bases from which template references can be made. So a call to
+ # render("test/template") will be looked up in the view load paths array and the closest match will be
+ # returned.
def view_paths
@view_paths || superclass.view_paths
end
def view_paths=(value)
- @view_paths = ActionView::ViewLoadPaths.new(Array(value)) if value
+ @view_paths = ActionView::Base.process_view_paths(value) if value
end
# Adds a view_path to the front of the view_paths array.
@@ -603,7 +613,8 @@ def process(request, response, method = :perform_action, *arguments) #:nodoc:
#
# This takes the current URL as is and only exchanges the action. In contrast, <tt>url_for :action => 'print'</tt>
# would have slashed-off the path components after the changed action.
- def url_for(options = {}) #:doc:
+ def url_for(options = {})
+ options ||= {}
case options
when String
options
@@ -641,7 +652,7 @@ def view_paths
end
def view_paths=(value)
- @template.view_paths = ViewLoadPaths.new(value)
+ @template.view_paths = ActionView::Base.process_view_paths(value)
end
# Adds a view_path to the front of the view_paths array.
@@ -1042,29 +1053,31 @@ def redirect_to(options = {}, response_status = {}) #:doc:
status = 302
end
+ response.redirected_to= options
+ logger.info("Redirected to #{options}") if logger && logger.info?
+
case options
when %r{^\w+://.*}
- raise DoubleRenderError if performed?
- logger.info("Redirected to #{options}") if logger && logger.info?
- response.redirect(options, interpret_status(status))
- response.redirected_to = options
- @performed_redirect = true
-
+ redirect_to_full_url(options, status)
when String
- redirect_to(request.protocol + request.host_with_port + options, :status=>status)
-
+ redirect_to_full_url(request.protocol + request.host_with_port + options, status)
when :back
- request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"], :status=>status) : raise(RedirectBackError)
-
- when Hash
- redirect_to(url_for(options), :status=>status)
- response.redirected_to = options
-
+ if referer = request.headers["Referer"]
+ redirect_to(referer, :status=>status)
+ else
+ raise RedirectBackError
+ end
else
- redirect_to(url_for(options), :status=>status)
+ redirect_to_full_url(url_for(options), status)
end
end
+ def redirect_to_full_url(url, status)
+ raise DoubleRenderError if performed?
+ response.redirect(url, interpret_status(status))
+ @performed_redirect = true
+ end
+
# Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that
# intermediate caches shouldn't cache the response.
#
@@ -1188,7 +1201,7 @@ def action_methods
end
def self.action_methods
- @action_methods ||= Set.new(public_instance_methods.map(&:to_s)) - hidden_actions
+ @action_methods ||= Set.new(public_instance_methods.map { |m| m.to_s }) - hidden_actions
end
def add_variables_to_assigns
@@ -1235,9 +1248,8 @@ def template_public?(template_name = default_template_name)
end
def template_exempt_from_layout?(template_name = default_template_name)
- extension = @template && @template.pick_template_extension(template_name)
- name_with_extension = !template_name.include?('.') && extension ? "#{template_name}.#{extension}" : template_name
- @@exempt_from_layout.any? { |ext| name_with_extension =~ ext }
+ template_name = @template.pick_template(template_name).to_s if @template
+ @@exempt_from_layout.any? { |ext| template_name =~ ext }
end
def default_template_name(action_name = self.action_name)
View
23 actionpack/lib/action_controller/caching/actions.rb
@@ -27,13 +27,15 @@ module Caching
# You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy
# for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
#
- # And you can also use :if to pass a Proc that specifies when the action should be cached.
+ # And you can also use :if (or :unless) to pass a Proc that specifies when the action should be cached.
+ #
+ # Finally, if you are using memcached, you can also pass :expires_in.
#
# class ListsController < ApplicationController
# before_filter :authenticate, :except => :public
# caches_page :public
# caches_action :index, :if => Proc.new { |c| !c.request.format.json? } # cache if is not a JSON request
- # caches_action :show, :cache_path => { :project => 1 }
+ # caches_action :show, :cache_path => { :project => 1 }, :expires_in => 1.hour
# caches_action :feed, :cache_path => Proc.new { |controller|
# controller.params[:user_id] ?
# controller.send(:user_list_url, c.params[:user_id], c.params[:id]) :
@@ -56,8 +58,10 @@ module ClassMethods
def caches_action(*actions)
return unless cache_configured?
options = actions.extract_options!
- cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path))
- around_filter(cache_filter, {:only => actions}.merge(options))
+ filter_options = { :only => actions, :if => options.delete(:if), :unless => options.delete(:unless) }
+
+ cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options)
+ around_filter(cache_filter, filter_options)
end
end
@@ -80,8 +84,8 @@ def initialize(options, &block)
end
def before(controller)
- cache_path = ActionCachePath.new(controller, path_options_for(controller, @options))
- if cache = controller.read_fragment(cache_path.path)
+ cache_path = ActionCachePath.new(controller, path_options_for(controller, @options.slice(:cache_path)))
+ if cache = controller.read_fragment(cache_path.path, @options[:store_options])
controller.rendered_action_cache = true
set_content_type!(controller, cache_path.extension)
options = { :text => cache }
@@ -96,7 +100,7 @@ def before(controller)
def after(controller)
return if controller.rendered_action_cache || !caching_allowed(controller)
action_content = cache_layout? ? content_for_layout(controller) : controller.response.body
- controller.write_fragment(controller.action_cache_path.path, action_content)
+ controller.write_fragment(controller.action_cache_path.path, action_content, @options[:store_options])
end
private
@@ -162,10 +166,7 @@ def extract_extension(request)
# If there's no extension in the path, check request.format
if extension.nil?
- extension = request.format.to_sym.to_s
- if extension=='all'
- extension = nil
- end
+ extension = request.cache_format
end
extension
end
View
426 actionpack/lib/action_controller/filters.rb
@@ -7,6 +7,225 @@ def self.included(base)
end
end
+ class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc:
+ def append_filter_to_chain(filters, filter_type, &block)
+ pos = find_filter_append_position(filters, filter_type)
+ update_filter_chain(filters, filter_type, pos, &block)
+ end
+
+ def prepend_filter_to_chain(filters, filter_type, &block)
+ pos = find_filter_prepend_position(filters, filter_type)
+ update_filter_chain(filters, filter_type, pos, &block)
+ end
+
+ def create_filters(filters, filter_type, &block)
+ filters, conditions = extract_options(filters, &block)
+ filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) }
+ filters
+ end
+
+ def skip_filter_in_chain(*filters, &test)
+ filters, conditions = extract_options(filters)
+ filters.each do |filter|
+ if callback = find(filter) then delete(callback) end
+ end if conditions.empty?
+ update_filter_in_chain(filters, :skip => conditions, &test)
+ end
+
+ private
+ def update_filter_chain(filters, filter_type, pos, &block)
+ new_filters = create_filters(filters, filter_type, &block)
+ insert(pos, new_filters).flatten!
+ end
+
+ def find_filter_append_position(filters, filter_type)
+ # appending an after filter puts it at the end of the call chain
+ # before and around filters go before the first after filter in the chain
+ unless filter_type == :after
+ each_with_index do |f,i|
+ return i if f.after?
+ end
+ end
+ return -1
+ end
+
+ def find_filter_prepend_position(filters, filter_type)
+ # prepending a before or around filter puts it at the front of the call chain
+ # after filters go before the first after filter in the chain
+ if filter_type == :after
+ each_with_index do |f,i|
+ return i if f.after?
+ end
+ return -1
+ end
+ return 0
+ end
+
+ def find_or_create_filter(filter, filter_type, options = {})
+ update_filter_in_chain([filter], options)
+
+ if found_filter = find(filter) { |f| f.type == filter_type }
+ found_filter
+ else
+ filter_kind = case
+ when filter.respond_to?(:before) && filter_type == :before
+ :before
+ when filter.respond_to?(:after) && filter_type == :after
+ :after
+ else
+ :filter
+ end
+
+ case filter_type
+ when :before
+ BeforeFilter.new(filter_kind, filter, options)
+ when :after
+ AfterFilter.new(filter_kind, filter, options)
+ else
+ AroundFilter.new(filter_kind, filter, options)
+ end
+ end
+ end
+
+ def update_filter_in_chain(filters, options, &test)
+ filters.map! { |f| block_given? ? find(f, &test) : find(f) }
+ filters.compact!
+
+ map! do |filter|
+ if filters.include?(filter)
+ new_filter = filter.dup
+ new_filter.update_options!(options)
+ new_filter
+ else
+ filter
+ end
+ end
+ end
+ end
+
+ class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
+ def initialize(kind, method, options = {})
+ super
+ update_options! options
+ end
+
+ def before?
+ self.class == BeforeFilter
+ end
+
+ def after?
+ self.class == AfterFilter
+ end
+
+ def around?
+ self.class == AroundFilter
+ end
+
+ # Make sets of strings from :only/:except options
+ def update_options!(other)
+ if other
+ convert_only_and_except_options_to_sets_of_strings(other)
+ if other[:skip]
+ convert_only_and_except_options_to_sets_of_strings(other[:skip])
+ end
+ end
+
+ options.update(other)
+ end
+
+ private
+ def should_not_skip?(controller)
+ if options[:skip]
+ !included_in_action?(controller, options[:skip])
+ else
+ true
+ end
+ end
+
+ def included_in_action?(controller, options)
+ if options[:only]
+ options[:only].include?(controller.action_name)
+ elsif options[:except]
+ !options[:except].include?(controller.action_name)
+ else
+ true
+ end
+ end
+
+ def should_run_callback?(controller)
+ should_not_skip?(controller) && included_in_action?(controller, options) && super
+ end
+
+ def convert_only_and_except_options_to_sets_of_strings(opts)
+ [:only, :except].each do |key|
+ if values = opts[key]
+ opts[key] = Array(values).map(&:to_s).to_set
+ end
+ end
+ end
+ end
+
+ class AroundFilter < Filter #:nodoc:
+ def type
+ :around
+ end
+
+ def call(controller, &block)
+ if should_run_callback?(controller)
+ method = filter_responds_to_before_and_after? ? around_proc : self.method
+
+ # For around_filter do |controller, action|
+ if method.is_a?(Proc) && method.arity == 2
+ evaluate_method(method, controller, block)
+ else
+ evaluate_method(method, controller, &block)
+ end
+ else
+ block.call
+ end
+ end
+
+ private
+ def filter_responds_to_before_and_after?
+ method.respond_to?(:before) && method.respond_to?(:after)
+ end
+
+ def around_proc
+ Proc.new do |controller, action|
+ method.before(controller)
+
+ if controller.send!(:performed?)
+ controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
+ else
+ begin
+ action.call
+ ensure
+ method.after(controller)
+ end
+ end
+ end
+ end
+ end
+
+ class BeforeFilter < Filter #:nodoc:
+ def type
+ :before
+ end
+
+ def call(controller, &block)
+ super
+ if controller.send!(:performed?)
+ controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
+ end
+ end
+ end
+
+ class AfterFilter < Filter #:nodoc:
+ def type
+ :after
+ end
+ end
+
# Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do
# authentication, caching, or auditing before the intended action is performed. Or to do localization or output
# compression after the action has been performed. Filters have access to the request, response, and all the instance
@@ -245,201 +464,6 @@ def self.included(base)
# filter and controller action will not be run. If +before+ renders or redirects,
# the second half of +around+ and will still run but +after+ and the
# action will not. If +around+ fails to yield, +after+ will not be run.
-
- class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc:
- def append_filter_to_chain(filters, filter_type, &block)
- pos = find_filter_append_position(filters, filter_type)
- update_filter_chain(filters, filter_type, pos, &block)
- end
-
- def prepend_filter_to_chain(filters, filter_type, &block)
- pos = find_filter_prepend_position(filters, filter_type)
- update_filter_chain(filters, filter_type, pos, &block)
- end
-
- def create_filters(filters, filter_type, &block)
- filters, conditions = extract_options(filters, &block)
- filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) }
- filters
- end
-
- def skip_filter_in_chain(*filters, &test)
- filters, conditions = extract_options(filters)
- filters.each do |filter|
- if callback = find(filter) then delete(callback) end
- end if conditions.empty?
- update_filter_in_chain(filters, :skip => conditions, &test)
- end
-
- private
- def update_filter_chain(filters, filter_type, pos, &block)
- new_filters = create_filters(filters, filter_type, &block)
- insert(pos, new_filters).flatten!
- end
-
- def find_filter_append_position(filters, filter_type)
- # appending an after filter puts it at the end of the call chain
- # before and around filters go before the first after filter in the chain
- unless filter_type == :after
- each_with_index do |f,i|
- return i if f.after?
- end
- end
- return -1
- end
-
- def find_filter_prepend_position(filters, filter_type)
- # prepending a before or around filter puts it at the front of the call chain
- # after filters go before the first after filter in the chain
- if filter_type == :after
- each_with_index do |f,i|
- return i if f.after?
- end
- return -1
- end
- return 0
- end
-
- def find_or_create_filter(filter, filter_type, options = {})
- update_filter_in_chain([filter], options)
-
- if found_filter = find(filter) { |f| f.type == filter_type }
- found_filter
- else
- filter_kind = case
- when filter.respond_to?(:before) && filter_type == :before
- :before
- when filter.respond_to?(:after) && filter_type == :after
- :after
- else
- :filter
- end
-
- case filter_type
- when :before
- BeforeFilter.new(filter_kind, filter, options)
- when :after
- AfterFilter.new(filter_kind, filter, options)
- else
- AroundFilter.new(filter_kind, filter, options)
- end
- end
- end
-
- def update_filter_in_chain(filters, options, &test)
- filters.map! { |f| block_given? ? find(f, &test) : find(f) }
- filters.compact!
-
- map! do |filter|
- if filters.include?(filter)
- new_filter = filter.dup
- new_filter.options.merge!(options)
- new_filter
- else
- filter
- end
- end
- end
- end
-
- class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
- def before?
- self.class == BeforeFilter
- end
-
- def after?
- self.class == AfterFilter
- end
-
- def around?
- self.class == AroundFilter
- end
-
- private
- def should_not_skip?(controller)
- if options[:skip]
- !included_in_action?(controller, options[:skip])
- else
- true
- end
- end
-
- def included_in_action?(controller, options)
- if options[:only]
- Array(options[:only]).map(&:to_s).include?(controller.action_name)
- elsif options[:except]
- !Array(options[:except]).map(&:to_s).include?(controller.action_name)
- else
- true
- end
- end
-
- def should_run_callback?(controller)
- should_not_skip?(controller) && included_in_action?(controller, options) && super
- end
- end
-
- class AroundFilter < Filter #:nodoc:
- def type
- :around
- end
-
- def call(controller, &block)
- if should_run_callback?(controller)
- method = filter_responds_to_before_and_after? ? around_proc : self.method
-
- # For around_filter do |controller, action|
- if method.is_a?(Proc) && method.arity == 2
- evaluate_method(method, controller, block)
- else
- evaluate_method(method, controller, &block)
- end
- else
- block.call
- end
- end
-
- private
- def filter_responds_to_before_and_after?
- method.respond_to?(:before) && method.respond_to?(:after)
- end
-
- def around_proc
- Proc.new do |controller, action|
- method.before(controller)
-
- if controller.send!(:performed?)
- controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
- else
- begin
- action.call
- ensure
- method.after(controller)
- end
- end
- end
- end
- end
-
- class BeforeFilter < Filter #:nodoc:
- def type
- :before
- end
-
- def call(controller, &block)
- super
- if controller.send!(:performed?)
- controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
- end
- end
- end
-
- class AfterFilter < Filter #:nodoc:
- def type
- :after
- end
- end
-
module ClassMethods
# The passed <tt>filters</tt> will be appended to the filter_chain and
# will execute before the action on this controller is performed.
@@ -545,13 +569,21 @@ def filter_chain
# Returns all the before filters for this class and all its ancestors.
# This method returns the actual filter that was assigned in the controller to maintain existing functionality.
def before_filters #:nodoc:
- filter_chain.select(&:before?).map(&:method)
+ filters = []
+ filter_chain.each do |filter|
+ filters << filter.method if filter.before?
+ end
+ filters
end
# Returns all the after filters for this class and all its ancestors.
# This method returns the actual filter that was assigned in the controller to maintain existing functionality.
def after_filters #:nodoc:
- filter_chain.select(&:after?).map(&:method)
+ filters = []
+ filter_chain.each do |filter|
+ filters << filter.method if filter.after?
+ end
+ filters
end
end
View
2 actionpack/lib/action_controller/layout.rb
@@ -304,7 +304,7 @@ def action_has_layout?
end
def layout_directory?(layout_name)
- @template.view_paths.find_template_file_for_path("#{File.join('layouts', layout_name)}.#{@template.template_format}.erb") ? true : false
+ @template.file_exists?("#{File.join('layouts', layout_name)}.#{@template.template_format}")
end
end
end
View
6 actionpack/lib/action_controller/mime_responds.rb
@@ -114,7 +114,11 @@ def initialize(controller)
@request = controller.request
@response = controller.response
- @mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
+ if ActionController::Base.use_accept_header
+ @mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
+ else
+ @mime_type_priority = [@request.format]
+ end
@order = []
@responses = {}
View
82 actionpack/lib/action_controller/mime_type.rb
@@ -72,57 +72,61 @@ def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], s
end
def parse(accept_header)
- # keep track of creation order to keep the subsequent sort stable
- list = []
- accept_header.split(/,/).each_with_index do |header, index|
- params, q = header.split(/;\s*q=/)
- if params
- params.strip!
- list << AcceptItem.new(index, params, q) unless params.empty?
+ if accept_header !~ /,/
+ [Mime::Type.lookup(accept_header)]
+ else
+ # keep track of creation order to keep the subsequent sort stable
+ list = []
+ accept_header.split(/,/).each_with_index do |header, index|
+ params, q = header.split(/;\s*q=/)
+ if params
+ params.strip!
+ list << AcceptItem.new(index, params, q) unless params.empty?
+ end
end
- end
- list.sort!
+ list.sort!
- # Take care of the broken text/xml entry by renaming or deleting it
- text_xml = list.index("text/xml")
- app_xml = list.index(Mime::XML.to_s)
+ # Take care of the broken text/xml entry by renaming or deleting it
+ text_xml = list.index("text/xml")
+ app_xml = list.index(Mime::XML.to_s)
- if text_xml && app_xml
- # set the q value to the max of the two
- list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
+ if text_xml && app_xml
+ # set the q value to the max of the two
+ list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
- # make sure app_xml is ahead of text_xml in the list
- if app_xml > text_xml
- list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
- app_xml, text_xml = text_xml, app_xml
- end
+ # make sure app_xml is ahead of text_xml in the list
+ if app_xml > text_xml
+ list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
+ app_xml, text_xml = text_xml, app_xml
+ end
- # delete text_xml from the list
- list.delete_at(text_xml)
+ # delete text_xml from the list
+ list.delete_at(text_xml)
- elsif text_xml
- list[text_xml].name = Mime::XML.to_s
- end
+ elsif text_xml
+ list[text_xml].name = Mime::XML.to_s
+ end
- # Look for more specific XML-based types and sort them ahead of app/xml
+ # Look for more specific XML-based types and sort them ahead of app/xml
- if app_xml
- idx = app_xml
- app_xml_type = list[app_xml]
+ if app_xml
+ idx = app_xml
+ app_xml_type = list[app_xml]
- while(idx < list.length)
- type = list[idx]
- break if type.q < app_xml_type.q
- if type.name =~ /\+xml$/
- list[app_xml], list[idx] = list[idx], list[app_xml]
- app_xml = idx
+ while(idx < list.length)
+ type = list[idx]
+ break if type.q < app_xml_type.q
+ if type.name =~ /\+xml$/
+ list[app_xml], list[idx] = list[idx], list[app_xml]
+ app_xml = idx
+ end
+ idx += 1
end
- idx += 1
end
- end
- list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
- list
+ list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
+ list
+ end
end
end
View
46 actionpack/lib/action_controller/request.rb
@@ -82,21 +82,34 @@ def content_type
# Returns the accepted MIME type for the request
def accepts
@accepts ||=
- if @env['HTTP_ACCEPT'].to_s.strip.empty?
- [ content_type, Mime::ALL ].compact # make sure content_type being nil is not included
- else
- Mime::Type.parse(@env['HTTP_ACCEPT'])
+ begin
+ header = @env['HTTP_ACCEPT'].to_s.strip
+
+ if header.empty?
+ [content_type, Mime::ALL].compact
+ else
+ Mime::Type.parse(header)
+ end
end
end
- # Returns the Mime type for the format used in the request. If there is no format available, the first of the
- # accept types will be used. Examples:
+ # Returns the Mime type for the format used in the request.
#
# GET /posts/5.xml | request.format => Mime::XML
# GET /posts/5.xhtml | request.format => Mime::HTML
- # GET /posts/5 | request.format => request.accepts.first (usually Mime::HTML for browsers)
+ # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
def format
- @format ||= parameters[:format] ? Mime::Type.lookup_by_extension(parameters[:format]) : accepts.first
+ @format ||= begin
+ if parameters[:format]
+ Mime::Type.lookup_by_extension(parameters[:format])
+ elsif ActionController::Base.use_accept_header
+ accepts.first
+ elsif xhr?
+ Mime::Type.lookup_by_extension("js")
+ else
+ Mime::Type.lookup_by_extension("html")
+ end
+ end
end
@@ -116,19 +129,26 @@ def format=(extension)
@format = Mime::Type.lookup_by_extension(parameters[:format])
end
+ # Returns a symbolized version of the <tt>:format</tt> parameter of the request.
+ # If no format is given it returns <tt>:js</tt>for AJAX requests and <tt>:html</tt>
+ # otherwise.
def template_format
parameter_format = parameters[:format]
- case
- when parameter_format.blank? && !xhr?
- :html
- when parameter_format.blank? && xhr?
+ if parameter_format
+ parameter_format.to_sym
+ elsif xhr?
:js
else
- parameter_format.to_sym
+ :html
end
end
+ def cache_format
+ parameter_format = parameters[:format]
+ parameter_format && parameter_format.to_sym
+ end
+
# Returns true if the request's "X-Requested-With" header contains
# "XMLHttpRequest". (The Prototype Javascript library sends this header with
# every Ajax request.)
View
4 actionpack/lib/action_controller/routing.rb
@@ -88,6 +88,10 @@ module ActionController
#
# map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
#
+ # Note: The default routes, as provided by the Rails generator, make all actions in every
+ # controller accessible via GET requests. You should consider removing them or commenting
+ # them out if you're using named routes and resources.
+ #
# == Named routes
#
# Routes can be named with the syntax <tt>map.name_of_route options</tt>,
View
19 actionpack/lib/action_controller/test_process.rb
@@ -207,13 +207,9 @@ def redirect_url_match?( pattern )
# Returns the template path of the file which was used to
# render this response (or nil)
- def rendered_file(with_controller=false)
- unless template.first_render.nil?
- unless with_controller
- template.first_render
- else
- template.first_render.split('/').last || template.first_render
- end
+ def rendered_file(with_controller = false)
+ if template.first_render
+ template.first_render.to_s
end
end
@@ -404,15 +400,6 @@ def xml_http_request(request_method, action, parameters = nil, session = nil, fl
end
alias xhr :xml_http_request
- def follow_redirect
- redirected_controller = @response.redirected_to[:controller]
- if redirected_controller && redirected_controller != @controller.controller_name
- raise "Can't follow redirects outside of current controller (from #{@controller.controller_name} to #{redirected_controller})"
- end
-
- get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys)
- end
-
def assigns(key = nil)
if key.nil?
@response.template.assigns
View
8 actionpack/lib/action_view.rb
@@ -21,14 +21,14 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
+
require 'action_view/template_handlers'
-require 'action_view/template_file'
-require 'action_view/view_load_paths'
+require 'action_view/renderable'
+require 'action_view/renderable_partial'
-require 'action_view/renderer'
require 'action_view/template'
-require 'action_view/partial_template'
require 'action_view/inline_template'
+require 'action_view/paths'
require 'action_view/base'
require 'action_view/partials'
View
118 actionpack/lib/action_view/base.rb
@@ -3,6 +3,12 @@ class ActionViewError < StandardError #:nodoc:
end
class MissingTemplate < ActionViewError #:nodoc:
+ def initialize(paths, path, template_format = nil)
+ full_template_path = path.include?('.') ? path : "#{path}.erb"
+ display_paths = paths.join(':')
+ template_type = (path =~ /layouts/i) ? 'layout' : 'template'
+ super("Missing #{template_type} #{full_template_path} in view path #{display_paths}")
+ end
end
# Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
@@ -151,7 +157,6 @@ class MissingTemplate < ActionViewError #:nodoc:
#
# See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details.
class Base
- extend TemplateHandlers
include ERB::Util
attr_accessor :base_path, :assigns, :template_extension, :first_render
@@ -166,7 +171,7 @@ class << self
delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB'
end
- # Specify whether file modification times should be checked to see if a template needs recompilation
+ # Specify whether templates should be cached. Otherwise the file we be read everytime it is accessed.
@@cache_template_loading = false
cattr_accessor :cache_template_loading
@@ -180,6 +185,10 @@ def self.cache_template_extensions=(*args)
@@debug_rjs = false
cattr_accessor :debug_rjs
+ # A warning will be displayed whenever an action results in a cache miss on your view paths.
+ @@warn_cache_misses = false
+ cattr_accessor :warn_cache_misses
+
attr_internal :request
delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
@@ -190,12 +199,6 @@ module CompiledTemplates #:nodoc:
end
include CompiledTemplates
- # Maps inline templates to their method names
- cattr_accessor :method_names
- @@method_names = {}
- # Map method names to the names passed in local assigns so far
- @@template_args = {}
-
# Cache public asset paths
cattr_reader :computed_public_paths
@@computed_public_paths = {}
@@ -213,6 +216,10 @@ def self.helper_modules #:nodoc:
return helpers
end
+ def self.process_view_paths(value)
+ ActionView::PathSet.new(Array(value))
+ end
+
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc:
@assigns = assigns_for_first_render
@assigns_added = nil
@@ -223,12 +230,14 @@ def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)
attr_reader :view_paths
def view_paths=(paths)
- @view_paths = ViewLoadPaths.new(Array(paths))
+ @view_paths = self.class.process_view_paths(paths)
end
# Renders the template present at <tt>template_path</tt> (relative to the view_paths array).
# The hash in <tt>local_assigns</tt> is made available as local variables.
def render(options = {}, local_assigns = {}, &block) #:nodoc:
+ local_assigns ||= {}
+
if options.is_a?(String)
render_file(options, nil, local_assigns)
elsif options == :update
@@ -263,13 +272,9 @@ def file_public?(template_path)#:nodoc:
template_path.split('/').last[0,1] != '_'
end
- # Returns a symbolized version of the <tt>:format</tt> parameter of the request,
- # or <tt>:html</tt> by default.
- #
- # EXCEPTION: If the <tt>:format</tt> parameter is not set, the Accept header will be examined for
- # whether it contains the JavaScript mime type as its first priority. If that's the case,
- # it will be used. This ensures that Ajax applications can use the same URL to support both
- # JavaScript and non-JavaScript users.
+ # The format to be used when choosing between multiple templates with
+ # the same name but differing formats. See +Request#template_format+
+ # for more details.
def template_format
return @template_format if @template_format
@@ -281,21 +286,50 @@ def template_format
end
def file_exists?(template_path)
- view_paths.template_exists?(template_file_from_name(template_path))
+ pick_template(template_path) ? true : false
+ rescue MissingTemplate
+ false
end
# Gets the extension for an existing template with the given template_path.
# Returns the format with the extension if that template exists.
#
- # pick_template_extension('users/show')
- # # => 'html.erb'
+ # pick_template('users/show')
+ # # => 'users/show.html.erb'
#
- # pick_template_extension('users/legacy')
- # # => "rhtml"
+ # pick_template('users/legacy')
+ # # => 'users/legacy.rhtml'
#
- def pick_template_extension(template_path)
- if template = template_file_from_name(template_path)
- template.extension
+ def pick_template(template_path)
+ path = template_path.sub(/^\//, '')
+ if m = path.match(/(.*)\.(\w+)$/)
+ template_file_name, template_file_extension = m[1], m[2]
+ else
+ template_file_name = path
+ end
+
+ # OPTIMIZE: Checks to lookup template in view path
+ if template = self.view_paths["#{template_file_name}.#{template_format}"]
+ template
+ elsif template = self.view_paths[template_file_name]
+ template
+ elsif first_render && template = self.view_paths["#{template_file_name}.#{first_render.extension}"]
+ template
+ elsif template_format == :js && template = self.view_paths["#{template_file_name}.html"]
+ @template_format = :html
+ template
+ else
+ template = Template.new(template_path, view_paths)
+
+ if self.class.warn_cache_misses && logger = ActionController::Base.logger
+ logger.debug "[PERFORMANCE] Rendering a template that was " +
+ "not found in view path. Templates outside the view path are " +
+ "not cached and result in expensive disk operations. Move this " +
+ "file into #{view_paths.join(':')} or add the folder to your " +
+ "view path list"
+ end
+
+ template
end
end
@@ -303,6 +337,10 @@ def pick_template_extension(template_path)
# Renders the template present at <tt>template_path</tt>. The hash in <tt>local_assigns</tt>
# is made available as local variables.
def render_file(template_path, use_full_path = nil, local_assigns = {}) #:nodoc:
+ unless use_full_path == nil
+ ActiveSupport::Deprecation.warn("use_full_path option has been deprecated and has no affect.", caller)
+ end
+
if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/")
raise ActionViewError, <<-END_ERROR
Due to changes in ActionMailer, you need to provide the mailer_name along with the template name.
@@ -316,11 +354,12 @@ def render_file(template_path, use_full_path = nil, local_assigns = {}) #:nodoc:
END_ERROR
end
- Template.new(self, template_path, use_full_path, local_assigns).render_template
+ template = pick_template(template_path)
+ template.render_template(self, local_assigns)
end
def render_inline(text, local_assigns = {}, type = nil)
- InlineTemplate.new(self, text, local_assigns, type).render
+ InlineTemplate.new(text, type).render(self, local_assigns)
end
def wrap_content_for_layout(content)
@@ -343,33 +382,10 @@ def assign_variables_from_controller
@assigns.each { |key, value| instance_variable_set("@#{key}", value) }
end
- def execute(template)
- send(template.method, template.locals) do |*names|
+ def execute(template, local_assigns = {})
+ send(template.method(local_assigns), local_assigns) do |*names|
instance_variable_get "@content_for_#{names.first || 'layout'}"
end
end
-
- def template_file_from_name(template_name)
- template_name = TemplateFile.from_path(template_name)
- pick_template(template_name) unless template_name.extension
- end
-
- def pick_template(file)
- if f = self.view_paths.find_template_file_for_path(file.dup_with_extension(template_format)) || file_from_first_render(file)
- f
- elsif template_format == :js && f = self.view_paths.find_template_file_for_path(file.dup_with_extension(:html))
- @template_format = :html
- f
- else
- nil
- end
- end
-
- # Determine the template extension from the <tt>@first_render</tt> filename
- def file_from_first_render(file)
- if extension = File.basename(@first_render.to_s)[/^[^.]+\.(.+)$/, 1]
- file.dup_with_extension(extension)
- end
- end
end
end
View
56 actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -209,6 +209,10 @@ def javascript_path(source)
# Note that the default javascript files will be included first. So Prototype and Scriptaculous are available to
# all subsequently included files.
#
+ # If you want Rails to search in all the subdirectories under javascripts, you should explicitly set <tt>:recursive</tt>:
+ #
+ # javascript_include_tag :all, :recursive => true
+ #
# == Caching multiple javascripts into one
#
# You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
@@ -235,18 +239,23 @@ def javascript_path(source)
#
# javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is true =>
# <script type="text/javascript" src="/javascripts/shop.js"></script>
+ #
+ # The <tt>:recursive</tt> option is also available for caching:
+ #
+ # javascript_include_tag :all, :cache => true, :recursive => true
def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
cache = options.delete("cache")
+ recursive = options.delete("recursive")
if ActionController::Base.perform_caching && cache
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
- write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources)) unless File.exists?(joined_javascript_path)
+ write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) unless File.exists?(joined_javascript_path)
javascript_src_tag(joined_javascript_name, options)
else
- expand_javascript_sources(sources).collect { |source| javascript_src_tag(source, options) }.join("\n")
+ expand_javascript_sources(sources, recursive).collect { |source| javascript_src_tag(source, options) }.join("\n")
end
end
@@ -332,13 +341,17 @@ def stylesheet_path(source)
# <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />
# <link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />
#
- # You can also include all styles in the stylesheet directory using <tt>:all</tt> as the source:
+ # You can also include all styles in the stylesheets directory using <tt>:all</tt> as the source:
#
# stylesheet_link_tag :all # =>
# <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
#
+ # If you want Rails to search in all the subdirectories under stylesheets, you should explicitly set <tt>:recursive</tt>:
+ #
+ # stylesheet_link_tag :all, :recursive => true
+ #
# == Caching multiple stylesheets into one
#
# You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
@@ -362,18 +375,23 @@ def stylesheet_path(source)
#
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is true =>
# <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # The <tt>:recursive</tt> option is also available for caching:
+ #
+ # stylesheet_link_tag :all, :cache => true, :recursive => true
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
cache = options.delete("cache")
+ recursive = options.delete("recursive")
if ActionController::Base.perform_caching && cache
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
- write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources)) unless File.exists?(joined_stylesheet_path)
+ write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) unless File.exists?(joined_stylesheet_path)
stylesheet_tag(joined_stylesheet_name, options)
else
- expand_stylesheet_sources(sources).collect { |source| stylesheet_tag(source, options) }.join("\n")
+ expand_stylesheet_sources(sources, recursive).collect { |source| stylesheet_tag(source, options) }.join("\n")
end
end
@@ -556,18 +574,19 @@ def stylesheet_tag(source, options)
tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
end
- def compute_javascript_paths(sources)
- expand_javascript_sources(sources).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
+ def compute_javascript_paths(*args)
+ expand_javascript_sources(*args).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
end
- def compute_stylesheet_paths(sources)
- expand_stylesheet_sources(sources).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
+ def compute_stylesheet_paths(*args)
+ expand_stylesheet_sources(*args).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
end
- def expand_javascript_sources(sources)
+ def expand_javascript_sources(sources, recursive = false)
if sources.include?(:all)
- all_javascript_files = Dir[File.join(JAVASCRIPTS_DIR, '*.js')].collect { |file| File.basename(file).gsub(/\.\w+$/, '') }.sort
- @@all_javascript_sources ||= ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
+ all_javascript_files = collect_asset_files(JAVASCRIPTS_DIR, ('**' if recursive), '*.js')
+ @@all_javascript_sources ||= {}
+ @@all_javascript_sources[recursive] ||= ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
else
expanded_sources = sources.collect do |source|
determine_source(source, @@javascript_expansions)
@@ -577,9 +596,10 @@ def expand_javascript_sources(sources)
end
end
- def expand_stylesheet_sources(sources)
+ def expand_stylesheet_sources(sources, recursive)
if sources.first == :all
- @@all_stylesheet_sources ||= Dir[File.join(STYLESHEETS_DIR, '*.css')].collect { |file| File.basename(file).gsub(/\.\w+$/, '') }.sort
+ @@all_stylesheet_sources ||= {}
+ @@all_stylesheet_sources[recursive] ||= collect_asset_files(STYLESHEETS_DIR, ('**' if recursive), '*.css')
else
sources.collect do |source|
determine_source(source, @@stylesheet_expansions)
@@ -604,6 +624,14 @@ def write_asset_file_contents(joined_asset_path, asset_paths)
FileUtils.mkdir_p(File.dirname(joined_asset_path))
File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
end
+
+ def collect_asset_files(*path)
+ dir = path.first
+
+ Dir[File.join(*path.compact)].collect do |file|
+ file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '')
+ end.sort
+ end
end
end
end
View
2 actionpack/lib/action_view/helpers/cache_helper.rb
@@ -32,7 +32,7 @@ module CacheHelper
# <i>Topics listed alphabetically</i>
# <% end %>
def cache(name = {}, options = nil, &block)
- handler = Base.handler_class_for_extension(current_render_extension.to_sym)
+ handler = Template.handler_class_for_extension(current_render_extension.to_sym)
handler.new(@controller).cache_fragment(block, name, options)
end
end
View
3 actionpack/lib/action_view/helpers/capture_helper.rb
@@ -34,9 +34,8 @@ def capture(*args, &block)
# Return captured buffer in erb.
if block_called_from_erb?(block)
with_output_buffer { block.call(*args) }
-
- # Return block result otherwise, but protect buffer also.
else
+ # Return block result otherwise, but protect buffer also.
with_output_buffer { return block.call(*args) }
end
end
View
7 actionpack/lib/action_view/helpers/date_helper.rb
@@ -159,7 +159,10 @@ def date_select(object_name, method, options = {}, html_options = {})
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified
# time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+).
# You can include the seconds with <tt>:include_seconds</tt>.
- #
+ #
+ # This method will also generate 3 input hidden tags, for the actual year, month and day unless the option
+ # <tt>:ignore_date</tt> is set to +true+.
+ #
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
@@ -655,7 +658,7 @@ def date_or_time_select(options, html_options = {})
order.reverse.each do |param|
# Send hidden fields for discarded elements once output has started
# This ensures AR can reconstruct valid dates using ParseDate
- next if discard[param] && date_or_time_select.empty?
+ next if discard[param] && (date_or_time_select.empty? || options[:ignore_date])
date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param])), html_options))
date_or_time_select.insert(0,
View
9 actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -397,7 +397,7 @@ def remote_form_for(record_or_name_or_array, *args, &proc)
# # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create',
# # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
# # return false;" type="button" value="Create" />
- # <%= submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %>
+ # <%= button_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %>
#
# # Submit to the remote action update and update the DIV succeed or fail based
# # on the success or failure of the request
@@ -405,11 +405,13 @@ def remote_form_for(record_or_name_or_array, *args, &proc)
# # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'},
# # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
# # return false;" type="button" value="Update" />
- # <%= submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' },
+ # <%= button_to_remote 'update_btn', 'Update', :url => { :action => 'update' },
# :update => { :success => "succeed", :failure => "fail" }
#
# <tt>options</tt> argument is the same as in form_remote_tag.
- def submit_to_remote(name, value, options = {})
+ #
+ # Note: This method used to be called submit_to_remote, but that's now just an alias for button_to_remote
+ def button_to_remote(name, value, options = {})
options[:with] ||= 'Form.serialize(this.form)'
options[:html] ||= {}
@@ -420,6 +422,7 @@ def submit_to_remote(name, value, options = {})
tag("input", options[:html], false)
end
+ alias_method :submit_to_remote, :button_to_remote
# Returns '<tt>eval(request.responseText)</tt>' which is the JavaScript function
# that +form_remote_tag+ can call in <tt>:complete</tt> to evaluate a multiple
View
2 actionpack/lib/action_view/helpers/text_helper.rb
@@ -27,7 +27,7 @@ module TextHelper
# %>
def concat(string, unused_binding = nil)
if unused_binding
- ActiveSupport::Deprecation.warn("The binding argument of #concat is no longer needed. Please remove it from your views and helpers.")
+ ActiveSupport::Deprecation.warn("The binding argument of #concat is no longer needed. Please remove it from your views and helpers.", caller)
end
output_buffer << string
View
8 actionpack/lib/action_view/helpers/url_helper.rb
@@ -63,17 +63,15 @@ module UrlHelper
# # calls @workshop.to_s
# # => /workshops/5
def url_for(options = {})
+ options ||= {}
case options
when Hash
- show_path = options[:host].nil? ? true : false
- options = { :only_path => show_path }.update(options.symbolize_keys)
+ options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
escape = options.key?(:escape) ? options.delete(:escape) : true
url = @controller.send(:url_for, options)
when String
escape = true
url = options
- when NilClass
- url = @controller.send(:url_for, nil)
else
escape = false
url = polymorphic_path(options)
@@ -468,7 +466,7 @@ def mail_to(email_address, name = nil, html_options = {})
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")
if encode == "javascript"
- "document.write('#{content_tag("a", name || email_address, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
+ "document.write('#{content_tag("a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
string << sprintf("%%%x", c)
end
"<script type=\"#{Mime::JS}\">eval(unescape('#{string}'))</script>"
View
17 actionpack/lib/action_view/inline_template.rb
@@ -1,16 +1,19 @@
module ActionView #:nodoc:
class InlineTemplate #:nodoc:
- include Renderer
+ include Renderable
- def initialize(view, source, locals = {}, type = nil)
- @view = view
+ attr_reader :source, :extension, :method_segment
+ def initialize(source, type = nil)
@source = source
@extension = type
- @locals = locals || {}
-
- @method_key = @source
- @handler = Base.handler_class_for_extension(@extension).new(@view)
+ @method_segment = "inline_#{@source.hash.abs}"
end
+
+ private
+ # Always recompile inline templates
+ def recompile?(local_assigns)
+ true
+ end
end
end
View
69 actionpack/lib/action_view/partial_template.rb
@@ -1,69 +0,0 @@
-module ActionView #:nodoc:
- class PartialTemplate < Template #:nodoc:
- attr_reader :variable_name, :object, :as
-
- def initialize(view, partial_path, object = nil, locals = {}, as = nil)
- @view_controller = view.controller if view.respond_to?(:controller)
- @as = as
- set_path_and_variable_name!(partial_path)
- super(view, @path, nil, locals)
- add_object_to_local_assigns!(object)
-
- # This is needed here in order to compile template with knowledge of 'counter'
- initialize_counter!
-
- # Prepare early. This is a performance optimization for partial collections
- prepare!
- end
-
- def render
- ActionController::Base.benchmark("Rendered #{@path.path_without_format_and_extension}", Logger::DEBUG, false) do
- super
- end
- end
-
- def render_member(object)
- @locals[:object] = @locals[@variable_name] = object
- @locals[as] = object if as
-
- template = render_template
- @locals[@counter_name] += 1
- @locals.delete(as)
- @locals.delete(@variable_name)
- @locals.delete(:object)
-
- template
- end
-
- def counter=(num)
- @locals[@counter_name] = num
- end
-
- private
- def add_object_to_local_assigns!(object)
- @locals[:object] ||=
- @locals[@variable_name] ||= object || @view_controller.instance_variable_get("@#{variable_name}")
- @locals[as] ||= @locals[:object] if as
- end
-
- def set_path_and_variable_name!(partial_path)
- if partial_path.include?('/')
- @variable_name = File.basename(partial_path)
- @path = "#{File.dirname(partial_path)}/_#{@variable_name}"
- elsif @view_controller
- @variable_name = partial_path
- @path = "#{@view_controller.class.controller_path}/_#{@variable_name}"
- else
- @variable_name = partial_path
- @path = "_#{@variable_name}"
- end
-
- @variable_name = @variable_name.sub(/\..*$/, '').to_sym
- end
-
- def initialize_counter!
- @counter_name ||= "#{@variable_name}_counter".to_sym
- @locals[@counter_name] = 0
- end
- end
-end
View
58 actionpack/lib/action_view/partials.rb
@@ -104,10 +104,12 @@ module ActionView
module Partials
private
def render_partial(partial_path, object_assigns = nil, local_assigns = {}) #:nodoc:
+ local_assigns ||= {}
+
case partial_path
when String, Symbol, NilClass
- # Render the template
- ActionView::PartialTemplate.new(self, partial_path, object_assigns, local_assigns).render_template
+ variable_name, path = partial_pieces(partial_path)
+ pick_template(path).render_partial(self, variable_name, object_assigns, local_assigns)
when ActionView::Helpers::FormBuilder
builder_partial_path = partial_path.class.to_s.demodulize.underscore.sub(/_builder$/, '')
render_partial(builder_partial_path, object_assigns, (local_assigns || {}).merge(builder_partial_path.to_sym => partial_path))
@@ -128,31 +130,43 @@ def render_partial_collection(partial_path, collection, partial_spacer_template
local_assigns = local_assigns ? local_assigns.clone : {}
spacer = partial_spacer_template ? render(:partial => partial_spacer_template) : ''
+ _partial_pieces = {}
+ _templates = {}
- if partial_path.nil?
- render_partial_collection_with_unknown_partial_path(collection, local_assigns, as)
- else
- render_partial_collection_with_known_partial_path(collection, partial_path, local_assigns, as)
- end.join(spacer)
- end
+ index = 0
+ collection.map do |object|
+ _partial_path ||= partial_path || ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path)
+ variable_name, path = _partial_pieces[_partial_path] ||= partial_pieces(_partial_path)
+ template = _templates[path] ||= pick_template(path)
- def render_partial_collection_with_known_partial_path(collection, partial_path, local_assigns, as)
- template = ActionView::PartialTemplate.new(self, partial_path, nil, local_assigns, as)
- collection.map do |element|
- template.render_member(element)
- end
+ local_assigns["#{variable_name}_counter".to_sym] = index
+ local_assigns[:object] = local_assigns[variable_name] = object
+ local_assigns[as] = object if as
+
+ result = template.render_partial(self, variable_name, object, local_assigns)
+
+ local_assigns.delete(as)
+ local_assigns.delete(variable_name)
+ local_assigns.delete(:object)
+ index += 1
+
+ result
+ end.join(spacer)
end
- def render_partial_collection_with_unknown_partial_path(collection, local_assigns, as)
- templates = Hash.new
- i = 0
- collection.map do |element|
- partial_path = ActionController::RecordIdentifier.partial_path(element, controller.class.controller_path)
- template = templates[partial_path] ||= ActionView::PartialTemplate.new(self, partial_path, nil, local_assigns, as)
- template.counter = i
- i += 1
- template.render_member(element)
+ def partial_pieces(partial_path)
+ if partial_path.include?('/')
+ variable_name = File.basename(partial_path)
+ path = "#{File.dirname(partial_path)}/_#{variable_name}"
+ elsif respond_to?(:controller)
+ variable_name = partial_path
+ path = "#{controller.class.controller_path}/_#{variable_name}"
+ else
+ variable_name = partial_path
+ path = "_#{variable_name}"
end
+ variable_name = variable_name.sub(/\..*$/, '').to_sym
+ return variable_name, path
end
end
end
View
96 actionpack/lib/action_view/paths.rb
@@ -0,0 +1,96 @@
+module ActionView #:nodoc:
+ class PathSet < Array #:nodoc:
+ def self.type_cast(obj)
+ if obj.is_a?(String)
+ if Base.warn_cache_misses && defined?(Rails) && Rails.initialized?
+ Rails.logger.debug "[PERFORMANCE] Processing view path during a " +
+ "request. This an expense disk operation that should be done at " +
+ "boot. You can manually process this view path with " +
+ "ActionView::Base.process_view_paths(#{obj.inspect}) and set it " +
+ "as your view path"
+ end
+ Path.new(obj)
+ else
+ obj
+ end
+ end
+
+ class Path #:nodoc:
+ attr_reader :path, :paths
+ delegate :to_s, :to_str, :inspect, :to => :path
+
+ def initialize(path)
+ @path = path.freeze
+ reload!
+ end
+
+ def ==(path)
+ to_str == path.to_str
+ end
+
+ def [](path)
+ @paths[path]
+ end
+
+ # Rebuild load path directory cache
+ def reload!
+ @paths = {}
+
+ templates_in_path do |template|
+ @paths[template.path] = template
+ @paths[template.path_without_extension] ||= template
+ end
+
+ @paths.freeze
+ end
+
+ private
+ def templates_in_path