diff options
author | Emilio Tagua <miloops@gmail.com> | 2009-08-10 18:07:33 -0300 |
---|---|---|
committer | Emilio Tagua <miloops@gmail.com> | 2009-08-10 18:07:33 -0300 |
commit | 0e2fbd80e2420329738b891240d44a056cea1de4 (patch) | |
tree | 5b16755670be58e168b5e86e2cdcb43ee5aa3918 /actionpack/lib | |
parent | eb3ae44ccaff1dc63eb31bf86d8db07c88ddc413 (diff) | |
parent | 600a89f2082beadf4af9fe140a1a2ae56386cd49 (diff) | |
download | rails-0e2fbd80e2420329738b891240d44a056cea1de4.tar.gz rails-0e2fbd80e2420329738b891240d44a056cea1de4.tar.bz2 rails-0e2fbd80e2420329738b891240d44a056cea1de4.zip |
Merge commit 'rails/master'
Conflicts:
activerecord/lib/active_record/calculations.rb
activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
Diffstat (limited to 'actionpack/lib')
17 files changed, 250 insertions, 111 deletions
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 04eaa02441..f3072fad74 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -4,8 +4,16 @@ module AbstractController include RenderingController + def self.next_serial + @helper_serial ||= 0 + @helper_serial += 1 + end + included do extlib_inheritable_accessor(:_helpers) { Module.new } + extlib_inheritable_accessor(:_helper_serial) do + AbstractController::Helpers.next_serial + end end module ClassMethods @@ -58,6 +66,8 @@ module AbstractController # of the helper module. Any methods defined in the block # will be helpers. def helper(*args, &block) + self._helper_serial = AbstractController::Helpers.next_serial + 1 + args.flatten.each do |arg| case arg when Module diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 61f1c715c8..698189bd46 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -41,7 +41,7 @@ module ActionController module ImplicitRender def send_action(*) ret = super - default_render unless performed? + default_render unless response_body ret end diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb index 23e7b1b3af..f94d1c669c 100644 --- a/actionpack/lib/action_controller/metal/compatibility.rb +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -64,6 +64,8 @@ module ActionController cattr_accessor :ip_spoofing_check self.ip_spoofing_check = true + + cattr_accessor :trusted_proxies end # For old tests diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 525787bf92..2b62a1be85 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -141,7 +141,7 @@ module ActionController end def decode_credentials(request) - ActiveSupport::Base64.decode64(authorization(request).split.last || '') + ActiveSupport::Base64.decode64(authorization(request).split(' ', 2).last || '') end def encode_credentials(user_name, password) @@ -197,9 +197,10 @@ module ActionController return false unless password method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD'] + uri = credentials[:uri][0,1] == '/' ? request.request_uri : request.url [true, false].any? do |password_is_ha1| - expected = expected_response(method, request.env['REQUEST_URI'], credentials, password, password_is_ha1) + expected = expected_response(method, uri, credentials, password, password_is_ha1) expected == credentials[:response] end end diff --git a/actionpack/lib/action_controller/routing/resources.rb b/actionpack/lib/action_controller/routing/resources.rb index 2dee0a3d87..4862cf7115 100644 --- a/actionpack/lib/action_controller/routing/resources.rb +++ b/actionpack/lib/action_controller/routing/resources.rb @@ -320,9 +320,10 @@ module ActionController # notes.resources :attachments # end # - # * <tt>:path_names</tt> - Specify different names for the 'new' and 'edit' actions. For example: + # * <tt>:path_names</tt> - Specify different path names for the actions. For example: # # new_products_path == '/productos/nuevo' - # map.resources :products, :as => 'productos', :path_names => { :new => 'nuevo', :edit => 'editar' } + # # bids_product_path(1) == '/productos/1/licitacoes' + # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' } # # You can also set default action names from an environment, like this: # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' } @@ -528,13 +529,13 @@ module ActionController resource = Resource.new(entities, options) with_options :controller => resource.controller do |map| + map_associations(resource, options) + map_collection_actions(map, resource) map_default_collection_actions(map, resource) map_new_actions(map, resource) map_member_actions(map, resource) - map_associations(resource, options) - if block_given? with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) end @@ -589,7 +590,10 @@ module ActionController resource.collection_methods.each do |method, actions| actions.each do |action| [method].flatten.each do |m| - map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action}", "#{action}_#{resource.name_prefix}#{resource.plural}", m) + action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) + action_path ||= action + + map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m) end end end diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb index 040a7e2cb6..09f6024d39 100644 --- a/actionpack/lib/action_controller/routing/route_set.rb +++ b/actionpack/lib/action_controller/routing/route_set.rb @@ -407,9 +407,24 @@ module ActionController # don't use the recalled keys when determining which routes to check routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }] - routes.each do |route| + routes[1].each_with_index do |route, index| results = route.__send__(method, options, merged, expire_on) - return results if results && (!results.is_a?(Array) || results.first) + if results && (!results.is_a?(Array) || results.first) + + # Compare results with Rails 3.0 behavior + if routes[0][index] != route + routes[0].each do |route2| + new_results = route2.__send__(method, options, merged, expire_on) + if new_results && (!new_results.is_a?(Array) || new_results.first) + ActiveSupport::Deprecation.warn "The URL you generated will use the first matching route in routes.rb rather than the \"best\" match. " + + "In Rails 3.0 #{new_results} would of been generated instead of #{results}" + break + end + end + end + + return results + end end end @@ -448,7 +463,10 @@ module ActionController @routes_by_controller ||= Hash.new do |controller_hash, controller| controller_hash[controller] = Hash.new do |action_hash, action| action_hash[action] = Hash.new do |key_hash, keys| - key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys) + key_hash[keys] = [ + routes_for_controller_and_action_and_keys(controller, action, keys), + deprecated_routes_for_controller_and_action_and_keys(controller, action, keys) + ] end end end @@ -460,17 +478,16 @@ module ActionController merged = options if expire_on[:controller] action = merged[:action] || 'index' - routes_by_controller[controller][action][merged.keys] + routes_by_controller[controller][action][merged.keys][1] end - def routes_for_controller_and_action(controller, action) - selected = routes.select do |route| + def routes_for_controller_and_action_and_keys(controller, action, keys) + routes.select do |route| route.matches_controller_and_action? controller, action end - (selected.length == routes.length) ? routes : selected end - def routes_for_controller_and_action_and_keys(controller, action, keys) + def deprecated_routes_for_controller_and_action_and_keys(controller, action, keys) selected = routes.select do |route| route.matches_controller_and_action? controller, action end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 5f9463eb91..4190fa21cd 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -246,7 +246,7 @@ module ActionDispatch remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].scan(/[^,\s]+/) unless remote_addr_list.blank? - not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES} + not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES || addr =~ ActionController::Base.trusted_proxies} return not_trusted_addrs.first unless not_trusted_addrs.empty? end remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',') @@ -265,7 +265,7 @@ EOM end if remote_ips - while remote_ips.size > 1 && TRUSTED_PROXIES =~ remote_ips.last.strip + while remote_ips.size > 1 && (TRUSTED_PROXIES =~ remote_ips.last.strip || ActionController::Base.trusted_proxies =~ remote_ips.last.strip) remote_ips.pop end diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index dd75cda6b9..d22adfa749 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -345,14 +345,17 @@ module ActionDispatch # # Use the first argument to narrow down assertions to only statements # of that type. Possible values are <tt>:replace</tt>, <tt>:replace_html</tt>, - # <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tt> and - # <tt>:insert_html</tt>. + # <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tta>, + # <tt>:insert_html</tt> and <tt>:redirect</tt>. # # Use the argument <tt>:insert</tt> followed by an insertion position to narrow # down the assertion to only statements that insert elements in that # position. Possible values are <tt>:top</tt>, <tt>:bottom</tt>, <tt>:before</tt> # and <tt>:after</tt>. # + # Use the argument <tt>:redirect</tt> follwed by a path to check that an statement + # which redirects to the specified path is generated. + # # Using the <tt>:remove</tt> statement, you will be able to pass a block, but it will # be ignored as there is no HTML passed for this statement. # @@ -399,6 +402,9 @@ module ActionDispatch # # # The same, but shorter. # assert_select "ol>li", 4 + # + # # Checking for a redirect. + # assert_select_rjs :redirect, root_path def assert_select_rjs(*args, &block) rjs_type = args.first.is_a?(Symbol) ? args.shift : nil id = args.first.is_a?(String) ? args.shift : nil @@ -576,7 +582,8 @@ module ActionDispatch :chained_replace => "\\$\\(#{RJS_ANY_ID}\\)\\.replace\\(#{RJS_PATTERN_HTML}\\)", :chained_replace_html => "\\$\\(#{RJS_ANY_ID}\\)\\.update\\(#{RJS_PATTERN_HTML}\\)", :replace_html => "Element\\.update\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)", - :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)" + :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)", + :redirect => "window.location.href = #{RJS_ANY_ID}" } [:remove, :show, :hide, :toggle].each do |action| RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)" diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 7932f01ebb..c171a5a8f5 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -164,6 +164,9 @@ module ActionView #:nodoc: # # See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details. class Base + module Subclasses + end + include Helpers, Rendering, Partials, ::ERB::Util extend ActiveSupport::Memoizable @@ -195,7 +198,9 @@ module ActionView #:nodoc: attr_internal :request, :layout - delegate :controller_path, :to => :controller, :allow_nil => true + def controller_path + @controller_path ||= controller && controller.controller_path + end delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers, :flash, :action_name, :controller_name, :to => :controller @@ -210,30 +215,35 @@ module ActionView #:nodoc: ActionView::PathSet.new(Array(value)) end + extlib_inheritable_accessor :helpers attr_reader :helpers - class ProxyModule < Module - def initialize(receiver) - @receiver = receiver - end + def self.for_controller(controller) + @views ||= {} - def include(*args) - super(*args) - @receiver.extend(*args) - end - end + # TODO: Decouple this so helpers are a separate concern in AV just like + # they are in AC. + if controller.class.respond_to?(:_helper_serial) + klass = @views[controller.class._helper_serial] ||= Class.new(self) do + Subclasses.const_set(controller.class.name.gsub(/::/, '__'), self) - def self.for_controller(controller) - new(controller.class.view_paths, {}, controller).tap do |view| - view.helpers.include(controller._helpers) if controller.respond_to?(:_helpers) + if controller.respond_to?(:_helpers) + include controller._helpers + self.helpers = controller._helpers + end + end + else + klass = self end + + klass.new(controller.class.view_paths, {}, controller) end def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil, formats = nil)#:nodoc: @formats = formats || [:html] @assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) } @controller = controller - @helpers = ProxyModule.new(self) + @helpers = self.class.helpers || Module.new @_content_for = Hash.new {|h,k| h[k] = "" } self.view_paths = view_paths end @@ -248,7 +258,7 @@ module ActionView #:nodoc: def with_template(current_template) _evaluate_assigns_and_ivars last_template, self.template = template, current_template - last_formats, self.formats = formats, [current_template.mime_type.to_sym] + Mime::SET.symbols + last_formats, self.formats = formats, current_template.formats yield ensure self.template, self.formats = last_template, last_formats diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb index dc4497581c..9951e11a37 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -98,7 +98,7 @@ module ActionView options[:schema_date] = "2005" # The Atom spec copyright date end - xml = options[:xml] || eval("xml", block.binding) + xml = options.delete(:xml) || eval("xml", block.binding) xml.instruct! if options[:instruct] options[:instruct].each do |target,attrs| diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index bde600f6ed..81029102b1 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -738,6 +738,7 @@ module ActionView options = options.stringify_keys tag_value = options.delete("value") name_and_id = options.dup + name_and_id["id"] = name_and_id["for"] add_default_name_and_id_for_value(tag_value, name_and_id) options.delete("index") options["for"] ||= name_and_id["id"] @@ -936,6 +937,10 @@ module ActionView @model_name ||= Struct.new(:partial_path).new(name.demodulize.underscore.sub!(/_builder$/, '')) end + def to_model + self + end + def initialize(object_name, object, template, options, proc) @nested_child_index = {} @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 8cb5882aab..4620a52272 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -162,6 +162,60 @@ module ActionView InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options) end + + # Returns <tt><select></tt>, <tt><optgroup></tt> and <tt><option></tt> tags for the collection of existing return values of + # +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will + # be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt> + # or <tt>:include_blank</tt> in the +options+ hash. + # + # Parameters: + # * +object+ - The instance of the class to be used for the select tag + # * +method+ - The attribute of +object+ corresponding to the select tag + # * +collection+ - An array of objects representing the <tt><optgroup></tt> tags. + # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an + # array of child objects representing the <tt><option></tt> tags. + # * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a + # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag. + # * +option_key_method+ - The name of a method which, when called on a child object of a member of + # +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag. + # * +option_value_method+ - The name of a method which, when called on a child object of a member of + # +collection+, returns a value to be used as the contents of its <tt><option></tt> tag. + # + # Example object structure for use with this method: + # class Continent < ActiveRecord::Base + # has_many :countries + # # attribs: id, name + # end + # class Country < ActiveRecord::Base + # belongs_to :continent + # # attribs: id, name, continent_id + # end + # class City < ActiveRecord::Base + # belongs_to :country + # # attribs: id, name, country_id + # end + # + # Sample usage: + # grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name) + # + # Possible output: + # <select name="city[country_id]"> + # <optgroup label="Africa"> + # <option value="1">South Africa</option> + # <option value="3">Somalia</option> + # </optgroup> + # <optgroup label="Europe"> + # <option value="7" selected="selected">Denmark</option> + # <option value="2">Ireland</option> + # </optgroup> + # </select> + # + def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) + InstanceTag.new(object, method, self, options.delete(:object)).to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options) + end + + + # Return select and option tags for the given object and method, using # #time_zone_options_for_select to generate the list of option tags. # @@ -490,6 +544,15 @@ module ActionView ) end + def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options) + html_options = html_options.stringify_keys + add_default_name_and_id(html_options) + value = value(object) + content_tag( + "select", add_options(option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value), options, value), html_options + ) + end + def to_time_zone_select_tag(priority_zones, options, html_options) html_options = html_options.stringify_keys add_default_name_and_id(html_options) @@ -524,6 +587,10 @@ module ActionView @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)) end + def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) + @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, 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, objectify_options(options), @default_options.merge(html_options)) end diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index c3ce4c671e..1d92bcb763 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -33,13 +33,15 @@ module ActionView end # Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt> - # (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "..."). + # (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...") + # for a total length not exceeding <tt>:length</tt>. + # # Pass a <tt>:separator</tt> to truncate +text+ at a natural break. # # ==== Examples # # truncate("Once upon a time in a world far far away") - # # => Once upon a time in a world f... + # # => Once upon a time in a world... # # truncate("Once upon a time in a world far far away", :separator => ' ') # # => Once upon a time in a world... @@ -48,19 +50,19 @@ module ActionView # # => Once upon a... # # truncate("And they found that many people were sleeping better.", :length => 25, "(clipped)") - # # => And they found that many (clipped) + # # => And they found t(clipped) # - # truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 15) - # # => And they found... (continued) + # truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 25) + # # => And they f... (continued) # # You can still use <tt>truncate</tt> with the old API that accepts the # +length+ as its optional second and the +ellipsis+ as its # optional third parameter: # truncate("Once upon a time in a world far far away", 14) - # # => Once upon a time in a world f... + # # => Once upon a... # - # truncate("And they found that many people were sleeping better.", 15, "... (continued)") - # # => And they found... (continued) + # truncate("And they found that many people were sleeping better.", 25, "... (continued)") + # # => And they f... (continued) def truncate(text, *args) options = args.extract_options! unless args.empty? @@ -239,12 +241,20 @@ module ActionView # # textilize("Visit the Rails website "here":http://www.rubyonrails.org/.) # # => "<p>Visit the Rails website <a href="http://www.rubyonrails.org/">here</a>.</p>" - def textilize(text) + # + # textilize("This is worded <strong>strongly</strong>") + # # => "<p>This is worded <strong>strongly</strong></p>" + # + # textilize("This is worded <strong>strongly</strong>", :filter_html) + # # => "<p>This is worded <strong>strongly</strong></p>" + # + def textilize(text, *options) + options ||= [:hard_breaks] + if text.blank? "" else - textilized = RedCloth.new(text, [ :hard_breaks ]) - textilized.hard_breaks = true if textilized.respond_to?(:hard_breaks=) + textilized = RedCloth.new(text, options) textilized.to_html end end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index c5a6d1f084..b07304e361 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -568,7 +568,7 @@ module ActionView when confirm && popup "if (#{confirm_javascript_function(confirm)}) { #{popup_javascript_function(popup)} };return false;" when confirm && method - "if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method)} };return false;" + "if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method, url, href)} };return false;" when confirm "return #{confirm_javascript_function(confirm)};" when method diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 986d3af454..64f08c447d 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -177,71 +177,70 @@ module ActionView @partial_names ||= Hash.new {|h,k| h[k] = ActiveSupport::ConcurrentHash.new } end - def initialize(view_context, options, formats) - object = options[:partial] - - @view, @formats = view_context, formats - @options = options || {} - - if object.is_a?(String) - @path = object - elsif - @object = object - @path = partial_path unless collection - end - - @locals = options[:locals] || {} - @object ||= @options[:object] || @locals[:object] - @layout = options[:layout] + def self.formats + @formats ||= Hash.new {|h,k| h[k] = Hash.new{|h,k| h[k] = Hash.new {|h,k| h[k] = {}}}} end - def render(&block) - template = find if @path + def initialize(view_context, options, block) + partial = options[:partial] - if collection - render_collection(template, &block) - else - render_object(template, &block) - end - end + @view = view_context + @options = options + @locals = options[:locals] || {} + @block = block - def render_template(template, &block) - @options[:_template] = template - @locals[:object] = @locals[template.variable_name] = @object - @locals[@options[:as]] = @object if @options[:as] + # Set up some instance variables to speed up memoizing + @partial_names = self.class.partial_names[@view.controller.class] + @templates = self.class.formats + @format = view_context.formats - content = @view._render_single_template(template, @locals, &block) - return content if block_given? || !@layout - find(@layout).render(@view, @locals) { content } + # Set up the object and path + @object = partial.is_a?(String) ? options[:object] : partial + @path = partial_path(partial) end - def render_object(template, &block) - @object ||= @locals[template.variable_name] - render_template(template, &block) + def render + return render_collection if collection + + template = find_template + render_template(template, @object || @locals[template.variable_name]) end - def render_collection(passed_template = nil, &block) - @options[:_template] = passed_template + def render_collection + # Even if no template is rendered, this will ensure that the MIME type + # for the empty response is the same as the provided template + @options[:_template] = default_template = find_template return nil if collection.blank? if @options.key?(:spacer_template) - spacer = @view.render_partial( - :partial => @options[:spacer_template], :_details => @options[:_details]) + spacer = find_template(@options[:spacer_template]).render(@view, @locals) end - index = 0 + segments = [] - collection.map do |@object| - @path = partial_path - template = passed_template || find + collection.each_with_index do |object, index| + template = default_template || find_template(partial_path(object)) @locals[template.counter_name] = index - index += 1 + segments << render_template(template, object) + end - render_template(template, &block) - end.join(spacer) + segments.join(spacer) end + def render_template(template, object = @object) + @options[:_template] ||= template + + # TODO: is locals[:object] really necessary? + @locals[:object] = @locals[template.variable_name] = object + @locals[@options[:as]] = object if @options[:as] + + content = @view._render_single_template(template, @locals, &@block) + return content if @block || !@options[:layout] + find_template(@options[:layout]).render(@view, @locals) { content } + end + + private def collection @collection ||= if @object.respond_to?(:to_ary) @@ -251,21 +250,22 @@ module ActionView end end - def find(path = @path) - prefix = @view.controller.controller_path unless path.include?(?/) - @view.find(path, {:formats => @view.formats}, prefix, true) + def find_template(path = @path) + return if !path + @templates[path][@view.controller_path][@format][I18n.locale] ||= begin + prefix = @view.controller.controller_path unless path.include?(?/) + @view.find(path, {:formats => @view.formats}, prefix, true) + end end def partial_path(object = @object) - self.class.partial_names[@view.controller.class][object.class] ||= begin - return nil unless object.class.respond_to?(:model_name) + return object if object.is_a?(String) + @partial_names[object.class] ||= begin + return nil unless object.respond_to?(:to_model) - name = object.class.model_name - path = @view.controller_path - if path && path.include?(?/) - File.join(File.dirname(path), name.partial_path) - else - name.partial_path + object.to_model.class.model_name.partial_path.dup.tap do |partial| + path = @view.controller_path + partial.insert(0, "#{File.dirname(path)}/") if path.include?(?/) end end end @@ -279,7 +279,7 @@ module ActionView end def _render_partial(options, &block) #:nodoc: - PartialRenderer.new(self, options, formats).render(&block) + PartialRenderer.new(self, options, block).render end end diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb index 4145045e2d..33d3f79ad3 100644 --- a/actionpack/lib/action_view/template/template.rb +++ b/actionpack/lib/action_view/template/template.rb @@ -7,19 +7,22 @@ require "action_view/template/resolver" module ActionView class Template extend TemplateHandlers - attr_reader :source, :identifier, :handler, :mime_type, :details + attr_reader :source, :identifier, :handler, :mime_type, :formats, :details def initialize(source, identifier, handler, details) @source = source @identifier = identifier @handler = handler @details = details + @method_names = {} format = details.delete(:format) || begin # TODO: Clean this up handler.respond_to?(:default_format) ? handler.default_format.to_sym.to_s : "html" end @mime_type = Mime::Type.lookup_by_extension(format.to_s) + @formats = [format.to_sym] + @formats << :html if format == :js @details[:formats] = Array.wrap(format.to_sym) end @@ -30,12 +33,12 @@ module ActionView # TODO: Figure out how to abstract this def variable_name - identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym + @variable_name ||= identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym end # TODO: Figure out how to abstract this def counter_name - "#{variable_name}_counter".to_sym + @counter_name ||= "#{variable_name}_counter".to_sym end # TODO: kill hax @@ -90,7 +93,8 @@ module ActionView def build_method_name(locals) # TODO: is locals.keys.hash reliably the same? - "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_") + @method_names[locals.keys.hash] ||= + "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_") end end end diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb index 81944ff546..9f12e5e0a8 100644 --- a/actionpack/lib/action_view/template/text.rb +++ b/actionpack/lib/action_view/template/text.rb @@ -15,7 +15,9 @@ module ActionView #:nodoc: def render(*) self end def mime_type() @content_type end - + + def formats() [mime_type] end + def partial?() false end end end |