aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
authorSven Fuchs <svenfuchs@artweb-design.de>2008-07-16 03:41:11 +0200
committerSven Fuchs <svenfuchs@artweb-design.de>2008-07-16 03:41:11 +0200
commit931f366ffcacc0444fcca2fb2e2b44644db9642f (patch)
tree4c056de1273d23e2b8494cfe452caaeb98ade820 /actionpack/lib
parent8691e255402b27eae594530001227fc05416a00c (diff)
parentfbef982e4b906b879240a35a1ecff447007da6b2 (diff)
downloadrails-931f366ffcacc0444fcca2fb2e2b44644db9642f.tar.gz
rails-931f366ffcacc0444fcca2fb2e2b44644db9642f.tar.bz2
rails-931f366ffcacc0444fcca2fb2e2b44644db9642f.zip
merge forward to current rails/master
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/action_controller/assertions/response_assertions.rb107
-rwxr-xr-xactionpack/lib/action_controller/base.rb90
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb23
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb18
-rw-r--r--actionpack/lib/action_controller/filters.rb414
-rw-r--r--actionpack/lib/action_controller/layout.rb4
-rw-r--r--actionpack/lib/action_controller/mime_responds.rb6
-rw-r--r--actionpack/lib/action_controller/mime_type.rb82
-rw-r--r--actionpack/lib/action_controller/mime_types.rb3
-rw-r--r--actionpack/lib/action_controller/polymorphic_routes.rb37
-rw-r--r--actionpack/lib/action_controller/rack_process.rb35
-rwxr-xr-xactionpack/lib/action_controller/request.rb49
-rw-r--r--actionpack/lib/action_controller/rescue.rb2
-rw-r--r--actionpack/lib/action_controller/resources.rb6
-rw-r--r--actionpack/lib/action_controller/routing.rb4
-rw-r--r--actionpack/lib/action_controller/routing/builder.rb3
-rw-r--r--actionpack/lib/action_controller/templates/rescues/diagnostics.erb4
-rw-r--r--actionpack/lib/action_controller/templates/rescues/template_error.erb4
-rw-r--r--actionpack/lib/action_controller/test_process.rb28
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/document.rb2
-rw-r--r--actionpack/lib/action_view.rb8
-rw-r--r--actionpack/lib/action_view/base.rb210
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb62
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb7
-rwxr-xr-xactionpack/lib/action_view/helpers/date_helper.rb50
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb26
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb27
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb50
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb137
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb384
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb38
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb10
-rw-r--r--actionpack/lib/action_view/inline_template.rb20
-rw-r--r--actionpack/lib/action_view/partial_template.rb70
-rw-r--r--actionpack/lib/action_view/partials.rb45
-rw-r--r--actionpack/lib/action_view/paths.rb96
-rw-r--r--actionpack/lib/action_view/renderable.rb78
-rw-r--r--actionpack/lib/action_view/renderable_partial.rb36
-rw-r--r--actionpack/lib/action_view/template.rb128
-rw-r--r--actionpack/lib/action_view/template_error.rb4
-rw-r--r--actionpack/lib/action_view/template_file.rb88
-rw-r--r--actionpack/lib/action_view/template_handler.rb14
-rw-r--r--actionpack/lib/action_view/template_handlers/builder.rb20
-rw-r--r--actionpack/lib/action_view/template_handlers/compilable.rb112
-rw-r--r--actionpack/lib/action_view/template_handlers/erb.rb12
-rw-r--r--actionpack/lib/action_view/template_handlers/rjs.rb19
-rw-r--r--actionpack/lib/action_view/view_load_paths.rb103
49 files changed, 1360 insertions, 1428 deletions
diff --git a/actionpack/lib/action_controller/assertions/response_assertions.rb b/actionpack/lib/action_controller/assertions/response_assertions.rb
index 3deda0b45a..765225ae24 100644
--- a/actionpack/lib/action_controller/assertions/response_assertions.rb
+++ b/actionpack/lib/action_controller/assertions/response_assertions.rb
@@ -56,74 +56,24 @@ module ActionController
# # 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
@@ -137,36 +87,37 @@ module ActionController
#
def assert_template(expected = nil, message=nil)
clean_backtrace do
- rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file
+ rendered = @response.rendered_template
msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
assert_block(msg) do
if expected.nil?
- !@response.rendered_with_file?
+ @response.rendered_template.nil?
else
- expected == rendered
+ rendered.to_s.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
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index bf34edcd85..df94f78f18 100755
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -340,6 +340,16 @@ module ActionController #:nodoc:
cattr_accessor :optimise_named_routes
self.optimise_named_routes = true
+ # 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 @@ module ActionController #:nodoc:
# 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 @@ module ActionController #:nodoc:
# 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 @@ module ActionController #: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 @@ module ActionController #:nodoc:
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.
@@ -702,6 +713,9 @@ module ActionController #:nodoc:
# # builds the complete response.
# render :partial => "person", :collection => @winners
#
+ # # Renders a collection of partials but with a custom local variable name
+ # render :partial => "admin_person", :collection => @winners, :as => :person
+ #
# # Renders the same collection of partials, but also renders the
# # person_divider partial between each person partial.
# render :partial => "person", :collection => @winners, :spacer_template => "person_divider"
@@ -733,6 +747,9 @@ module ActionController #:nodoc:
# # Renders the template located in [TEMPLATE_ROOT]/weblog/show.r(html|xml) (in Rails, app/views/weblog/show.erb)
# render :template => "weblog/show"
#
+ # # Renders the template with a local variable
+ # render :template => "weblog/show", :locals => {:customer => Customer.new}
+ #
# === Rendering a file
#
# File rendering works just like action rendering except that it takes a filesystem path. By default, the path
@@ -852,22 +869,21 @@ module ActionController #:nodoc:
else
if file = options[:file]
- render_for_file(file, options[:status], options[:use_full_path], options[:locals] || {})
+ render_for_file(file, options[:status], nil, options[:locals] || {})
elsif template = options[:template]
- render_for_file(template, options[:status], true)
+ render_for_file(template, options[:status], true, options[:locals] || {})
elsif inline = options[:inline]
add_variables_to_assigns
- tmpl = ActionView::InlineTemplate.new(@template, options[:inline], options[:locals], options[:type])
- render_for_text(@template.render_template(tmpl), options[:status])
+ render_for_text(@template.render(options), options[:status])
elsif action_name = options[:action]
template = default_template_name(action_name.to_s)
if options[:layout] && !template_exempt_from_layout?(template)
- render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true)
+ render_with_a_layout(:file => template, :status => options[:status], :layout => true)
else
- render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true)
+ render_with_no_layout(:file => template, :status => options[:status])
end
elsif xml = options[:xml]
@@ -887,12 +903,12 @@ module ActionController #:nodoc:
if collection = options[:collection]
render_for_text(
@template.send!(:render_partial_collection, partial, collection,
- options[:spacer_template], options[:locals]), options[:status]
+ options[:spacer_template], options[:locals], options[:as]), options[:status]
)
else
render_for_text(
@template.send!(:render_partial, partial,
- ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]), options[:status]
+ options[:object], options[:locals]), options[:status]
)
end
@@ -1037,29 +1053,31 @@ module ActionController #:nodoc:
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.
#
@@ -1092,10 +1110,10 @@ module ActionController #:nodoc:
private
- def render_for_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc:
+ def render_for_file(template_path, status = nil, use_full_path = nil, locals = {}) #:nodoc:
add_variables_to_assigns
logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
- render_for_text(@template.render_file(template_path, use_full_path, locals), status)
+ render_for_text(@template.render(:file => template_path, :locals => locals), status)
end
def render_for_text(text = nil, status = nil, append_response = false) #:nodoc:
@@ -1183,7 +1201,7 @@ module ActionController #:nodoc:
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
@@ -1230,8 +1248,8 @@ module ActionController #:nodoc:
end
def template_exempt_from_layout?(template_name = default_template_name)
- template_name = @template.send(:template_file_from_name, template_name) if @template
- @@exempt_from_layout.any? { |ext| template_name.to_s =~ 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)
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index 65a36f7f98..f3535f8330 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -27,13 +27,15 @@ module ActionController #:nodoc:
# 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 ActionController #:nodoc:
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 @@ module ActionController #:nodoc:
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 @@ module ActionController #:nodoc:
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 @@ module ActionController #:nodoc:
# 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
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index e4f5de44ab..b1f25fdf5c 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -60,17 +60,17 @@ module ActionController #:nodoc:
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
end
- def fragment_for(block, name = {}, options = nil) #:nodoc:
- unless perform_caching then block.call; return end
-
- buffer = yield
-
- if cache = read_fragment(name, options)
- buffer.concat(cache)
+ def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
+ if perform_caching
+ if cache = read_fragment(name, options)
+ buffer.concat(cache)
+ else
+ pos = buffer.length
+ block.call
+ write_fragment(name, buffer[pos..-1], options)
+ end
else
- pos = buffer.length
block.call
- write_fragment(name, buffer[pos..-1], options)
end
end
diff --git a/actionpack/lib/action_controller/filters.rb b/actionpack/lib/action_controller/filters.rb
index 60d92d9b98..10dc0cc45b 100644
--- a/actionpack/lib/action_controller/filters.rb
+++ b/actionpack/lib/action_controller/filters.rb
@@ -7,6 +7,225 @@ module ActionController #:nodoc:
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 @@ module ActionController #:nodoc:
# 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.
diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb
index 0721f71498..8b6febe254 100644
--- a/actionpack/lib/action_controller/layout.rb
+++ b/actionpack/lib/action_controller/layout.rb
@@ -254,7 +254,7 @@ module ActionController #:nodoc:
@template.instance_variable_set("@content_for_layout", content_for_layout)
response.layout = layout
status = template_with_options ? options[:status] : nil
- render_for_text(@template.render_file(layout, true), status)
+ render_for_text(@template.render(layout), status)
else
render_with_no_layout(options, extra_options, &block)
end
@@ -304,7 +304,7 @@ module ActionController #:nodoc:
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
diff --git a/actionpack/lib/action_controller/mime_responds.rb b/actionpack/lib/action_controller/mime_responds.rb
index 1dbd8b9e6f..29294476f7 100644
--- a/actionpack/lib/action_controller/mime_responds.rb
+++ b/actionpack/lib/action_controller/mime_responds.rb
@@ -114,7 +114,11 @@ module ActionController #:nodoc:
@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 = {}
diff --git a/actionpack/lib/action_controller/mime_type.rb b/actionpack/lib/action_controller/mime_type.rb
index fa123f7808..a7215e6ea3 100644
--- a/actionpack/lib/action_controller/mime_type.rb
+++ b/actionpack/lib/action_controller/mime_type.rb
@@ -72,57 +72,61 @@ module Mime
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
diff --git a/actionpack/lib/action_controller/mime_types.rb b/actionpack/lib/action_controller/mime_types.rb
index 01a266d3fe..2d7fba1173 100644
--- a/actionpack/lib/action_controller/mime_types.rb
+++ b/actionpack/lib/action_controller/mime_types.rb
@@ -17,4 +17,5 @@ Mime::Type.register "multipart/form-data", :multipart_form
Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
# http://www.ietf.org/rfc/rfc4627.txt
-Mime::Type.register "application/json", :json, %w( text/x-json ) \ No newline at end of file
+# http://www.json.org/JSONRequest.html
+Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) \ No newline at end of file
diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb
index 509fa6a08e..7c30bf0778 100644
--- a/actionpack/lib/action_controller/polymorphic_routes.rb
+++ b/actionpack/lib/action_controller/polymorphic_routes.rb
@@ -48,6 +48,9 @@ module ActionController
#
# # calls post_url(post)
# polymorphic_url(post) # => "http://example.com/posts/1"
+ # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
+ # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
+ # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
#
# ==== Options
#
@@ -83,8 +86,6 @@ module ActionController
else [ record_or_hash_or_array ]
end
- args << format if format
-
inflection =
case
when options[:action].to_s == "new"
@@ -96,6 +97,9 @@ module ActionController
else
:singular
end
+
+ args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
+ args << format if format
named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
send!(named_route, *args)
@@ -136,11 +140,19 @@ module ActionController
else
record = records.pop
route = records.inject("") do |string, parent|
- string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
+ if parent.is_a?(Symbol) || parent.is_a?(String)
+ string << "#{parent}_"
+ else
+ string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
+ end
end
end
- route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
+ if record.is_a?(Symbol) || record.is_a?(String)
+ route << "#{record}_"
+ else
+ route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
+ end
action_prefix(options) + namespace + route + routing_type(options).to_s
end
@@ -163,16 +175,17 @@ module ActionController
end
end
+ # Remove the first symbols from the array and return the url prefix
+ # implied by those symbols.
def extract_namespace(record_or_hash_or_array)
- returning "" do |namespace|
- if record_or_hash_or_array.is_a?(Array)
- record_or_hash_or_array.delete_if do |record_or_namespace|
- if record_or_namespace.is_a?(String) || record_or_namespace.is_a?(Symbol)
- namespace << "#{record_or_namespace}_"
- end
- end
- end
+ return "" unless record_or_hash_or_array.is_a?(Array)
+
+ namespace_keys = []
+ while (key = record_or_hash_or_array.first) && key.is_a?(String) || key.is_a?(Symbol)
+ namespace_keys << record_or_hash_or_array.shift
end
+
+ namespace_keys.map {|k| "#{k}_"}.join
end
end
end
diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb
index 9b4aa9b7cf..01bc1ebb26 100644
--- a/actionpack/lib/action_controller/rack_process.rb
+++ b/actionpack/lib/action_controller/rack_process.rb
@@ -24,6 +24,19 @@ module ActionController #:nodoc:
super()
end
+ %w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO
+ PATH_TRANSLATED QUERY_STRING REMOTE_HOST
+ REMOTE_IDENT REMOTE_USER SCRIPT_NAME
+ SERVER_NAME SERVER_PROTOCOL
+
+ HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
+ HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST
+ HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
+ define_method(env.sub(/^HTTP_/n, '').downcase) do
+ @env[env]
+ end
+ end
+
# The request body is an IO input stream. If the RAW_POST_DATA environment
# variable is already set, wrap it in a StringIO.
def body
@@ -35,7 +48,7 @@ module ActionController #:nodoc:
end
def key?(key)
- @env.key? key
+ @env.key?(key)
end
def query_parameters
@@ -85,6 +98,18 @@ module ActionController #:nodoc:
@env['REMOTE_ADDR']
end
+ def request_method
+ @env['REQUEST_METHOD'].downcase.to_sym
+ end
+
+ def server_port
+ @env['SERVER_PORT'].to_i
+ end
+
+ def server_software
+ @env['SERVER_SOFTWARE'].split("/").first
+ end
+
def session
unless defined?(@session)
if @session_options == false
@@ -178,9 +203,9 @@ end_msg
normalize_headers(@headers)
if [204, 304].include?(@status.to_i)
@headers.delete "Content-Type"
- [status.to_i, @headers.to_hash, []]
+ [status, @headers.to_hash, []]
else
- [status.to_i, @headers.to_hash, self]
+ [status, @headers.to_hash, self]
end
end
alias to_a out
@@ -225,8 +250,8 @@ end_msg
headers['Content-Language'] = options.delete('language') if options['language']
headers['Expires'] = options.delete('expires') if options['expires']
- @status = options.delete('Status') if options['Status']
- @status ||= 200
+ @status = options['Status'] || "200 OK"
+
# Convert 'cookie' header to 'Set-Cookie' headers.
# Because Set-Cookie header can appear more the once in the response body,
# we store it in a line break seperated string that will be translated to
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb
index 9b02f2c8a1..2d9f6c3e6f 100755
--- a/actionpack/lib/action_controller/request.rb
+++ b/actionpack/lib/action_controller/request.rb
@@ -82,21 +82,34 @@ module ActionController
# 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,6 +129,26 @@ module ActionController
@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]
+
+ if parameter_format
+ parameter_format.to_sym
+ elsif xhr?
+ :js
+ else
+ :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.)
diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb
index 40ef4ea044..163ed87fbb 100644
--- a/actionpack/lib/action_controller/rescue.rb
+++ b/actionpack/lib/action_controller/rescue.rb
@@ -178,7 +178,7 @@ module ActionController #:nodoc:
@template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
@template.send!(:assign_variables_from_controller)
- @template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false))
+ @template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception), :use_full_path => false))
response.content_type = Mime::HTML
render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb
index 9fb1f9fa39..af2fcaf3ad 100644
--- a/actionpack/lib/action_controller/resources.rb
+++ b/actionpack/lib/action_controller/resources.rb
@@ -72,7 +72,7 @@ module ActionController
end
def conditions
- @conditions = @options[:conditions] || {}
+ @conditions ||= @options[:conditions] || {}
end
def path
@@ -80,9 +80,9 @@ module ActionController
end
def new_path
- new_action = self.options[:path_names][:new] if self.options[:path_names]
+ new_action = self.options[:path_names][:new] if self.options[:path_names]
new_action ||= Base.resources_path_names[:new]
- @new_path ||= "#{path}/#{new_action}"
+ @new_path ||= "#{path}/#{new_action}"
end
def member_path
diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb
index 8846dcc504..dfbaa53b7c 100644
--- a/actionpack/lib/action_controller/routing.rb
+++ b/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>,
diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb
index 4740113ed0..b8323847fd 100644
--- a/actionpack/lib/action_controller/routing/builder.rb
+++ b/actionpack/lib/action_controller/routing/builder.rb
@@ -67,10 +67,9 @@ module ActionController
options = options.dup
if options[:namespace]
- options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}"
+ options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
options.delete(:path_prefix)
options.delete(:name_prefix)
- options.delete(:namespace)
end
requirements = (options.delete(:requirements) || {}).dup
diff --git a/actionpack/lib/action_controller/templates/rescues/diagnostics.erb b/actionpack/lib/action_controller/templates/rescues/diagnostics.erb
index 032f945ed2..385c6c1b09 100644
--- a/actionpack/lib/action_controller/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_controller/templates/rescues/diagnostics.erb
@@ -6,6 +6,6 @@
</h1>
<pre><%=h @exception.clean_message %></pre>
-<%= render_file(@rescues_path + "/_trace.erb", false) %>
+<%= render(:file => @rescues_path + "/_trace.erb", :use_full_path => false) %>
-<%= render_file(@rescues_path + "/_request_and_response.erb", false) %>
+<%= render(:file => @rescues_path + "/_request_and_response.erb", :use_full_path => false) %>
diff --git a/actionpack/lib/action_controller/templates/rescues/template_error.erb b/actionpack/lib/action_controller/templates/rescues/template_error.erb
index eda64db3e9..4aecc68d18 100644
--- a/actionpack/lib/action_controller/templates/rescues/template_error.erb
+++ b/actionpack/lib/action_controller/templates/rescues/template_error.erb
@@ -15,7 +15,7 @@
<% @real_exception = @exception
@exception = @exception.original_exception || @exception %>
-<%= render_file(@rescues_path + "/_trace.erb", false) %>
+<%= render(:file => @rescues_path + "/_trace.erb", :use_full_path => false) %>
<% @exception = @real_exception %>
-<%= render_file(@rescues_path + "/_request_and_response.erb", false) %>
+<%= render(:file => @rescues_path + "/_request_and_response.erb", :use_full_path => false) %>
diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb
index 0cf143210d..a6e0c98936 100644
--- a/actionpack/lib/action_controller/test_process.rb
+++ b/actionpack/lib/action_controller/test_process.rb
@@ -205,21 +205,10 @@ module ActionController #:nodoc:
p.match(redirect_url) != nil
end
- # 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
- end
- end
-
- # Was this template rendered by a file?
- def rendered_with_file?
- !rendered_file.nil?
+ # Returns the template of the file which was used to
+ # render this response (or nil)
+ def rendered_template
+ template._first_render
end
# A shortcut to the flash. Returns an empyt hash if no session flash exists.
@@ -404,15 +393,6 @@ module ActionController #:nodoc:
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
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
index 607fd186b9..b8d73c350d 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
@@ -17,7 +17,7 @@ module HTML #:nodoc:
@root = Node.new(nil)
node_stack = [ @root ]
while token = tokenizer.next
- node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token)
+ node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token, strict)
node_stack.last.children << node unless node.tag? && node.closing == :close
if node.tag?
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 067a871f79..dd555b3792 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -21,12 +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/template'
-require 'action_view/partial_template'
require 'action_view/inline_template'
+require 'action_view/paths'
require 'action_view/base'
require 'action_view/partials'
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 4f3cc46a14..04e8d3a358 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -3,6 +3,12 @@ module ActionView #: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
@@ -153,20 +159,19 @@ module ActionView #:nodoc:
class Base
include ERB::Util
- attr_accessor :base_path, :assigns, :template_extension, :first_render
+ attr_accessor :base_path, :assigns, :template_extension
attr_accessor :controller
+ attr_accessor :_first_render, :_last_render
attr_writer :template_format
- attr_accessor :current_render_extension
attr_accessor :output_buffer
- # Specify trim mode for the ERB compiler. Defaults to '-'.
- # See ERb documentation for suitable values.
- @@erb_trim_mode = '-'
- cattr_accessor :erb_trim_mode
+ 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 @@ module ActionView #:nodoc:
@@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,19 +199,10 @@ module ActionView #: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 = {}
- class ObjectWrapper < Struct.new(:value) #:nodoc:
- end
-
def self.helper_modules #:nodoc:
helpers = []
Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file|
@@ -216,6 +216,10 @@ module ActionView #: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
@@ -226,39 +230,20 @@ module ActionView #:nodoc:
attr_reader :view_paths
def view_paths=(paths)
- @view_paths = ViewLoadPaths.new(Array(paths))
- end
-
- # Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true,
- # it's relative to the view_paths array, otherwise it's absolute. The hash in <tt>local_assigns</tt>
- # is made available as local variables.
- def render_file(template_path, use_full_path = true, local_assigns = {}) #:nodoc:
- 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.
-
- render "user_mailer/signup"
- render :file => "user_mailer/signup"
-
-If you are rendering a subtemplate, you must now use controller-like partial syntax:
-
- render :partial => 'signup' # no mailer_name necessary
- END_ERROR
- end
-
- Template.new(self, template_path, use_full_path, local_assigns).render_template
+ @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, true, local_assigns)
+ render_file(options, nil, local_assigns)
elsif options == :update
update_page(&block)
elsif options.is_a?(Hash)
- use_full_path = options[:use_full_path]
- options = options.reverse_merge(:locals => {}, :use_full_path => true)
+ options = options.reverse_merge(:locals => {})
if partial_layout = options.delete(:layout)
if block_given?
@@ -271,59 +256,112 @@ If you are rendering a subtemplate, you must now use controller-like partial syn
end
end
elsif options[:file]
- render_file(options[:file], use_full_path || false, options[:locals])
+ render_file(options[:file], nil, options[:locals])
elsif options[:partial] && options[:collection]
- render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals])
+ render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals], options[:as])
elsif options[:partial]
- render_partial(options[:partial], ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals])
+ render_partial(options[:partial], options[:object], options[:locals])
elsif options[:inline]
- template = InlineTemplate.new(self, options[:inline], options[:locals], options[:type])
- render_template(template)
+ render_inline(options[:inline], options[:locals], options[:type])
end
end
end
- def render_template(template) #:nodoc:
- template.render_template
- end
-
# Returns true is the file may be rendered implicitly.
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
if controller && controller.respond_to?(:request)
- parameter_format = controller.request.parameters[:format]
- accept_format = controller.request.accepts.first
-
- case
- when parameter_format.blank? && accept_format != :js
- @template_format = :html
- when parameter_format.blank? && accept_format == :js
- @template_format = :js
- else
- @template_format = parameter_format.to_sym
- end
+ @template_format = controller.request.template_format
else
@template_format = :html
end
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('users/show')
+ # # => 'users/show.html.erb'
+ #
+ # pick_template('users/legacy')
+ # # => 'users/legacy.rhtml'
+ #
+ 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.format_and_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
private
+ # 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.
+
+ render "user_mailer/signup"
+ render :file => "user_mailer/signup"
+
+ If you are rendering a subtemplate, you must now use controller-like partial syntax:
+
+ render :partial => 'signup' # no mailer_name necessary
+ END_ERROR
+ end
+
+ template = pick_template(template_path)
+ template.render_template(self, local_assigns)
+ end
+
+ def render_inline(text, local_assigns = {}, type = nil)
+ InlineTemplate.new(text, type).render(self, local_assigns)
+ end
+
def wrap_content_for_layout(content)
original_content_for_layout, @content_for_layout = @content_for_layout, content
yield
@@ -344,42 +382,10 @@ If you are rendering a subtemplate, you must now use controller-like partial syn
@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_extension(template_name) unless template_name.extension
- 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_extension('users/legacy')
- # # => "rhtml"
- #
- def pick_template_extension(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
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index e5a95a961c..bf13945844 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -209,6 +209,10 @@ module ActionView
# 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 @@ module ActionView
#
# 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))
+ 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 @@ module ActionView
# <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 @@ module ActionView
#
# 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))
+ 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 @@ module ActionView
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 @@ module ActionView
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)
@@ -601,10 +621,16 @@ module ActionView
end
def write_asset_file_contents(joined_asset_path, asset_paths)
- unless file_exist?(joined_asset_path)
- FileUtils.mkdir_p(File.dirname(joined_asset_path))
- File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
- end
+ 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
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index 930c397785..64d1ad2715 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -32,8 +32,7 @@ module ActionView
# <i>Topics listed alphabetically</i>
# <% end %>
def cache(name = {}, options = nil, &block)
- handler = Template.handler_class_for_extension(current_render_extension.to_sym)
- handler.new(@controller).cache_fragment(block, name, options)
+ @controller.fragment_for(output_buffer, name, options, &block)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index 9cd9d3d06a..720e2da8cc 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -31,10 +31,12 @@ module ActionView
# </body></html>
#
def capture(*args, &block)
- if output_buffer
+ # Return captured buffer in erb.
+ if block_called_from_erb?(block)
with_output_buffer { block.call(*args) }
else
- block.call(*args)
+ # Return block result otherwise, but protect buffer also.
+ with_output_buffer { return block.call(*args) }
end
end
@@ -117,6 +119,7 @@ module ActionView
ivar = "@content_for_#{name}"
content = capture(&block) if block_given?
instance_variable_set(ivar, "#{instance_variable_get(ivar)}#{content}")
+ nil
end
private
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 61fff42d5a..1f213127d3 100755
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -158,13 +158,16 @@ module ActionView
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
# choices are valid.
def date_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options, html_options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options)
end
# 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
@@ -193,7 +196,7 @@ module ActionView
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
# choices are valid.
def time_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_time_select_tag(options, html_options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_time_select_tag(options, html_options)
end
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
@@ -219,7 +222,7 @@ module ActionView
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
def datetime_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options, html_options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
end
# Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
@@ -558,23 +561,32 @@ module ActionView
# select_year(2006, :start_year => 2000, :end_year => 2010)
#
def select_year(date, options = {}, html_options = {})
- val = date ? (date.kind_of?(Fixnum) ? date : date.year) : ''
+ if !date || date == 0
+ value = ''
+ middle_year = Date.today.year
+ elsif date.kind_of?(Fixnum)
+ value = middle_year = date
+ else
+ value = middle_year = date.year
+ end
+
if options[:use_hidden]
- hidden_html(options[:field_name] || 'year', val, options)
+ hidden_html(options[:field_name] || 'year', value, options)
else
- year_options = []
- y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
+ year_options = ''
+ start_year = options[:start_year] || middle_year - 5
+ end_year = options[:end_year] || middle_year + 5
+ step_val = start_year < end_year ? 1 : -1
- start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
- step_val = start_year < end_year ? 1 : -1
start_year.step(end_year, step_val) do |year|
- year_options << ((val == year) ?
- content_tag(:option, year, :value => year, :selected => "selected") :
- content_tag(:option, year, :value => year)
- )
+ if value == year
+ year_options << content_tag(:option, year, :value => year, :selected => "selected")
+ else
+ year_options << content_tag(:option, year, :value => year)
+ end
year_options << "\n"
end
- select_html(options[:field_name] || 'year', year_options.join, options, html_options)
+ select_html(options[:field_name] || 'year', year_options, options, html_options)
end
end
@@ -659,7 +671,7 @@ module ActionView
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,
@@ -708,15 +720,15 @@ module ActionView
class FormBuilder
def date_select(method, options = {}, html_options = {})
- @template.date_select(@object_name, method, options.merge(:object => @object))
+ @template.date_select(@object_name, method, options.merge(:object => @object), html_options)
end
def time_select(method, options = {}, html_options = {})
- @template.time_select(@object_name, method, options.merge(:object => @object))
+ @template.time_select(@object_name, method, options.merge(:object => @object), html_options)
end
def datetime_select(method, options = {}, html_options = {})
- @template.datetime_select(@object_name, method, options.merge(:object => @object))
+ @template.datetime_select(@object_name, method, options.merge(:object => @object), html_options)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 63a932320e..bafc635ad2 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -333,7 +333,7 @@ module ActionView
# # => <label for="post_title" class="title_label">A short title</label>
#
def label(object_name, method, text = nil, options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_label_tag(text, options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options)
end
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -355,7 +355,7 @@ module ActionView
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
#
def text_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("text", options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
end
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -377,7 +377,7 @@ module ActionView
# # => <input type="text" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
#
def password_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("password", options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", options)
end
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -395,7 +395,7 @@ module ActionView
# hidden_field(:user, :token)
# # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
def hidden_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("hidden", options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
end
# Returns an file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -414,7 +414,7 @@ module ActionView
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
#
def file_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("file", options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options)
end
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
@@ -442,7 +442,7 @@ module ActionView
# # #{@entry.body}
# # </textarea>
def text_area(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_text_area_tag(options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
end
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -468,7 +468,7 @@ module ActionView
# # <input name="eula[accepted]" type="hidden" value="no" />
#
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
end
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
@@ -488,7 +488,7 @@ module ActionView
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
def radio_button(object_name, method, tag_value, options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_radio_button_tag(tag_value, options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
end
end
@@ -501,9 +501,9 @@ module ActionView
DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
- def initialize(object_name, method_name, template_object, local_binding = nil, object = nil)
+ def initialize(object_name, method_name, template_object, object = nil)
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
- @template_object, @local_binding = template_object, local_binding
+ @template_object= template_object
@object = object
if @object_name.sub!(/\[\]$/,"")
if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
@@ -601,7 +601,11 @@ module ActionView
end
def object
- @object || (@template_object.instance_variable_get("@#{@object_name}") rescue nil)
+ @object || @template_object.instance_variable_get("@#{@object_name}")
+ rescue NameError
+ # As @object_name may contain the nested syntax (item[subobject]) we
+ # need to fallback to nil.
+ nil
end
def value(object)
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index b3f8e63c1b..87d49397c6 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -96,7 +96,7 @@ module ActionView
# By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
# or <tt>:selected => nil</tt> to leave all options unselected.
def select(object, method, choices, options = {}, html_options = {})
- InstanceTag.new(object, method, self, nil, options.delete(:object)).to_select_tag(choices, options, html_options)
+ InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
end
# Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
@@ -130,12 +130,12 @@ module ActionView
# <option value="3">M. Clark</option>
# </select>
def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
- InstanceTag.new(object, method, self, nil, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
+ InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
end
# Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
- InstanceTag.new(object, method, self, nil, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
+ InstanceTag.new(object, method, self, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
end
# Return select and option tags for the given object and method, using
@@ -150,7 +150,8 @@ module ActionView
# You can also supply an array of TimeZone objects
# as +priority_zones+, so that they will be listed above the rest of the
# (long) list. (You can use TimeZone.us_zones as a convenience for
- # obtaining a list of the US time zones.)
+ # obtaining a list of the US time zones, or a Regexp to select the zones
+ # of your choice)
#
# Finally, this method supports a <tt>:default</tt> option, which selects
# a default TimeZone if the object's time zone is +nil+.
@@ -164,9 +165,11 @@ module ActionView
#
# time_zone_select( "user", 'time_zone', [ TimeZone['Alaska'], TimeZone['Hawaii'] ])
#
+ # time_zone_select( "user", 'time_zone', /Australia/)
+ #
# time_zone_select( "user", "time_zone", TZInfo::Timezone.all.sort, :model => TZInfo::Timezone)
def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
- InstanceTag.new(object, method, self, nil, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
+ InstanceTag.new(object, method, self, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
end
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
@@ -292,7 +295,8 @@ module ActionView
# selected option tag. You can also supply an array of TimeZone objects
# as +priority_zones+, so that they will be listed above the rest of the
# (long) list. (You can use TimeZone.us_zones as a convenience for
- # obtaining a list of the US time zones.)
+ # obtaining a list of the US time zones, or a Regexp to select the zones
+ # of your choice)
#
# The +selected+ parameter must be either +nil+, or a string that names
# a TimeZone.
@@ -311,6 +315,9 @@ module ActionView
convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
if priority_zones
+ if priority_zones.is_a?(Regexp)
+ priority_zones = model.all.find_all {|z| z =~ priority_zones}
+ end
zone_options += options_for_select(convert_zones[priority_zones], selected)
zone_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
@@ -438,19 +445,19 @@ module ActionView
class FormBuilder
def select(method, choices, options = {}, html_options = {})
- @template.select(@object_name, method, choices, options.merge(:object => @object), html_options)
+ @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
end
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
- @template.collection_select(@object_name, method, collection, value_method, text_method, options.merge(:object => @object), html_options)
+ @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
end
def country_select(method, priority_countries = nil, options = {}, html_options = {})
- @template.country_select(@object_name, method, priority_countries, options.merge(:object => @object), html_options)
+ @template.country_select(@object_name, method, priority_countries, objectify_options(options), @default_options.merge(html_options))
end
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
- @template.time_zone_select(@object_name, method, priority_zones, options.merge(:object => @object), html_options)
+ @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 3a97f1390f..bdfb2eebd7 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -6,7 +6,7 @@ module ActionView
# Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like
# FormHelper does. Instead, you provide the names and values manually.
#
- # NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
+ # NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
# <tt>:disabled => true</tt> will give <tt>disabled="disabled"</tt>.
module FormTagHelper
# Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like
@@ -20,15 +20,15 @@ module ActionView
# * A list of parameters to feed to the URL the form will be posted to.
#
# ==== Examples
- # form_tag('/posts')
+ # form_tag('/posts')
# # => <form action="/posts" method="post">
#
- # form_tag('/posts/1', :method => :put)
+ # form_tag('/posts/1', :method => :put)
# # => <form action="/posts/1" method="put">
#
- # form_tag('/upload', :multipart => true)
+ # form_tag('/upload', :multipart => true)
# # => <form action="/upload" method="post" enctype="multipart/form-data">
- #
+ #
# <% form_tag '/posts' do -%>
# <div><%= submit_tag 'Save' %></div>
# <% end -%>
@@ -88,7 +88,7 @@ module ActionView
# * <tt>:size</tt> - The number of visible characters that will fit in the input.
# * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
# * Any other key creates standard HTML attributes for the tag.
- #
+ #
# ==== Examples
# text_field_tag 'name'
# # => <input id="name" name="name" type="text" />
@@ -146,13 +146,13 @@ module ActionView
# # => <input id="token" name="token" type="hidden" value="VUBJKB23UIVI1UU1VOBVI@" />
#
# hidden_field_tag 'collected_input', '', :onchange => "alert('Input collected!')"
- # # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
+ # # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
# # type="hidden" value="" />
def hidden_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
end
- # Creates a file upload field. If you are using file uploads then you will also need
+ # Creates a file upload field. If you are using file uploads then you will also need
# to set the multipart option for the form tag:
#
# <%= form_tag { :action => "post" }, { :multipart => true } %>
@@ -160,7 +160,7 @@ module ActionView
# <%= submit_tag %>
# <%= end_form_tag %>
#
- # The specified URL will then be passed a File object containing the selected file, or if the field
+ # The specified URL will then be passed a File object containing the selected file, or if the field
# was left blank, a StringIO object.
#
# ==== Options
@@ -181,7 +181,7 @@ module ActionView
# # => <input id="resume" name="resume" type="file" value="~/resume.doc" />
#
# file_field_tag 'user_pic', :accept => 'image/png,image/gif,image/jpeg'
- # # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" />
+ # # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" />
#
# file_field_tag 'file', :accept => 'text/html', :class => 'upload', :value => 'index.html'
# # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" />
@@ -286,7 +286,7 @@ module ActionView
tag :input, html_options
end
- # Creates a radio button; use groups of radio buttons named the same to allow users to
+ # Creates a radio button; use groups of radio buttons named the same to allow users to
# select from a group of options.
#
# ==== Options
@@ -313,14 +313,14 @@ module ActionView
tag :input, html_options
end
- # Creates a submit button with the text <tt>value</tt> as the caption.
+ # Creates a submit button with the text <tt>value</tt> as the caption.
#
# ==== Options
# * <tt>:confirm => 'question?'</tt> - This will add a JavaScript confirm
# prompt with the question specified. If the user accepts, the form is
# processed normally, otherwise no action is taken.
# * <tt>:disabled</tt> - If true, the user will not be able to use this input.
- # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a disabled version
+ # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a disabled version
# of the submit button when the form is submitted.
# * Any other key creates standard HTML options for the tag.
#
@@ -335,7 +335,7 @@ module ActionView
# # => <input disabled="disabled" name="commit" type="submit" value="Save edits" />
#
# submit_tag "Complete sale", :disable_with => "Please wait..."
- # # => <input name="commit" onclick="this.disabled=true;this.value='Please wait...';this.form.submit();"
+ # # => <input name="commit" onclick="this.disabled=true;this.value='Please wait...';this.form.submit();"
# # type="submit" value="Complete sale" />
#
# submit_tag nil, :class => "form_submit"
@@ -346,27 +346,29 @@ module ActionView
# # name="commit" type="submit" value="Edit" />
def submit_tag(value = "Save changes", options = {})
options.stringify_keys!
-
+
if disable_with = options.delete("disable_with")
+ disable_with = "this.value='#{disable_with}'"
+ disable_with << ";#{options.delete('onclick')}" if options['onclick']
+
options["onclick"] = [
"this.setAttribute('originalValue', this.value)",
"this.disabled=true",
- "this.value='#{disable_with}'",
- "#{options["onclick"]}",
+ disable_with,
"result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit())",
"if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false }",
"return result;",
].join(";")
end
-
+
if confirm = options.delete("confirm")
options["onclick"] ||= ''
options["onclick"] += "return #{confirm_javascript_function(confirm)};"
end
-
+
tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
end
-
+
# Displays an image which when clicked will submit the form.
#
# <tt>source</tt> is passed to AssetTagHelper#image_path
@@ -412,7 +414,7 @@ module ActionView
concat(content)
concat("</fieldset>")
end
-
+
private
def html_options_for_form(url_for_options, options, *parameters_for_url)
returning options.stringify_keys do |html_options|
@@ -420,7 +422,7 @@ module ActionView
html_options["action"] = url_for(url_for_options, *parameters_for_url)
end
end
-
+
def extra_tags_for_form(html_options)
case method = html_options.delete("method").to_s
when /^get$/i # must be case-insentive, but can't use downcase as might be nil
@@ -434,12 +436,12 @@ module ActionView
content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag, :style => 'margin:0;padding:0')
end
end
-
+
def form_tag_html(html_options)
extra_tags = extra_tags_for_form(html_options)
tag(:form, html_options, true) + extra_tags
end
-
+
def form_tag_in_block(html_options, &block)
content = capture(&block)
concat(form_tag_html(html_options))
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index 7404a251e4..22bd5cb440 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -4,10 +4,10 @@ require 'action_view/helpers/prototype_helper'
module ActionView
module Helpers
# Provides functionality for working with JavaScript in your views.
- #
+ #
# == Ajax, controls and visual effects
- #
- # * For information on using Ajax, see
+ #
+ # * For information on using Ajax, see
# ActionView::Helpers::PrototypeHelper.
# * For information on using controls and visual effects, see
# ActionView::Helpers::ScriptaculousHelper.
@@ -20,22 +20,22 @@ module ActionView
# and ActionView::Helpers::ScriptaculousHelper), you must do one of the
# following:
#
- # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD
- # section of your page (recommended): This function will return
+ # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD
+ # section of your page (recommended): This function will return
# references to the JavaScript files created by the +rails+ command in
# your <tt>public/javascripts</tt> directory. Using it is recommended as
- # the browser can then cache the libraries instead of fetching all the
+ # the browser can then cache the libraries instead of fetching all the
# functions anew on every request.
- # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but
+ # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but
# will only include the Prototype core library, which means you are able
- # to use all basic AJAX functionality. For the Scriptaculous-based
- # JavaScript helpers, like visual effects, autocompletion, drag and drop
+ # to use all basic AJAX functionality. For the Scriptaculous-based
+ # JavaScript helpers, like visual effects, autocompletion, drag and drop
# and so on, you should use the method described above.
# * Use <tt><%= define_javascript_functions %></tt>: this will copy all the
# JavaScript support functions within a single script block. Not
# recommended.
#
- # For documentation on +javascript_include_tag+ see
+ # For documentation on +javascript_include_tag+ see
# ActionView::Helpers::AssetTagHelper.
module JavaScriptHelper
unless const_defined? :JAVASCRIPT_PATH
@@ -43,13 +43,13 @@ module ActionView
end
include PrototypeHelper
-
- # Returns a link that will trigger a JavaScript +function+ using the
+
+ # Returns a link that will trigger a JavaScript +function+ using the
# onclick handler and return false after the fact.
#
# The +function+ argument can be omitted in favor of an +update_page+
# block, which evaluates to a string when the template is rendered
- # (instead of making an Ajax request first).
+ # (instead of making an Ajax request first).
#
# Examples:
# link_to_function "Greeting", "alert('Hello world!')"
@@ -70,36 +70,31 @@ module ActionView
# <a href="#" id="more_link" onclick="try {
# $(&quot;details&quot;).visualEffect(&quot;toggle_blind&quot;);
# $(&quot;more_link&quot;).update(&quot;Show me less&quot;);
- # }
- # catch (e) {
- # alert('RJS error:\n\n' + e.toString());
+ # }
+ # catch (e) {
+ # alert('RJS error:\n\n' + e.toString());
# alert('$(\&quot;details\&quot;).visualEffect(\&quot;toggle_blind\&quot;);
# \n$(\&quot;more_link\&quot;).update(\&quot;Show me less\&quot;);');
- # throw e
+ # throw e
# };
# return false;">Show me more</a>
#
def link_to_function(name, *args, &block)
- html_options = args.extract_options!
- function = args[0] || ''
-
- html_options.symbolize_keys!
- function = update_page(&block) if block_given?
- content_tag(
- "a", name,
- html_options.merge({
- :href => html_options[:href] || "#",
- :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function}; return false;"
- })
- )
+ html_options = args.extract_options!.symbolize_keys
+
+ function = block_given? ? update_page(&block) : args[0] || ''
+ onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
+ href = html_options[:href] || '#'
+
+ content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
end
-
- # Returns a button that'll trigger a JavaScript +function+ using the
+
+ # Returns a button that'll trigger a JavaScript +function+ using the
# onclick handler.
#
# The +function+ argument can be omitted in favor of an +update_page+
# block, which evaluates to a string when the template is rendered
- # (instead of making an Ajax request first).
+ # (instead of making an Ajax request first).
#
# Examples:
# button_to_function "Greeting", "alert('Hello world!')"
@@ -111,45 +106,30 @@ module ActionView
# page[:details].visual_effect :toggle_slide
# end
def button_to_function(name, *args, &block)
- html_options = args.extract_options!
- function = args[0] || ''
-
- html_options.symbolize_keys!
- function = update_page(&block) if block_given?
- tag(:input, html_options.merge({
- :type => "button", :value => name,
- :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
- }))
- end
+ html_options = args.extract_options!.symbolize_keys
- # Includes the Action Pack JavaScript libraries inside a single <script>
- # tag. The function first includes prototype.js and then its core extensions,
- # (determined by filenames starting with "prototype").
- # Afterwards, any additional scripts will be included in undefined order.
- #
- # Note: The recommended approach is to copy the contents of
- # lib/action_view/helpers/javascripts/ into your application's
- # public/javascripts/ directory, and use +javascript_include_tag+ to
- # create remote <script> links.
- def define_javascript_functions
- javascript = "<script type=\"#{Mime::JS}\">"
-
- # load prototype.js and its extensions first
- prototype_libs = Dir.glob(File.join(JAVASCRIPT_PATH, 'prototype*')).sort.reverse
- prototype_libs.each do |filename|
- javascript << "\n" << IO.read(filename)
- end
-
- # load other libraries
- (Dir.glob(File.join(JAVASCRIPT_PATH, '*')) - prototype_libs).each do |filename|
- javascript << "\n" << IO.read(filename)
- end
- javascript << '</script>'
+ function = block_given? ? update_page(&block) : args[0] || ''
+ onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
+
+ tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
end
+ JS_ESCAPE_MAP = {
+ '\\' => '\\\\',
+ '</' => '<\/',
+ "\r\n" => '\n',
+ "\n" => '\n',
+ "\r" => '\n',
+ '"' => '\\"',
+ "'" => "\\'" }
+
# Escape carrier returns and single and double quotes for JavaScript segments.
def escape_javascript(javascript)
- (javascript || '').gsub('\\','\0\0').gsub('</','<\/').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }
+ if javascript
+ javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] }
+ else
+ ''
+ end
end
# Returns a JavaScript tag with the +content+ inside. Example:
@@ -163,7 +143,7 @@ module ActionView
# </script>
#
# +html_options+ may be a hash of attributes for the <script> tag. Example:
- # javascript_tag "alert('All is good')", :defer => 'defer'
+ # javascript_tag "alert('All is good')", :defer => 'defer'
# # => <script defer="defer" type="text/javascript">alert('All is good')</script>
#
# Instead of passing the content as an argument, you can also use a block
@@ -180,30 +160,35 @@ module ActionView
content_or_options_with_block
end
- tag = content_tag("script", javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
+ tag = content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
- block_given? ? concat(tag) : tag
+ if block_called_from_erb?(block)
+ concat(tag)
+ else
+ tag
+ end
end
def javascript_cdata_section(content) #:nodoc:
"\n//#{cdata_section("\n#{content}\n//")}\n"
end
-
+
protected
def options_for_javascript(options)
- '{' + options.map {|k, v| "#{k}:#{v}"}.sort.join(', ') + '}'
+ if options.empty?
+ '{}'
+ else
+ "{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
+ end
end
-
+
def array_or_string_for_javascript(option)
- js_option = if option.kind_of?(Array)
+ if option.kind_of?(Array)
"['#{option.join('\',\'')}']"
elsif !option.nil?
"'#{option}'"
end
- js_option
end
end
-
- JavascriptHelper = JavaScriptHelper unless const_defined? :JavascriptHelper
end
end
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index a7c3b9ddc3..edb43844a4 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -3,25 +3,25 @@ require 'set'
module ActionView
module Helpers
# Prototype[http://www.prototypejs.org/] is a JavaScript library that provides
- # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
+ # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
# Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php]
- # functionality, and more traditional object-oriented facilities for JavaScript.
+ # functionality, and more traditional object-oriented facilities for JavaScript.
# This module provides a set of helpers to make it more convenient to call
- # functions from Prototype using Rails, including functionality to call remote
- # Rails methods (that is, making a background request to a Rails action) using Ajax.
- # This means that you can call actions in your controllers without
- # reloading the page, but still update certain parts of it using
+ # functions from Prototype using Rails, including functionality to call remote
+ # Rails methods (that is, making a background request to a Rails action) using Ajax.
+ # This means that you can call actions in your controllers without
+ # reloading the page, but still update certain parts of it using
# injections into the DOM. A common use case is having a form that adds
# a new element to a list without reloading the page or updating a shopping
# cart total when a new item is added.
#
# == Usage
- # To be able to use these helpers, you must first include the Prototype
- # JavaScript framework in your pages.
+ # To be able to use these helpers, you must first include the Prototype
+ # JavaScript framework in your pages.
#
# javascript_include_tag 'prototype'
#
- # (See the documentation for
+ # (See the documentation for
# ActionView::Helpers::JavaScriptHelper for more information on including
# this and other JavaScript files in your Rails templates.)
#
@@ -29,7 +29,7 @@ module ActionView
#
# link_to_remote "Add to cart",
# :url => { :action => "add", :id => product.id },
- # :update => { :success => "cart", :failure => "error" }
+ # :update => { :success => "cart", :failure => "error" }
#
# ...through a form...
#
@@ -50,8 +50,8 @@ module ActionView
# :update => :hits,
# :with => 'query'
# %>
- #
- # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
+ #
+ # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
# are listed here); check out the documentation for each method to find out more about its usage and options.
#
# === Common Options
@@ -63,7 +63,7 @@ module ActionView
# When building your action handlers (that is, the Rails actions that receive your background requests), it's
# important to remember a few things. First, whatever your action would normall return to the browser, it will
# return to the Ajax call. As such, you typically don't want to render with a layout. This call will cause
- # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up.
+ # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up.
# You can turn the layout off on particular actions by doing the following:
#
# class SiteController < ActionController::Base
@@ -74,8 +74,8 @@ module ActionView
#
# render :layout => false
#
- # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the
- # method that Ajax uses to make background requests) method.
+ # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the
+ # method that Ajax uses to make background requests) method.
# def name
# # Is this an XmlHttpRequest request?
# if (request.xhr?)
@@ -93,7 +93,7 @@ module ActionView
#
# Dropping this in your ApplicationController turns the layout off for every request that is an "xhr" request.
#
- # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
+ # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
# render text output, like this:
#
# render :text => 'Return this from my method!'
@@ -103,7 +103,7 @@ module ActionView
#
# == Updating multiple elements
# See JavaScriptGenerator for information on updating multiple elements
- # on the page in an Ajax response.
+ # on the page in an Ajax response.
module PrototypeHelper
unless const_defined? :CALLBACKS
CALLBACKS = Set.new([ :uninitialized, :loading, :loaded,
@@ -114,64 +114,64 @@ module ActionView
:form, :with, :update, :script ]).merge(CALLBACKS)
end
- # Returns a link to a remote action defined by <tt>options[:url]</tt>
- # (using the url_for format) that's called in the background using
+ # Returns a link to a remote action defined by <tt>options[:url]</tt>
+ # (using the url_for format) that's called in the background using
# XMLHttpRequest. The result of that request can then be inserted into a
- # DOM object whose id can be specified with <tt>options[:update]</tt>.
+ # DOM object whose id can be specified with <tt>options[:update]</tt>.
# Usually, the result would be a partial prepared by the controller with
- # render :partial.
+ # render :partial.
#
# Examples:
- # # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true});
+ # # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true});
# # return false;">Delete this post</a>
- # link_to_remote "Delete this post", :update => "posts",
+ # link_to_remote "Delete this post", :update => "posts",
# :url => { :action => "destroy", :id => post.id }
#
- # # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true});
+ # # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true});
# # return false;"><img alt="Refresh" src="/images/refresh.png?" /></a>
- # link_to_remote(image_tag("refresh"), :update => "emails",
+ # link_to_remote(image_tag("refresh"), :update => "emails",
# :url => { :action => "list_emails" })
- #
+ #
# You can override the generated HTML options by specifying a hash in
# <tt>options[:html]</tt>.
- #
+ #
# link_to_remote "Delete this post", :update => "posts",
- # :url => post_url(@post), :method => :delete,
- # :html => { :class => "destructive" }
+ # :url => post_url(@post), :method => :delete,
+ # :html => { :class => "destructive" }
#
# You can also specify a hash for <tt>options[:update]</tt> to allow for
- # easy redirection of output to an other DOM element if a server-side
+ # easy redirection of output to an other DOM element if a server-side
# error occurs:
#
# Example:
- # # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5',
+ # # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5',
# # {asynchronous:true, evalScripts:true}); return false;">Delete this post</a>
# link_to_remote "Delete this post",
# :url => { :action => "destroy", :id => post.id },
# :update => { :success => "posts", :failure => "error" }
#
- # Optionally, you can use the <tt>options[:position]</tt> parameter to
- # influence how the target DOM element is updated. It must be one of
+ # Optionally, you can use the <tt>options[:position]</tt> parameter to
+ # influence how the target DOM element is updated. It must be one of
# <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>.
#
# The method used is by default POST. You can also specify GET or you
# can simulate PUT or DELETE over POST. All specified with <tt>options[:method]</tt>
#
# Example:
- # # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'});
+ # # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'});
# # return false;">Destroy</a>
# link_to_remote "Destroy", :url => person_url(:id => person), :method => :delete
#
- # By default, these remote requests are processed asynchronous during
- # which various JavaScript callbacks can be triggered (for progress
- # indicators and the likes). All callbacks get access to the
- # <tt>request</tt> object, which holds the underlying XMLHttpRequest.
+ # By default, these remote requests are processed asynchronous during
+ # which various JavaScript callbacks can be triggered (for progress
+ # indicators and the likes). All callbacks get access to the
+ # <tt>request</tt> object, which holds the underlying XMLHttpRequest.
#
# To access the server response, use <tt>request.responseText</tt>, to
# find out the HTTP status, use <tt>request.status</tt>.
#
# Example:
- # # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true,
+ # # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true,
# # onComplete:function(request){undoRequestCompleted(request)}}); return false;">hello</a>
# word = 'hello'
# link_to_remote word,
@@ -180,43 +180,43 @@ module ActionView
#
# The callbacks that may be specified are (in order):
#
- # <tt>:loading</tt>:: Called when the remote document is being
+ # <tt>:loading</tt>:: Called when the remote document is being
# loaded with data by the browser.
# <tt>:loaded</tt>:: Called when the browser has finished loading
# the remote document.
- # <tt>:interactive</tt>:: Called when the user can interact with the
- # remote document, even though it has not
+ # <tt>:interactive</tt>:: Called when the user can interact with the
+ # remote document, even though it has not
# finished loading.
# <tt>:success</tt>:: Called when the XMLHttpRequest is completed,
# and the HTTP status code is in the 2XX range.
# <tt>:failure</tt>:: Called when the XMLHttpRequest is completed,
# and the HTTP status code is not in the 2XX
# range.
- # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
- # (fires after success/failure if they are
+ # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
+ # (fires after success/failure if they are
# present).
- #
- # You can further refine <tt>:success</tt> and <tt>:failure</tt> by
+ #
+ # You can further refine <tt>:success</tt> and <tt>:failure</tt> by
# adding additional callbacks for specific status codes.
#
# Example:
- # # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true,
- # # on404:function(request){alert('Not found...? Wrong URL...?')},
+ # # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true,
+ # # on404:function(request){alert('Not found...? Wrong URL...?')},
# # onFailure:function(request){alert('HTTP Error ' + request.status + '!')}}); return false;">hello</a>
# link_to_remote word,
# :url => { :action => "action" },
# 404 => "alert('Not found...? Wrong URL...?')",
# :failure => "alert('HTTP Error ' + request.status + '!')"
#
- # A status code callback overrides the success/failure handlers if
+ # A status code callback overrides the success/failure handlers if
# present.
#
# If you for some reason or another need synchronous processing (that'll
- # block the browser while the request is happening), you can specify
+ # block the browser while the request is happening), you can specify
# <tt>options[:type] = :synchronous</tt>.
#
# You can customize further browser side call logic by passing in
- # JavaScript code snippets via some optional parameters. In their order
+ # JavaScript code snippets via some optional parameters. In their order
# of use these are:
#
# <tt>:confirm</tt>:: Adds confirmation dialog.
@@ -228,7 +228,7 @@ module ActionView
# <tt>:after</tt>:: Called immediately after request was
# initiated and before <tt>:loading</tt>.
# <tt>:submit</tt>:: Specifies the DOM element ID that's used
- # as the parent of the form elements. By
+ # as the parent of the form elements. By
# default this is the current form, but
# it could just as well be the ID of a
# table row or any other DOM element.
@@ -238,10 +238,10 @@ module ActionView
# URL query string.
#
# Example:
- #
+ #
# :with => "'name=' + $('name').value"
#
- # You can generate a link that uses AJAX in the general case, while
+ # You can generate a link that uses AJAX in the general case, while
# degrading gracefully to plain link behavior in the absence of
# JavaScript by setting <tt>html_options[:href]</tt> to an alternate URL.
# Note the extra curly braces around the <tt>options</tt> hash separate
@@ -251,7 +251,7 @@ module ActionView
# link_to_remote "Delete this post",
# { :update => "posts", :url => { :action => "destroy", :id => post.id } },
# :href => url_for(:action => "destroy", :id => post.id)
- def link_to_remote(name, options = {}, html_options = nil)
+ def link_to_remote(name, options = {}, html_options = nil)
link_to_function(name, remote_function(options), html_options || options.delete(:html))
end
@@ -262,15 +262,15 @@ module ActionView
# and defining callbacks is the same as link_to_remote.
# Examples:
# # Call get_averages and put its results in 'avg' every 10 seconds
- # # Generates:
- # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages',
+ # # Generates:
+ # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages',
# # {asynchronous:true, evalScripts:true})}, 10)
# periodically_call_remote(:url => { :action => 'get_averages' }, :update => 'avg')
#
# # Call invoice every 10 seconds with the id of the customer
# # If it succeeds, update the invoice DIV; if it fails, update the error DIV
# # Generates:
- # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'},
+ # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'},
# # '/testing/invoice/16', {asynchronous:true, evalScripts:true})}, 10)
# periodically_call_remote(:url => { :action => 'invoice', :id => customer.id },
# :update => { :success => "invoice", :failure => "error" }
@@ -286,11 +286,11 @@ module ActionView
javascript_tag(code)
end
- # Returns a form tag that will submit using XMLHttpRequest in the
- # background instead of the regular reloading POST arrangement. Even
+ # Returns a form tag that will submit using XMLHttpRequest in the
+ # background instead of the regular reloading POST arrangement. Even
# though it's using JavaScript to serialize the form elements, the form
# submission will work just like a regular submission as viewed by the
- # receiving side (all elements available in <tt>params</tt>). The options for
+ # receiving side (all elements available in <tt>params</tt>). The options for
# specifying the target with <tt>:url</tt> and defining callbacks is the same as
# +link_to_remote+.
#
@@ -299,21 +299,21 @@ module ActionView
#
# Example:
# # Generates:
- # # <form action="/some/place" method="post" onsubmit="new Ajax.Request('',
+ # # <form action="/some/place" method="post" onsubmit="new Ajax.Request('',
# # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">
- # form_remote_tag :html => { :action =>
+ # form_remote_tag :html => { :action =>
# url_for(:controller => "some", :action => "place") }
#
# The Hash passed to the <tt>:html</tt> key is equivalent to the options (2nd)
# argument in the FormTagHelper.form_tag method.
#
- # By default the fall-through action is the same as the one specified in
+ # By default the fall-through action is the same as the one specified in
# the <tt>:url</tt> (and the default method is <tt>:post</tt>).
#
# form_remote_tag also takes a block, like form_tag:
# # Generates:
- # # <form action="/" method="post" onsubmit="new Ajax.Request('/',
- # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)});
+ # # <form action="/" method="post" onsubmit="new Ajax.Request('/',
+ # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)});
# # return false;"> <div><input name="commit" type="submit" value="Save" /></div>
# # </form>
# <% form_remote_tag :url => '/posts' do -%>
@@ -323,19 +323,19 @@ module ActionView
options[:form] = true
options[:html] ||= {}
- options[:html][:onsubmit] =
- (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") +
+ options[:html][:onsubmit] =
+ (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") +
"#{remote_function(options)}; return false;"
form_tag(options[:html].delete(:action) || url_for(options[:url]), options[:html], &block)
end
- # Creates a form that will submit using XMLHttpRequest in the background
- # instead of the regular reloading POST arrangement and a scope around a
+ # Creates a form that will submit using XMLHttpRequest in the background
+ # instead of the regular reloading POST arrangement and a scope around a
# specific resource that is used as a base for questioning about
- # values for the fields.
+ # values for the fields.
#
- # === Resource
+ # === Resource
#
# Example:
# <% remote_form_for(@post) do |f| %>
@@ -348,7 +348,7 @@ module ActionView
# ...
# <% end %>
#
- # === Nested Resource
+ # === Nested Resource
#
# Example:
# <% remote_form_for([@post, @comment]) do |f| %>
@@ -387,29 +387,31 @@ module ActionView
concat('</form>')
end
alias_method :form_remote_for, :remote_form_for
-
+
# Returns a button input tag with the element name of +name+ and a value (i.e., display text) of +value+
# that will submit form using XMLHttpRequest in the background instead of a regular POST request that
- # reloads the page.
+ # reloads the page.
#
# # Create a button that submits to the create action
- # #
- # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create',
- # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
+ # #
+ # # 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
# #
- # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'},
- # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
+ # # 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,7 +422,8 @@ module ActionView
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
# update return document using +update_element_function+ calls.
@@ -430,11 +433,11 @@ module ActionView
# Returns the JavaScript needed for a remote function.
# Takes the same arguments as link_to_remote.
- #
+ #
# Example:
- # # Generates: <select id="options" onchange="new Ajax.Updater('options',
+ # # Generates: <select id="options" onchange="new Ajax.Updater('options',
# # '/testing/update_options', {asynchronous:true, evalScripts:true})">
- # <select id="options" onchange="<%= remote_function(:update => "options",
+ # <select id="options" onchange="<%= remote_function(:update => "options",
# :url => { :action => :update_options }) %>">
# <option value="0">Hello</option>
# <option value="1">World</option>
@@ -452,7 +455,7 @@ module ActionView
update << "'#{options[:update]}'"
end
- function = update.empty? ?
+ function = update.empty? ?
"new Ajax.Request(" :
"new Ajax.Updater(#{update}, "
@@ -473,9 +476,9 @@ module ActionView
# callback when its contents have changed. The default callback is an
# Ajax call. By default the value of the observed field is sent as a
# parameter with the Ajax call.
- #
+ #
# Example:
- # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest',
+ # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest',
# # '/testing/find_suggestion', {asynchronous:true, evalScripts:true, parameters:'q=' + value})})
# <%= observe_field :suggest, :url => { :action => :find_suggestion },
# :frequency => 0.25,
@@ -497,14 +500,14 @@ module ActionView
# new Form.Element.Observer('glass', 1, function(element, value) {alert('Element changed')})
# The element parameter is the DOM element being observed, and the value is its value at the
# time the observer is triggered.
- #
+ #
# Additional options are:
# <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
# this field will be detected. Not setting this
# option at all or to a value equal to or less than
# zero will use event based observation instead of
# time based observation.
- # <tt>:update</tt>:: Specifies the DOM ID of the element whose
+ # <tt>:update</tt>:: Specifies the DOM ID of the element whose
# innerHTML should be updated with the
# XMLHttpRequest response text.
# <tt>:with</tt>:: A JavaScript expression specifying the parameters
@@ -515,7 +518,7 @@ module ActionView
# variable +value+.
#
# Examples
- #
+ #
# :with => "'my_custom_key=' + value"
# :with => "'person[name]=' + prompt('New name')"
# :with => "Form.Element.serialize('other-field')"
@@ -541,7 +544,7 @@ module ActionView
# observe_field 'book_title',
# :url => 'http://example.com/books/edit/1',
# :with => 'title'
- #
+ #
# # Sends params: {:book_title => 'Title of the book'} when the focus leaves
# # the input field.
# observe_field 'book_title',
@@ -555,7 +558,7 @@ module ActionView
build_observer('Form.Element.EventObserver', field_id, options)
end
end
-
+
# Observes the form with the DOM ID specified by +form_id+ and calls a
# callback when its contents have changed. The default callback is an
# Ajax call. By default all fields of the observed field are sent as
@@ -571,16 +574,17 @@ module ActionView
build_observer('Form.EventObserver', form_id, options)
end
end
-
- # All the methods were moved to GeneratorMethods so that
+
+ # All the methods were moved to GeneratorMethods so that
# #include_helpers_from_context has nothing to overwrite.
class JavaScriptGenerator #:nodoc:
def initialize(context, &block) #:nodoc:
@context, @lines = context, []
+ @context.output_buffer = @lines if @context
include_helpers_from_context
@context.instance_exec(self, &block)
end
-
+
private
def include_helpers_from_context
@context.extended_by.each do |mod|
@@ -588,17 +592,17 @@ module ActionView
end
extend GeneratorMethods
end
-
- # JavaScriptGenerator generates blocks of JavaScript code that allow you
- # to change the content and presentation of multiple DOM elements. Use
+
+ # JavaScriptGenerator generates blocks of JavaScript code that allow you
+ # to change the content and presentation of multiple DOM elements. Use
# this in your Ajax response bodies, either in a <script> tag or as plain
# JavaScript sent with a Content-type of "text/javascript".
#
- # Create new instances with PrototypeHelper#update_page or with
- # ActionController::Base#render, then call +insert_html+, +replace_html+,
- # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in
- # methods on the yielded generator in any order you like to modify the
- # content and appearance of the current page.
+ # Create new instances with PrototypeHelper#update_page or with
+ # ActionController::Base#render, then call +insert_html+, +replace_html+,
+ # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in
+ # methods on the yielded generator in any order you like to modify the
+ # content and appearance of the current page.
#
# Example:
#
@@ -611,12 +615,12 @@ module ActionView
# page.visual_effect :highlight, 'list'
# page.hide 'status-indicator', 'cancel-link'
# end
- #
+ #
#
# Helper methods can be used in conjunction with JavaScriptGenerator.
- # When a helper method is called inside an update block on the +page+
+ # When a helper method is called inside an update block on the +page+
# object, that method will also have access to a +page+ object.
- #
+ #
# Example:
#
# module ApplicationHelper
@@ -652,7 +656,7 @@ module ActionView
# end
# end
#
- # You can also use PrototypeHelper#update_page_tag instead of
+ # You can also use PrototypeHelper#update_page_tag instead of
# PrototypeHelper#update_page to wrap the generated JavaScript in a
# <script> tag.
module GeneratorMethods
@@ -665,7 +669,7 @@ module ActionView
end
end
end
-
+
# Returns a element reference by finding it through +id+ in the DOM. This element can then be
# used for further method calls. Examples:
#
@@ -686,31 +690,31 @@ module ActionView
JavaScriptElementProxy.new(self, ActionController::RecordIdentifier.dom_id(id))
end
end
-
- # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
+
+ # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
# expression as an argument to another JavaScriptGenerator method.
def literal(code)
ActiveSupport::JSON::Variable.new(code.to_s)
end
-
+
# Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
# used for further method calls. Examples:
#
# page.select('p') # => $$('p');
# page.select('p.welcome b').first # => $$('p.welcome b').first();
# page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
- #
+ #
# You can also use prototype enumerations with the collection. Observe:
- #
+ #
# # Generates: $$('#items li').each(function(value) { value.hide(); });
# page.select('#items li').each do |value|
# value.hide
- # end
+ # end
#
- # Though you can call the block param anything you want, they are always rendered in the
+ # Though you can call the block param anything you want, they are always rendered in the
# javascript as 'value, index.' Other enumerations, like collect() return the last statement:
#
- # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
+ # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
# page.select('#items li').collect('hidden') do |item|
# item.hide
# end
@@ -718,13 +722,13 @@ module ActionView
def select(pattern)
JavaScriptElementCollectionProxy.new(self, pattern)
end
-
+
# Inserts HTML at the specified +position+ relative to the DOM element
# identified by the given +id+.
- #
+ #
# +position+ may be one of:
- #
- # <tt>:top</tt>:: HTML is inserted inside the element, before the
+ #
+ # <tt>:top</tt>:: HTML is inserted inside the element, before the
# element's existing content.
# <tt>:bottom</tt>:: HTML is inserted inside the element, after the
# element's existing content.
@@ -747,7 +751,7 @@ module ActionView
insertion = position.to_s.camelize
call "new Insertion.#{insertion}", id, render(*options_for_render)
end
-
+
# Replaces the inner HTML of the DOM element with the given +id+.
#
# +options_for_render+ may be either a string of HTML to insert, or a hash
@@ -761,7 +765,7 @@ module ActionView
def replace_html(id, *options_for_render)
call 'Element.update', id, render(*options_for_render)
end
-
+
# Replaces the "outer HTML" (i.e., the entire element, not just its
# contents) of the DOM element with the given +id+.
#
@@ -783,7 +787,7 @@ module ActionView
# </div>
#
# # Insert a new person
- # #
+ # #
# # Generates: new Insertion.Bottom({object: "Matz", partial: "person"}, "");
# page.insert_html :bottom, :partial => 'person', :object => @person
#
@@ -795,7 +799,7 @@ module ActionView
def replace(id, *options_for_render)
call 'Element.replace', id, render(*options_for_render)
end
-
+
# Removes the DOM elements with the given +ids+ from the page.
#
# Example:
@@ -807,9 +811,9 @@ module ActionView
def remove(*ids)
loop_on_multiple_args 'Element.remove', ids
end
-
+
# Shows hidden DOM elements with the given +ids+.
- #
+ #
# Example:
#
# # Show a few people
@@ -819,7 +823,7 @@ module ActionView
def show(*ids)
loop_on_multiple_args 'Element.show', ids
end
-
+
# Hides the visible DOM elements with the given +ids+.
#
# Example:
@@ -829,9 +833,9 @@ module ActionView
# page.hide 'person_29', 'person_9', 'person_0'
#
def hide(*ids)
- loop_on_multiple_args 'Element.hide', ids
+ loop_on_multiple_args 'Element.hide', ids
end
-
+
# Toggles the visibility of the DOM elements with the given +ids+.
# Example:
#
@@ -841,9 +845,9 @@ module ActionView
# page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements
#
def toggle(*ids)
- loop_on_multiple_args 'Element.toggle', ids
+ loop_on_multiple_args 'Element.toggle', ids
end
-
+
# Displays an alert dialog with the given +message+.
#
# Example:
@@ -853,21 +857,21 @@ module ActionView
def alert(message)
call 'alert', message
end
-
+
# Redirects the browser to the given +location+ using JavaScript, in the same form as +url_for+.
#
# Examples:
#
# # Generates: window.location.href = "/mycontroller";
# page.redirect_to(:action => 'index')
- #
+ #
# # Generates: window.location.href = "/account/signup";
# page.redirect_to(:controller => 'account', :action => 'signup')
def redirect_to(location)
url = location.is_a?(String) ? location : @context.url_for(location)
record "window.location.href = #{url.inspect}"
end
-
+
# Reloads the browser's current +location+ using JavaScript
#
# Examples:
@@ -881,17 +885,17 @@ module ActionView
# Calls the JavaScript +function+, optionally with the given +arguments+.
#
# If a block is given, the block will be passed to a new JavaScriptGenerator;
- # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt>
+ # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt>
# and passed as the called function's final argument.
- #
+ #
# Examples:
#
# # Generates: Element.replace(my_element, "My content to replace with.")
# page.call 'Element.replace', 'my_element', "My content to replace with."
- #
+ #
# # Generates: alert('My message!')
# page.call 'alert', 'My message!'
- #
+ #
# # Generates:
# # my_method(function() {
# # $("one").show();
@@ -904,7 +908,7 @@ module ActionView
def call(function, *arguments, &block)
record "#{function}(#{arguments_for_call(arguments, block)})"
end
-
+
# Assigns the JavaScript +variable+ the given +value+.
#
# Examples:
@@ -915,13 +919,13 @@ module ActionView
# # Generates: record_count = 33;
# page.assign 'record_count', 33
#
- # # Generates: tabulated_total = 47
+ # # Generates: tabulated_total = 47
# page.assign 'tabulated_total', @total_from_cart
#
def assign(variable, value)
record "#{variable} = #{javascript_object_for(value)}"
end
-
+
# Writes raw JavaScript to the page.
#
# Example:
@@ -930,10 +934,10 @@ module ActionView
def <<(javascript)
@lines << javascript
end
-
+
# Executes the content of the block after a delay of +seconds+. Example:
#
- # # Generates:
+ # # Generates:
# # setTimeout(function() {
# # ;
# # new Effect.Fade("notice",{});
@@ -946,13 +950,13 @@ module ActionView
yield
record "}, #{(seconds * 1000).to_i})"
end
-
- # Starts a script.aculo.us visual effect. See
+
+ # Starts a script.aculo.us visual effect. See
# ActionView::Helpers::ScriptaculousHelper for more information.
def visual_effect(name, id = nil, options = {})
record @context.send(:visual_effect, name, id, options)
end
-
+
# Creates a script.aculo.us sortable element. Useful
# to recreate sortable elements after items get added
# or deleted.
@@ -960,66 +964,66 @@ module ActionView
def sortable(id, options = {})
record @context.send(:sortable_element_js, id, options)
end
-
+
# Creates a script.aculo.us draggable element.
# See ActionView::Helpers::ScriptaculousHelper for more information.
def draggable(id, options = {})
record @context.send(:draggable_element_js, id, options)
end
-
+
# Creates a script.aculo.us drop receiving element.
# See ActionView::Helpers::ScriptaculousHelper for more information.
def drop_receiving(id, options = {})
record @context.send(:drop_receiving_element_js, id, options)
end
-
+
private
def loop_on_multiple_args(method, ids)
- record(ids.size>1 ?
- "#{javascript_object_for(ids)}.each(#{method})" :
+ record(ids.size>1 ?
+ "#{javascript_object_for(ids)}.each(#{method})" :
"#{method}(#{ids.first.to_json})")
end
-
+
def page
self
end
-
+
def record(line)
returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do
self << line
end
end
-
+
def render(*options_for_render)
old_format = @context && @context.template_format
@context.template_format = :html if @context
- Hash === options_for_render.first ?
- @context.render(*options_for_render) :
+ Hash === options_for_render.first ?
+ @context.render(*options_for_render) :
options_for_render.first.to_s
ensure
@context.template_format = old_format if @context
end
-
+
def javascript_object_for(object)
object.respond_to?(:to_json) ? object.to_json : object.inspect
end
-
+
def arguments_for_call(arguments, block = nil)
arguments << block_to_function(block) if block
arguments.map { |argument| javascript_object_for(argument) }.join ', '
end
-
+
def block_to_function(block)
generator = self.class.new(@context, &block)
literal("function() { #{generator.to_s} }")
- end
+ end
def method_missing(method, *arguments)
JavaScriptProxy.new(self, method.to_s.camelize)
end
end
end
-
+
# Yields a JavaScriptGenerator and returns the generated JavaScript code.
# Use this to update multiple elements on a page in an Ajax response.
# See JavaScriptGenerator for more information.
@@ -1032,13 +1036,13 @@ module ActionView
def update_page(&block)
JavaScriptGenerator.new(@template, &block).to_s
end
-
+
# Works like update_page but wraps the generated JavaScript in a <script>
# tag. Use this to include generated JavaScript in an ERb template.
# See JavaScriptGenerator for more information.
#
# +html_options+ may be a hash of <script> attributes to be passed
- # to ActionView::Helpers::JavaScriptHelper#javascript_tag.
+ # to ActionView::Helpers::JavaScriptHelper#javascript_tag.
def update_page_tag(html_options = {}, &block)
javascript_tag update_page(&block), html_options
end
@@ -1046,7 +1050,7 @@ module ActionView
protected
def options_for_ajax(options)
js_options = build_callbacks(options)
-
+
js_options['asynchronous'] = options[:type] != :synchronous
js_options['method'] = method_option_to_s(options[:method]) if options[:method]
js_options['insertion'] = "Insertion.#{options[:position].to_s.camelize}" if options[:position]
@@ -1059,7 +1063,7 @@ module ActionView
elsif options[:with]
js_options['parameters'] = options[:with]
end
-
+
if protect_against_forgery? && !options[:form]
if js_options['parameters']
js_options['parameters'] << " + '&"
@@ -1068,14 +1072,14 @@ module ActionView
end
js_options['parameters'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')"
end
-
+
options_for_javascript(js_options)
end
- def method_option_to_s(method)
+ def method_option_to_s(method)
(method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'"
end
-
+
def build_observer(klass, name, options = {})
if options[:with] && (options[:with] !~ /[\{=(.]/)
options[:with] = "'#{options[:with]}=' + encodeURIComponent(value)"
@@ -1092,7 +1096,7 @@ module ActionView
javascript << ")"
javascript_tag(javascript)
end
-
+
def build_callbacks(options)
callbacks = {}
options.each do |callback, code|
@@ -1105,7 +1109,7 @@ module ActionView
end
end
- # Converts chained method calls on DOM proxy elements into JavaScript chains
+ # Converts chained method calls on DOM proxy elements into JavaScript chains
class JavaScriptProxy < ActiveSupport::BasicObject #:nodoc:
def initialize(generator, root = nil)
@@ -1121,7 +1125,7 @@ module ActionView
call("#{method.to_s.camelize(:lower)}", *arguments, &block)
end
end
-
+
def call(function, *arguments, &block)
append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})")
self
@@ -1130,23 +1134,23 @@ module ActionView
def assign(variable, value)
append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
end
-
+
def function_chain
@function_chain ||= @generator.instance_variable_get(:@lines)
end
-
+
def append_to_function_chain!(call)
function_chain[-1].chomp!(';')
function_chain[-1] += ".#{call};"
end
end
-
+
class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
def initialize(generator, id)
@id = id
super(generator, "$(#{id.to_json})")
end
-
+
# Allows access of element attributes through +attribute+. Examples:
#
# page['foo']['style'] # => $('foo').style;
@@ -1157,11 +1161,11 @@ module ActionView
append_to_function_chain!(attribute)
self
end
-
+
def []=(variable, value)
assign(variable, value)
end
-
+
def replace_html(*options_for_render)
call 'update', @generator.send(:render, *options_for_render)
end
@@ -1169,11 +1173,11 @@ module ActionView
def replace(*options_for_render)
call 'replace', @generator.send(:render, *options_for_render)
end
-
+
def reload(options_for_replace = {})
replace(options_for_replace.merge({ :partial => @id.to_s }))
end
-
+
end
class JavaScriptVariableProxy < JavaScriptProxy #:nodoc:
@@ -1192,7 +1196,7 @@ module ActionView
def to_json(options = nil)
@variable
end
-
+
private
def append_to_function_chain!(call)
@generator << @variable if @empty
@@ -1210,7 +1214,7 @@ module ActionView
def initialize(generator, pattern)
super(generator, @pattern = pattern)
end
-
+
def each_slice(variable, number, &block)
if block
enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
@@ -1219,18 +1223,18 @@ module ActionView
append_enumerable_function!("eachSlice(#{number.to_json});")
end
end
-
+
def grep(variable, pattern, &block)
enumerate :grep, :variable => variable, :return => true, :method_args => [pattern], :yield_args => %w(value index), &block
end
-
+
def in_groups_of(variable, number, fill_with = nil)
arguments = [number]
arguments << fill_with unless fill_with.nil?
add_variable_assignment!(variable)
append_enumerable_function!("inGroupsOf(#{arguments_for_call arguments});")
- end
-
+ end
+
def inject(variable, memo, &block)
enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block
end
@@ -1292,13 +1296,13 @@ module ActionView
function_chain.push("return #{function_chain.pop.chomp(';')};")
end
end
-
+
def append_enumerable_function!(call)
function_chain[-1].chomp!(';')
function_chain[-1] += ".#{call}"
end
end
-
+
class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
def initialize(generator, pattern)
super(generator, "$$(#{pattern.to_json})")
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index e1abec1847..5a296da247 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -12,14 +12,14 @@ module ActionView
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple).to_set
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
- # Returns an empty HTML tag of type +name+ which by default is XHTML
- # compliant. Set +open+ to true to create an open tag compatible
- # with HTML 4.0 and below. Add HTML attributes by passing an attributes
+ # Returns an empty HTML tag of type +name+ which by default is XHTML
+ # compliant. Set +open+ to true to create an open tag compatible
+ # with HTML 4.0 and below. Add HTML attributes by passing an attributes
# hash to +options+. Set +escape+ to false to disable attribute value
# escaping.
#
# ==== Options
- # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
+ # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
# <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
# symbols or strings for the attribute names.
#
@@ -30,7 +30,7 @@ module ActionView
# tag("br", nil, true)
# # => <br>
#
- # tag("input", { :type => 'text', :disabled => true })
+ # tag("input", { :type => 'text', :disabled => true })
# # => <input type="text" disabled="disabled" />
#
# tag("img", { :src => "open & shut.png" })
@@ -43,13 +43,13 @@ module ActionView
end
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
- # HTML attributes by passing an attributes hash to +options+.
+ # HTML attributes by passing an attributes hash to +options+.
# Instead of passing the content as an argument, you can also use a block
# in which case, you pass your +options+ as the second parameter.
# Set escape to false to disable attribute value escaping.
#
# ==== Options
- # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
+ # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
# <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
# symbols or strings for the attribute names.
#
@@ -68,7 +68,13 @@ module ActionView
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
if block_given?
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
- concat(content_tag_string(name, capture(&block), options, escape))
+ content_tag = content_tag_string(name, capture(&block), options, escape)
+
+ if block_called_from_erb?(block)
+ concat(content_tag)
+ else
+ content_tag
+ end
else
content_tag_string(name, content_or_options_with_block, options, escape)
end
@@ -102,6 +108,22 @@ module ActionView
end
private
+ BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template'
+
+ if RUBY_VERSION < '1.9.0'
+ # Check whether we're called from an erb template.
+ # We'd return a string in any other case, but erb <%= ... %>
+ # can't take an <% end %> later on, so we have to use <% ... %>
+ # and implicitly concat.
+ def block_called_from_erb?(block)
+ block && eval(BLOCK_CALLED_FROM_ERB, block)
+ end
+ else
+ def block_called_from_erb?(block)
+ block && eval(BLOCK_CALLED_FROM_ERB, block.binding)
+ end
+ end
+
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
"<#{name}#{tag_options}>#{content}</#{name}>"
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index a1a91f6b3d..3e3452b615 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -27,14 +27,10 @@ module ActionView
# %>
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
- if output_buffer && string
- output_buffer << string
- else
- string
- end
+ output_buffer << string
end
if RUBY_VERSION < '1.9'
@@ -472,7 +468,7 @@ module ActionView
[-\w]+ # subdomain or domain
(?:\.[-\w]+)* # remaining subdomains or domain
(?::\d+)? # port
- (?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:][^\s$]))+)?)* # path
+ (?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))+)?)* # path
(?:\?[\w\+@%&=.;-]+)? # query string
(?:\#[\w\-]*)? # trailing anchor
)
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index baecd304cd..e5178938fd 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -63,17 +63,15 @@ module ActionView
# # 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 @@ module ActionView
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>"
@@ -535,7 +533,7 @@ module ActionView
when method
"#{method_javascript_function(method, url, href)}return false;"
when popup
- popup_javascript_function(popup) + 'return false;'
+ "#{popup_javascript_function(popup)}return false;"
else
html_options["onclick"]
end
diff --git a/actionpack/lib/action_view/inline_template.rb b/actionpack/lib/action_view/inline_template.rb
index fd0ad48302..5e00cef13f 100644
--- a/actionpack/lib/action_view/inline_template.rb
+++ b/actionpack/lib/action_view/inline_template.rb
@@ -1,17 +1,19 @@
module ActionView #:nodoc:
- class InlineTemplate < Template #:nodoc:
- def initialize(view, source, locals = {}, type = nil)
- @view = view
+ class InlineTemplate #:nodoc:
+ include Renderable
+ attr_reader :source, :extension, :method_segment
+
+ def initialize(source, type = nil)
@source = source
@extension = type
- @locals = locals || {}
-
- @handler = self.class.handler_class_for_extension(@extension).new(@view)
+ @method_segment = "inline_#{@source.hash.abs}"
end
- def method_key
- @source
- end
+ private
+ # Always recompile inline templates
+ def recompile?(local_assigns)
+ true
+ end
end
end
diff --git a/actionpack/lib/action_view/partial_template.rb b/actionpack/lib/action_view/partial_template.rb
deleted file mode 100644
index 0cf996ca04..0000000000
--- a/actionpack/lib/action_view/partial_template.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-module ActionView #:nodoc:
- class PartialTemplate < Template #:nodoc:
- attr_reader :variable_name, :object
-
- def initialize(view, partial_path, object = nil, locals = {})
- @view_controller = view.controller if view.respond_to?(:controller)
- set_path_and_variable_name!(partial_path)
- super(view, @path, true, 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
- @handler.render(self)
- end
- end
-
- def render_member(object)
- @locals[:object] = @locals[@variable_name] = object
-
- template = render_template
- @locals[@counter_name] += 1
- @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] ||=
- if object.is_a?(ActionView::Base::ObjectWrapper)
- object.value
- else
- object
- end || @view_controller.instance_variable_get("@#{variable_name}")
- 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
diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb
index 6b294be6bd..5aa4c83009 100644
--- a/actionpack/lib/action_view/partials.rb
+++ b/actionpack/lib/action_view/partials.rb
@@ -104,10 +104,11 @@ 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
+ pick_template(find_partial_path(partial_path)).render_partial(self, 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))
@@ -123,35 +124,33 @@ module ActionView
end
end
- def render_partial_collection(partial_path, collection, partial_spacer_template = nil, local_assigns = {}) #:nodoc:
+ def render_partial_collection(partial_path, collection, partial_spacer_template = nil, local_assigns = {}, as = nil) #:nodoc:
return " " if collection.empty?
local_assigns = local_assigns ? local_assigns.clone : {}
spacer = partial_spacer_template ? render(:partial => partial_spacer_template) : ''
+ _paths = {}
+ _templates = {}
- if partial_path.nil?
- render_partial_collection_with_unknown_partial_path(collection, local_assigns)
- else
- render_partial_collection_with_known_partial_path(collection, partial_path, local_assigns)
+ index = 0
+ collection.map do |object|
+ _partial_path ||= partial_path || ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path)
+ path = _paths[_partial_path] ||= find_partial_path(_partial_path)
+ template = _templates[path] ||= pick_template(path)
+ local_assigns[template.counter_name] = index
+ result = template.render_partial(self, object, local_assigns, as)
+ index += 1
+ result
end.join(spacer)
end
- def render_partial_collection_with_known_partial_path(collection, partial_path, local_assigns)
- template = ActionView::PartialTemplate.new(self, partial_path, nil, local_assigns)
- collection.map do |element|
- template.render_member(element)
- end
- end
-
- def render_partial_collection_with_unknown_partial_path(collection, local_assigns)
- 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)
- template.counter = i
- i += 1
- template.render_member(element)
+ def find_partial_path(partial_path)
+ if partial_path.include?('/')
+ "#{File.dirname(partial_path)}/_#{File.basename(partial_path)}"
+ elsif respond_to?(:controller)
+ "#{controller.class.controller_path}/_#{partial_path}"
+ else
+ "_#{partial_path}"
end
end
end
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
new file mode 100644
index 0000000000..b0ab7d0c67
--- /dev/null
+++ b/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
+ (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file|
+ unless File.directory?(file)
+ template = Template.new(file.split("#{self}/").last, self)
+ # Eager load memoized methods and freeze cached template
+ template.freeze if Base.cache_template_loading
+ yield template
+ end
+ end
+ end
+ end
+
+ def initialize(*args)
+ super(*args).map! { |obj| self.class.type_cast(obj) }
+ end
+
+ def reload!
+ each { |path| path.reload! }
+ end
+
+ def <<(obj)
+ super(self.class.type_cast(obj))
+ end
+
+ def push(*objs)
+ delete_paths!(objs)
+ super(*objs.map { |obj| self.class.type_cast(obj) })
+ end
+
+ def unshift(*objs)
+ delete_paths!(objs)
+ super(*objs.map { |obj| self.class.type_cast(obj) })
+ end
+
+ def [](template_path)
+ each do |path|
+ if template = path[template_path]
+ return template
+ end
+ end
+ nil
+ end
+
+ private
+ def delete_paths!(paths)
+ paths.each { |p1| delete_if { |p2| p1.to_s == p2.to_s } }
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb
new file mode 100644
index 0000000000..f66356c939
--- /dev/null
+++ b/actionpack/lib/action_view/renderable.rb
@@ -0,0 +1,78 @@
+module ActionView
+ module Renderable
+ # NOTE: The template that this mixin is beening include into is frozen
+ # So you can not set or modify any instance variables
+
+ def self.included(base)
+ @@mutex = Mutex.new
+ end
+
+ include ActiveSupport::Memoizable
+
+ def handler
+ Template.handler_class_for_extension(extension)
+ end
+ memoize :handler
+
+ def compiled_source
+ handler.new(nil).compile(self) if handler.compilable?
+ end
+ memoize :compiled_source
+
+ def render(view, local_assigns = {})
+ view._first_render ||= self
+ view._last_render = self
+ view.send(:evaluate_assigns)
+ compile(local_assigns) if handler.compilable?
+ handler.new(view).render(self, local_assigns)
+ end
+
+ def method(local_assigns)
+ if local_assigns && local_assigns.any?
+ local_assigns_keys = "locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}"
+ end
+ ['_run', extension, method_segment, local_assigns_keys].compact.join('_').to_sym
+ end
+
+ private
+ # Compile and evaluate the template's code
+ def compile(local_assigns)
+ render_symbol = method(local_assigns)
+
+ @@mutex.synchronize do
+ return false unless recompile?(render_symbol)
+
+ locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join
+
+ source = <<-end_src
+ def #{render_symbol}(local_assigns)
+ old_output_buffer = output_buffer;#{locals_code};#{compiled_source}
+ ensure
+ self.output_buffer = old_output_buffer
+ end
+ end_src
+
+ begin
+ file_name = respond_to?(:filename) ? filename : 'compiled-template'
+ ActionView::Base::CompiledTemplates.module_eval(source, file_name, 0)
+ rescue Exception => e # errors from template code
+ if logger = ActionController::Base.logger
+ logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
+ logger.debug "Function body: #{source}"
+ logger.debug "Backtrace: #{e.backtrace.join("\n")}"
+ end
+
+ raise ActionView::TemplateError.new(self, {}, e)
+ end
+ end
+ end
+
+ # Method to check whether template compilation is necessary.
+ # The template will be compiled if the file has not been compiled yet, or
+ # if local_assigns has a new key, which isn't supported by the compiled code yet.
+ def recompile?(symbol)
+ meth = Base::CompiledTemplates.instance_method(template.method) rescue nil
+ !(meth && Base.cache_template_loading)
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/renderable_partial.rb b/actionpack/lib/action_view/renderable_partial.rb
new file mode 100644
index 0000000000..fdb1a5e6a7
--- /dev/null
+++ b/actionpack/lib/action_view/renderable_partial.rb
@@ -0,0 +1,36 @@
+module ActionView
+ module RenderablePartial
+ # NOTE: The template that this mixin is beening include into is frozen
+ # So you can not set or modify any instance variables
+
+ include ActiveSupport::Memoizable
+
+ def variable_name
+ name.sub(/\A_/, '').to_sym
+ end
+ memoize :variable_name
+
+ def counter_name
+ "#{variable_name}_counter".to_sym
+ end
+ memoize :counter_name
+
+ def render(view, local_assigns = {})
+ ActionController::Base.benchmark("Rendered #{path_without_format_and_extension}", Logger::DEBUG, false) do
+ super
+ end
+ end
+
+ def render_partial(view, object = nil, local_assigns = {}, as = nil)
+ object ||= local_assigns[:object] ||
+ local_assigns[variable_name] ||
+ view.controller.instance_variable_get("@#{variable_name}") if view.respond_to?(:controller)
+
+ # Ensure correct object is reassigned to other accessors
+ local_assigns[:object] = local_assigns[variable_name] = object
+ local_assigns[as] = object if as
+
+ render_template(view, local_assigns)
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 4c3f252c10..304aec3a4c 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -1,94 +1,96 @@
module ActionView #:nodoc:
- class Template #:nodoc:
+ class Template
extend TemplateHandlers
+ include ActiveSupport::Memoizable
+ include Renderable
- attr_accessor :locals
- attr_reader :handler, :path, :extension, :filename, :method
+ attr_accessor :filename, :load_path, :base_path, :name, :format, :extension
+ delegate :to_s, :to => :path
- def initialize(view, path, use_full_path, locals = {})
- @view = view
- @paths = view.view_paths
+ def initialize(template_path, load_paths = [])
+ template_path = template_path.dup
+ @base_path, @name, @format, @extension = split(template_path)
+ @base_path.to_s.gsub!(/\/$/, '') # Push to split method
+ @load_path, @filename = find_full_path(template_path, load_paths)
- @original_path = path
- @path = TemplateFile.from_path(path, !use_full_path)
- @view.first_render ||= @path.to_s
- @source = nil # Don't read the source until we know that it is required
- set_extension_and_file_name(use_full_path)
-
- @locals = locals || {}
- @handler = self.class.handler_class_for_extension(@extension).new(@view)
+ # Extend with partial super powers
+ extend RenderablePartial if @name =~ /^_/
end
- def render_template
- render
- rescue Exception => e
- raise e unless filename
- if TemplateError === e
- e.sub_template_of(filename)
- raise e
- else
- raise TemplateError.new(self, @view.assigns, e)
- end
+ def format_and_extension
+ (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions
end
+ memoize :format_and_extension
- def render
- prepare!
- @handler.render(self)
+ def path
+ [base_path, [name, format, extension].compact.join('.')].compact.join('/')
end
+ memoize :path
def path_without_extension
- @path.path_without_extension
+ [base_path, [name, format].compact.join('.')].compact.join('/')
end
+ memoize :path_without_extension
- def source
- @source ||= File.read(self.filename)
+ def path_without_format_and_extension
+ [base_path, name].compact.join('/')
end
+ memoize :path_without_format_and_extension
- def method_key
- @filename
+ def source
+ File.read(filename)
end
+ memoize :source
- def base_path_for_exception
- (@paths.find_load_path_for_path(@path) || @paths.first).to_s
+ def method_segment
+ segment = File.expand_path(filename)
+ segment.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT)
+ segment.gsub!(/([^a-zA-Z0-9_])/) { $1.ord }
end
+ memoize :method_segment
- def prepare!
- @view.send :evaluate_assigns
- @view.current_render_extension = @extension
-
- if @handler.compilable?
- @handler.compile_template(self) # compile the given template, if necessary
- @method = @view.method_names[method_key] # Set the method name for this template and run it
+ def render_template(view, local_assigns = {})
+ render(view, local_assigns)
+ rescue Exception => e
+ raise e unless filename
+ if TemplateError === e
+ e.sub_template_of(filename)
+ raise e
+ else
+ raise TemplateError.new(self, view.assigns, e)
end
end
private
- def set_extension_and_file_name(use_full_path)
- @extension = @path.extension
-
- if use_full_path
- unless @extension
- @path = @view.send(:template_file_from_name, @path)
- raise_missing_template_exception unless @path
- @extension = @path.extension
- end
+ def valid_extension?(extension)
+ Template.template_handler_extensions.include?(extension)
+ end
- if @path = @paths.find_template_file_for_path(path)
- @filename = @path.full_path
- @extension = @path.extension
- end
- else
- @filename = @path.full_path
+ def find_full_path(path, load_paths)
+ load_paths = Array(load_paths) + [nil]
+ load_paths.each do |load_path|
+ file = [load_path, path].compact.join('/')
+ return load_path, file if File.exist?(file)
end
-
- raise_missing_template_exception if @filename.blank?
+ raise MissingTemplate.new(load_paths, path)
end
- def raise_missing_template_exception
- full_template_path = @original_path.include?('.') ? @original_path : "#{@original_path}.#{@view.template_format}.erb"
- display_paths = @paths.join(':')
- template_type = (@original_path =~ /layouts/i) ? 'layout' : 'template'
- raise(MissingTemplate, "Missing #{template_type} #{full_template_path} in view path #{display_paths}")
+ # Returns file split into an array
+ # [base_path, name, format, extension]
+ def split(file)
+ if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/)
+ if m[5] # Mulipart formats
+ [m[1], m[2], "#{m[3]}.#{m[4]}", m[5]]
+ elsif m[4] # Single format
+ [m[1], m[2], m[3], m[4]]
+ else
+ if valid_extension?(m[3]) # No format
+ [m[1], m[2], nil, m[3]]
+ else # No extension
+ [m[1], m[2], m[3], nil]
+ end
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_view/template_error.rb b/actionpack/lib/action_view/template_error.rb
index 65d80362b5..35fc07bdb2 100644
--- a/actionpack/lib/action_view/template_error.rb
+++ b/actionpack/lib/action_view/template_error.rb
@@ -7,7 +7,7 @@ module ActionView
attr_reader :original_exception
def initialize(template, assigns, original_exception)
- @base_path = template.base_path_for_exception
+ @base_path = template.base_path
@assigns, @source, @original_exception = assigns.dup, template.source, original_exception
@file_path = template.filename
@backtrace = compute_backtrace
@@ -105,6 +105,6 @@ module ActionView
end
if defined?(Exception::TraceSubstitutions)
- Exception::TraceSubstitutions << [/:in\s+`_run_(html|xml).*'\s*$/, '']
+ Exception::TraceSubstitutions << [/:in\s+`_run_.*'\s*$/, '']
Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}/}, ''] if defined?(RAILS_ROOT)
end
diff --git a/actionpack/lib/action_view/template_file.rb b/actionpack/lib/action_view/template_file.rb
deleted file mode 100644
index dd66482b3c..0000000000
--- a/actionpack/lib/action_view/template_file.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-module ActionView #:nodoc:
- # TemplateFile abstracts the pattern of querying a file path for its
- # path with or without its extension. The path is only the partial path
- # from the load path root e.g. "hello/index.html.erb" not
- # "app/views/hello/index.html.erb"
- class TemplateFile
- def self.from_path(path, use_full_path = false)
- path.is_a?(self) ? path : new(path, use_full_path)
- end
-
- def self.from_full_path(load_path, full_path)
- file = new(full_path.split(load_path).last)
- file.load_path = load_path
- file.freeze
- end
-
- attr_accessor :load_path, :base_path, :name, :format, :extension
- delegate :to_s, :inspect, :to => :path
-
- def initialize(path, use_full_path = false)
- path = path.dup
-
- # Clear the forward slash in the beginning unless using full path
- trim_forward_slash!(path) unless use_full_path
-
- @base_path, @name, @format, @extension = split(path)
- end
-
- def freeze
- @load_path.freeze
- @base_path.freeze
- @name.freeze
- @format.freeze
- @extension.freeze
- super
- end
-
- def format_and_extension
- extensions = [format, extension].compact.join(".")
- extensions.blank? ? nil : extensions
- end
-
- def full_path
- if load_path
- "#{load_path}/#{path}"
- else
- path
- end
- end
-
- def path
- base_path.to_s + [name, format, extension].compact.join(".")
- end
-
- def path_without_extension
- base_path.to_s + [name, format].compact.join(".")
- end
-
- def path_without_format_and_extension
- "#{base_path}#{name}"
- end
-
- def dup_with_extension(extension)
- file = dup
- file.extension = extension ? extension.to_s : nil
- file
- end
-
- private
- def trim_forward_slash!(path)
- path.sub!(/^\//, '')
- end
-
- # Returns file split into an array
- # [base_path, name, format, extension]
- def split(file)
- if m = file.match(/^(.*\/)?(\w+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/)
- if m[5] # Mulipart formats
- [m[1], m[2], "#{m[3]}.#{m[4]}", m[5]]
- elsif m[4] # Single format
- [m[1], m[2], m[3], m[4]]
- else # No format
- [m[1], m[2], nil, m[3]]
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/template_handler.rb b/actionpack/lib/action_view/template_handler.rb
index 39e578e586..e2dd305f93 100644
--- a/actionpack/lib/action_view/template_handler.rb
+++ b/actionpack/lib/action_view/template_handler.rb
@@ -1,9 +1,5 @@
module ActionView
class TemplateHandler
- def self.line_offset
- 0
- end
-
def self.compilable?
false
end
@@ -12,7 +8,7 @@ module ActionView
@view = view
end
- def render(template)
+ def render(template, local_assigns = {})
end
def compile(template)
@@ -21,13 +17,5 @@ module ActionView
def compilable?
self.class.compilable?
end
-
- def line_offset
- self.class.line_offset
- end
-
- # Called by CacheHelper#cache
- def cache_fragment(block, name = {}, options = nil)
- end
end
end
diff --git a/actionpack/lib/action_view/template_handlers/builder.rb b/actionpack/lib/action_view/template_handlers/builder.rb
index ee02ce1a6f..335ec1abb4 100644
--- a/actionpack/lib/action_view/template_handlers/builder.rb
+++ b/actionpack/lib/action_view/template_handlers/builder.rb
@@ -5,23 +5,13 @@ module ActionView
class Builder < TemplateHandler
include Compilable
- def self.line_offset
- 2
- end
-
def compile(template)
- content_type_handler = (@view.send!(:controller).respond_to?(:response) ? "controller.response" : "controller")
-
- "#{content_type_handler}.content_type ||= Mime::XML\n" +
- "xml = ::Builder::XmlMarkup.new(:indent => 2)\n" +
+ # ActionMailer does not have a response
+ "controller.respond_to?(:response) && controller.response.content_type ||= Mime::XML;" +
+ "xml = ::Builder::XmlMarkup.new(:indent => 2);" +
+ "self.output_buffer = xml.target!;" +
template.source +
- "\nxml.target!\n"
- end
-
- def cache_fragment(block, name = {}, options = nil)
- @view.fragment_for(block, name, options) do
- eval('xml.target!', block.binding)
- end
+ ";xml.target!;"
end
end
end
diff --git a/actionpack/lib/action_view/template_handlers/compilable.rb b/actionpack/lib/action_view/template_handlers/compilable.rb
index 1aef81ba1a..a0ebaefeef 100644
--- a/actionpack/lib/action_view/template_handlers/compilable.rb
+++ b/actionpack/lib/action_view/template_handlers/compilable.rb
@@ -1,21 +1,8 @@
module ActionView
module TemplateHandlers
module Compilable
-
def self.included(base)
base.extend ClassMethod
-
- # Map method names to their compile time
- base.cattr_accessor :compile_time
- base.compile_time = {}
-
- # Map method names to the names passed in local assigns so far
- base.cattr_accessor :template_args
- base.template_args = {}
-
- # Count the number of inline templates
- base.cattr_accessor :inline_template_count
- base.inline_template_count = 0
end
module ClassMethod
@@ -24,105 +11,10 @@ module ActionView
true
end
end
-
- def render(template)
- @view.send :execute, template
- end
-
- # Compile and evaluate the template's code
- def compile_template(template)
- return unless compile_template?(template)
-
- render_symbol = assign_method_name(template)
- render_source = create_template_source(template, render_symbol)
- line_offset = self.template_args[render_symbol].size + self.line_offset
-
- begin
- file_name = template.filename || 'compiled-template'
- ActionView::Base::CompiledTemplates.module_eval(render_source, file_name, -line_offset)
- rescue Exception => e # errors from template code
- if @view.logger
- @view.logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
- @view.logger.debug "Function body: #{render_source}"
- @view.logger.debug "Backtrace: #{e.backtrace.join("\n")}"
- end
-
- raise ActionView::TemplateError.new(template, @view.assigns, e)
- end
-
- self.compile_time[render_symbol] = Time.now
- # logger.debug "Compiled template #{file_name || template}\n ==> #{render_symbol}" if logger
- end
-
- private
-
- # Method to check whether template compilation is necessary.
- # The template will be compiled if the inline template or file has not been compiled yet,
- # if local_assigns has a new key, which isn't supported by the compiled code yet,
- # or if the file has changed on disk and checking file mods hasn't been disabled.
- def compile_template?(template)
- method_key = template.method_key
- render_symbol = @view.method_names[method_key]
-
- compile_time = self.compile_time[render_symbol]
- if compile_time && supports_local_assigns?(render_symbol, template.locals)
- if template.filename && !@view.cache_template_loading
- template_changed_since?(template.filename, compile_time)
- end
- else
- true
- end
- end
-
- def assign_method_name(template)
- @view.method_names[template.method_key] ||= compiled_method_name(template)
- end
- def compiled_method_name(template)
- ['_run', self.class.to_s.demodulize.underscore, compiled_method_name_file_path_segment(template.filename)].compact.join('_').to_sym
+ def render(template, local_assigns = {})
+ @view.send(:execute, template, local_assigns)
end
-
- def compiled_method_name_file_path_segment(file_name)
- if file_name
- s = File.expand_path(file_name)
- s.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT)
- s.gsub!(/([^a-zA-Z0-9_])/) { $1.ord }
- s
- else
- (self.inline_template_count += 1).to_s
- end
- end
-
- # Method to create the source code for a given template.
- def create_template_source(template, render_symbol)
- body = compile(template)
-
- self.template_args[render_symbol] ||= {}
- locals_keys = self.template_args[render_symbol].keys | template.locals.keys
- self.template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h }
-
- locals_code = ""
- locals_keys.each do |key|
- locals_code << "#{key} = local_assigns[:#{key}]\n"
- end
-
- "def #{render_symbol}(local_assigns)\nold_output_buffer = output_buffer;#{locals_code}#{body}\nensure\nself.output_buffer = old_output_buffer\nend"
- end
-
- # Return true if the given template was compiled for a superset of the keys in local_assigns
- def supports_local_assigns?(render_symbol, local_assigns)
- local_assigns.empty? ||
- ((args = self.template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) })
- end
-
- # Method to handle checking a whether a template has changed since last compile; isolated so that templates
- # not stored on the file system can hook and extend appropriately.
- def template_changed_since?(file_name, compile_time)
- lstat = File.lstat(file_name)
- compile_time < lstat.mtime ||
- (lstat.symlink? && compile_time < File.stat(file_name).mtime)
- end
-
end
end
end
diff --git a/actionpack/lib/action_view/template_handlers/erb.rb b/actionpack/lib/action_view/template_handlers/erb.rb
index ad4ccc7c42..2f2febaa52 100644
--- a/actionpack/lib/action_view/template_handlers/erb.rb
+++ b/actionpack/lib/action_view/template_handlers/erb.rb
@@ -42,12 +42,14 @@ module ActionView
class ERB < TemplateHandler
include Compilable
- def compile(template)
- ::ERB.new(template.source, nil, @view.erb_trim_mode, '@output_buffer').src
- end
+ # Specify trim mode for the ERB compiler. Defaults to '-'.
+ # See ERb documentation for suitable values.
+ cattr_accessor :erb_trim_mode
+ self.erb_trim_mode = '-'
- def cache_fragment(block, name = {}, options = nil) #:nodoc:
- @view.fragment_for(block, name, options) { @view.response.template.output_buffer ||= '' }
+ def compile(template)
+ src = ::ERB.new(template.source, nil, erb_trim_mode, '@output_buffer').src
+ "__in_erb_template=true;#{src}"
end
end
end
diff --git a/actionpack/lib/action_view/template_handlers/rjs.rb b/actionpack/lib/action_view/template_handlers/rjs.rb
index 5854e33fed..a700655c9a 100644
--- a/actionpack/lib/action_view/template_handlers/rjs.rb
+++ b/actionpack/lib/action_view/template_handlers/rjs.rb
@@ -3,24 +3,9 @@ module ActionView
class RJS < TemplateHandler
include Compilable
- def self.line_offset
- 2
- end
-
def compile(template)
- "controller.response.content_type ||= Mime::JS\n" +
- "update_page do |page|\n#{template.source}\nend"
- end
-
- def cache_fragment(block, name = {}, options = nil) #:nodoc:
- @view.fragment_for(block, name, options) do
- begin
- debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, false
- eval('page.to_s', block.binding)
- ensure
- ActionView::Base.debug_rjs = debug_mode
- end
- end
+ "controller.response.content_type ||= Mime::JS;" +
+ "update_page do |page|;#{template.source}\nend"
end
end
end
diff --git a/actionpack/lib/action_view/view_load_paths.rb b/actionpack/lib/action_view/view_load_paths.rb
deleted file mode 100644
index e873d96aa0..0000000000
--- a/actionpack/lib/action_view/view_load_paths.rb
+++ /dev/null
@@ -1,103 +0,0 @@
-module ActionView #:nodoc:
- class ViewLoadPaths < Array #:nodoc:
- def self.type_cast(obj)
- obj.is_a?(String) ? LoadPath.new(obj) : obj
- end
-
- class LoadPath #:nodoc:
- attr_reader :path, :paths
- delegate :to_s, :inspect, :to => :path
-
- def initialize(path)
- @path = path.freeze
- reload!
- end
-
- def eql?(view_path)
- view_path.is_a?(ViewPath) && @path == view_path.path
- end
-
- def hash
- @path.hash
- end
-
- # Rebuild load path directory cache
- def reload!
- @paths = {}
-
- files.each do |file|
- @paths[file.path] = file
- @paths[file.path_without_extension] ||= file
- end
-
- @paths.freeze
- end
-
- # Tries to find the extension for the template name.
- # If it does not it exist, tries again without the format extension
- # find_template_file_for_partial_path('users/show') => 'html.erb'
- # find_template_file_for_partial_path('users/legacy') => 'rhtml'
- def find_template_file_for_partial_path(file)
- @paths[file.path] || @paths[file.path_without_extension] || @paths[file.path_without_format_and_extension]
- end
-
- private
- # Get all the files and directories in the path
- def files_in_path
- Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")
- end
-
- # Create an array of all the files within the path
- def files
- files_in_path.map do |file|
- TemplateFile.from_full_path(@path, file) unless File.directory?(file)
- end.compact
- end
- end
-
- def initialize(*args)
- super(*args).map! { |obj| self.class.type_cast(obj) }
- end
-
- def reload!
- each { |path| path.reload! }
- end
-
- def <<(obj)
- super(self.class.type_cast(obj))
- end
-
- def push(*objs)
- delete_paths!(objs)
- super(*objs.map { |obj| self.class.type_cast(obj) })
- end
-
- def unshift(*objs)
- delete_paths!(objs)
- super(*objs.map { |obj| self.class.type_cast(obj) })
- end
-
- def template_exists?(file)
- find_load_path_for_path(file) ? true : false
- end
-
- def find_load_path_for_path(file)
- find { |path| path.paths[file.to_s] }
- end
-
- def find_template_file_for_path(file)
- file = TemplateFile.from_path(file)
- each do |path|
- if f = path.find_template_file_for_partial_path(file)
- return f
- end
- end
- nil
- end
-
- private
- def delete_paths!(paths)
- paths.each { |p1| delete_if { |p2| p1.to_s == p2.to_s } }
- end
- end
-end