diff options
Diffstat (limited to 'actionpack/lib/action_controller')
20 files changed, 489 insertions, 432 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? |