diff options
Diffstat (limited to 'actionpack/lib/action_view')
18 files changed, 438 insertions, 235 deletions
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index ad59d92086..c65048bfa0 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -158,6 +158,7 @@ module ActionView #:nodoc: # See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details. class Base include ERB::Util + extend ActiveSupport::Memoizable attr_accessor :base_path, :assigns, :template_extension attr_accessor :controller @@ -169,6 +170,7 @@ module ActionView #:nodoc: class << self delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB' + delegate :logger, :to => 'ActionController::Base' end def self.cache_template_loading=(*args) @@ -202,27 +204,28 @@ module ActionView #:nodoc: end include CompiledTemplates - def self.helper_modules #:nodoc: - helpers = [] - Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file| - next unless file =~ /^([a-z][a-z_]*_helper).rb$/ - require "action_view/helpers/#{$1}" - helper_module_name = $1.camelize - if Helpers.const_defined?(helper_module_name) - helpers << Helpers.const_get(helper_module_name) - end - end - return helpers - end - def self.process_view_paths(value) ActionView::PathSet.new(Array(value)) end + attr_reader :helpers + + class ProxyModule < Module + def initialize(receiver) + @receiver = receiver + end + + def include(*args) + super(*args) + @receiver.extend(*args) + end + end + def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc: @assigns = assigns_for_first_render @assigns_added = nil @controller = controller + @helpers = ProxyModule.new(self) self.view_paths = view_paths end @@ -238,7 +241,7 @@ module ActionView #:nodoc: local_assigns ||= {} if options.is_a?(String) - render_file(options, nil, local_assigns) + render(:file => options, :locals => local_assigns) elsif options == :update update_page(&block) elsif options.is_a?(Hash) @@ -246,31 +249,34 @@ module ActionView #:nodoc: if partial_layout = options.delete(:layout) if block_given? - wrap_content_for_layout capture(&block) do + begin + @_proc_for_layout = block concat(render(options.merge(:partial => partial_layout))) + ensure + @_proc_for_layout = nil end else - wrap_content_for_layout render(options) do + begin + original_content_for_layout, @content_for_layout = @content_for_layout, render(options) render(options.merge(:partial => partial_layout)) + ensure + @content_for_layout = original_content_for_layout end end elsif options[:file] - render_file(options[:file], nil, options[:locals]) - elsif options[:partial] && options[:collection] - render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals], options[:as]) + if options[:use_full_path] + ActiveSupport::Deprecation.warn("use_full_path option has been deprecated and has no affect.", caller) + end + + pick_template(options[:file]).render_template(self, options[:locals]) elsif options[:partial] - render_partial(options[:partial], options[:object], options[:locals]) + render_partial(options) elsif options[:inline] - render_inline(options[:inline], options[:locals], options[:type]) + InlineTemplate.new(options[:inline], options[:type]).render(self, options[:locals]) end end end - # Returns true is the file may be rendered implicitly. - def file_public?(template_path)#:nodoc: - template_path.split('/').last[0,1] != '_' - end - # The format to be used when choosing between multiple templates with # the same name but differing formats. See +Request#template_format+ # for more details. @@ -322,7 +328,7 @@ module ActionView #:nodoc: else template = Template.new(template_path, view_paths) - if self.class.warn_cache_misses && logger = ActionController::Base.logger + if self.class.warn_cache_misses && 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 " + @@ -333,47 +339,9 @@ module ActionView #:nodoc: template end end - - extend ActiveSupport::Memoizable memoize :pick_template 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.is_a?(String) && !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 - ensure - @content_for_layout = original_content_for_layout - end - # Evaluate the local assigns and pushes them to the view. def evaluate_assigns unless @assigns_added @@ -392,11 +360,5 @@ module ActionView #:nodoc: controller.response.content_type ||= content_type end end - - def execute(method, local_assigns = {}) - send(method, local_assigns) do |*names| - instance_variable_get "@content_for_#{names.first || 'layout'}" - end - end end end diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb new file mode 100644 index 0000000000..05e1cf990a --- /dev/null +++ b/actionpack/lib/action_view/helpers.rb @@ -0,0 +1,39 @@ +Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file| + next unless file =~ /^([a-z][a-z_]*_helper).rb$/ + require "action_view/helpers/#{$1}" +end + +module ActionView #:nodoc: + module Helpers #:nodoc: + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + include SanitizeHelper::ClassMethods + end + + include ActiveRecordHelper + include AssetTagHelper + include AtomFeedHelper + include BenchmarkHelper + include CacheHelper + include CaptureHelper + include DateHelper + include DebugHelper + include FormCountryHelper + include FormHelper + include FormOptionsHelper + include FormTagHelper + include NumberHelper + include PrototypeHelper + include RecordIdentificationHelper + include RecordTagHelper + include SanitizeHelper + include ScriptaculousHelper + include TagHelper + include TextHelper + include TranslationHelper + include UrlHelper + end +end diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb index fce03ff605..c339e10701 100644 --- a/actionpack/lib/action_view/helpers/active_record_helper.rb +++ b/actionpack/lib/action_view/helpers/active_record_helper.rb @@ -189,15 +189,15 @@ module ActionView end options[:object_name] ||= params.first - I18n.with_options :locale => options[:locale], :scope => [:active_record, :error] do |locale| + I18n.with_options :locale => options[:locale], :scope => [:activerecord, :errors, :template] do |locale| header_message = if options.include?(:header_message) options[:header_message] else object_name = options[:object_name].to_s.gsub('_', ' ') - object_name = I18n.t(object_name, :default => object_name) - locale.t :header_message, :count => count, :object_name => object_name + object_name = I18n.t(object_name, :default => object_name, :scope => [:activerecord, :models], :count => 1) + locale.t :header, :count => count, :model => object_name end - message = options.include?(:message) ? options[:message] : locale.t(:message) + message = options.include?(:message) ? options[:message] : locale.t(:body) error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join contents = '' diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index c2b4f51c9c..623ed1e8df 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -612,7 +612,7 @@ module ActionView end def join_asset_file_contents(paths) - paths.collect { |path| File.read(File.join(ASSETS_DIR, path.split("?").first)) }.join("\n\n") + paths.collect { |path| File.read(asset_file_path(path)) }.join("\n\n") end def write_asset_file_contents(joined_asset_path, asset_paths) @@ -621,10 +621,14 @@ module ActionView # Set mtime to the latest of the combined files to allow for # consistent ETag without a shared filesystem. - mt = asset_paths.map { |p| File.mtime(File.join(ASSETS_DIR, p)) }.max + mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max File.utime(mt, mt, joined_asset_path) end + def asset_file_path(path) + File.join(ASSETS_DIR, path.split('?').first) + end + def collect_asset_files(*path) dir = path.first diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb index ebb1cb34bc..e65d5d1f60 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -17,7 +17,7 @@ module ActionView # # GET /posts.atom # def index # @posts = Post.find(:all) - # + # # respond_to do |format| # format.html # format.atom @@ -29,12 +29,12 @@ module ActionView # atom_feed do |feed| # feed.title("My great blog!") # feed.updated((@posts.first.created_at)) - # + # # for post in @posts # feed.entry(post) do |entry| # entry.title(post.title) # entry.content(post.body, :type => 'html') - # + # # entry.author do |author| # author.name("DHH") # end @@ -47,8 +47,9 @@ module ActionView # * <tt>:language</tt>: Defaults to "en-US". # * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host. # * <tt>:url</tt>: The URL for this feed. Defaults to the current URL. - # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you - # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified, + # * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}" + # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you + # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified, # 2005 is used (as an "I don't care" value). # # Other namespaces can be added to the root element: @@ -81,7 +82,7 @@ module ActionView else options[:schema_date] = "2005" # The Atom spec copyright date end - + xml = options[:xml] || eval("xml", block.binding) xml.instruct! @@ -89,10 +90,10 @@ module ActionView feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)} xml.feed(feed_opts) do - xml.id("tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}") + xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}") xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port)) xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url) - + yield AtomFeedBuilder.new(xml, self, options) end end @@ -102,7 +103,7 @@ module ActionView def initialize(xml, view, feed_options = {}) @xml, @view, @feed_options = xml, view, feed_options end - + # Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used. def updated(date_or_time = nil) @xml.updated((date_or_time || Time.now.utc).xmlschema) @@ -115,9 +116,10 @@ module ActionView # * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists. # * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists. # * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record. + # * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}" def entry(record, options = {}) - @xml.entry do - @xml.id("tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}") + @xml.entry do + @xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}") if options[:published] || (record.respond_to?(:created_at) && record.created_at) @xml.published((options[:published] || record.created_at).xmlschema) diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 9cb2ed9bca..e452b04b31 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -588,9 +588,7 @@ module ActionView private def include_helpers_from_context - @context.extended_by.each do |mod| - extend mod unless mod.name =~ /^ActionView::Helpers/ - end + extend @context.helpers if @context.respond_to?(:helpers) extend GeneratorMethods end diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index c3c03394ee..435ba936e1 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -1,22 +1,27 @@ require 'action_view/helpers/tag_helper' -require 'html/document' + +begin + require 'html/document' +rescue LoadError + html_scanner_path = "#{File.dirname(__FILE__)}/../../action_controller/vendor/html-scanner" + if File.directory?(html_scanner_path) + $:.unshift html_scanner_path + require 'html/document' + end +end module ActionView module Helpers #:nodoc: # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements. # These helper methods extend ActionView making them callable within your template files. module SanitizeHelper - def self.included(base) - base.extend(ClassMethods) - end - # This +sanitize+ helper will html encode all tags and strip all attributes that aren't specifically allowed. # It also strips href/src tags with invalid protocols, like javascript: especially. It does its best to counter any # tricks that hackers may use, like throwing in unicode/ascii/hex values to get past the javascript: filters. Check out # the extensive test suite. # # <%= sanitize @article.body %> - # + # # You can add or remove tags/attributes if you want to customize it a bit. See ActionView::Base for full docs on the # available options. You can add tags/attributes for single uses of +sanitize+ by passing either the <tt>:attributes</tt> or <tt>:tags</tt> options: # @@ -27,27 +32,27 @@ module ActionView # Custom Use (only the mentioned tags and attributes are allowed, nothing else) # # <%= sanitize @article.body, :tags => %w(table tr td), :attributes => %w(id class style) - # + # # Add table tags to the default allowed tags - # + # # Rails::Initializer.run do |config| # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' # end - # + # # Remove tags to the default allowed tags - # + # # Rails::Initializer.run do |config| # config.after_initialize do # ActionView::Base.sanitized_allowed_tags.delete 'div' # end # end - # + # # Change allowed default attributes - # + # # Rails::Initializer.run do |config| # config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style' # end - # + # # Please note that sanitizing user-provided text does not guarantee that the # resulting markup is valid (conforming to a document type) or even well-formed. # The output may still contain e.g. unescaped '<', '>', '&' characters and @@ -62,8 +67,8 @@ module ActionView self.class.white_list_sanitizer.sanitize_css(style) end - # Strips all HTML tags from the +html+, including comments. This uses the - # html-scanner tokenizer and so its HTML parsing ability is limited by + # Strips all HTML tags from the +html+, including comments. This uses the + # html-scanner tokenizer and so its HTML parsing ability is limited by # that of html-scanner. # # ==== Examples @@ -73,10 +78,10 @@ module ActionView # # strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...") # # => Bold no more! See more here... - # + # # strip_tags("<div id='top-bar'>Welcome to my website!</div>") # # => Welcome to my website! - def strip_tags(html) + def strip_tags(html) self.class.full_sanitizer.sanitize(html) end @@ -96,21 +101,48 @@ module ActionView end module ClassMethods #:nodoc: - def self.extended(base) - class << base - attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer - - # we want these to be class methods on ActionView::Base, they'll get mattr_readers for these below. - helper_def = [:sanitized_protocol_separator, :sanitized_uri_attributes, :sanitized_bad_tags, :sanitized_allowed_tags, - :sanitized_allowed_attributes, :sanitized_allowed_css_properties, :sanitized_allowed_css_keywords, - :sanitized_shorthand_css_properties, :sanitized_allowed_protocols, :sanitized_protocol_separator=].collect! do |prop| - prop = prop.to_s - "def #{prop}(#{:value if prop =~ /=$/}) white_list_sanitizer.#{prop.sub /sanitized_/, ''} #{:value if prop =~ /=$/} end" - end.join("\n") - eval helper_def - end - end - + attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer + + def sanitized_protocol_separator + white_list_sanitizer.protocol_separator + end + + def sanitized_uri_attributes + white_list_sanitizer.uri_attributes + end + + def sanitized_bad_tags + white_list_sanitizer.bad_tags + end + + def sanitized_allowed_tags + white_list_sanitizer.allowed_tags + end + + def sanitized_allowed_attributes + white_list_sanitizer.allowed_attributes + end + + def sanitized_allowed_css_properties + white_list_sanitizer.allowed_css_properties + end + + def sanitized_allowed_css_keywords + white_list_sanitizer.allowed_css_keywords + end + + def sanitized_shorthand_css_properties + white_list_sanitizer.shorthand_css_properties + end + + def sanitized_allowed_protocols + white_list_sanitizer.allowed_protocols + end + + def sanitized_protocol_separator=(value) + white_list_sanitizer.protocol_separator = value + end + # Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with # any object that responds to +sanitize+. # diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index f9096d0029..a5d43b90f4 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -1,5 +1,14 @@ require 'action_view/helpers/tag_helper' -require 'html/document' + +begin + require 'html/document' +rescue LoadError + html_scanner_path = "#{File.dirname(__FILE__)}/../../action_controller/vendor/html-scanner" + if File.directory?(html_scanner_path) + $:.unshift html_scanner_path + require 'html/document' + end +end module ActionView module Helpers #:nodoc: @@ -439,8 +448,10 @@ module ActionView # array every time it is called. This can be used for example, to alternate # classes for table rows. You can use named cycles to allow nesting in loops. # Passing a Hash as the last parameter with a <tt>:name</tt> key will create a - # named cycle. You can manually reset a cycle by calling reset_cycle and passing the - # name of the cycle. + # named cycle. The default name for a cycle without a +:name+ key is + # <tt>"default"</tt>. You can manually reset a cycle by calling reset_cycle + # and passing the name of the cycle. The current cycle string can be obtained + # anytime using the current_cycle method. # # ==== Examples # # Alternate CSS classes for even and odd numbers... @@ -487,6 +498,23 @@ module ActionView return cycle.to_s end + # Returns the current cycle string after a cycle has been started. Useful + # for complex table highlighing or any other design need which requires + # the current cycle string in more than one place. + # + # ==== Example + # # Alternate background colors + # @items = [1,2,3,4] + # <% @items.each do |item| %> + # <div style="background-color:<%= cycle("red","white","blue") %>"> + # <span style="background-color:<%= current_cycle %><%= item %></span> + # </div> + # <% end %> + def current_cycle(name = "default") + cycle = get_cycle(name) + cycle.current_value unless cycle.nil? + end + # Resets a cycle so that it starts from the first element the next time # it is called. Pass in +name+ to reset a named cycle. # @@ -523,11 +551,29 @@ module ActionView @index = 0 end + def current_value + @values[previous_index].to_s + end + def to_s value = @values[@index].to_s - @index = (@index + 1) % @values.size + @index = next_index return value end + + private + + def next_index + step_index(1) + end + + def previous_index + step_index(-1) + end + + def step_index(n) + (@index + n) % @values.size + end end private diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index 60ac5c8790..de4c1d7689 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -11,10 +11,12 @@ module ActionView keys = I18n.send :normalize_translation_keys, e.locale, e.key, e.options[:scope] content_tag('span', keys.join(', '), :class => 'translation_missing') end + alias :t :translate def localize(*args) I18n.localize *args end + alias :l :localize end end end
\ No newline at end of file diff --git a/actionpack/lib/action_view/locale/en-US.rb b/actionpack/lib/action_view/locale/en-US.rb deleted file mode 100644 index 2c3676dca8..0000000000 --- a/actionpack/lib/action_view/locale/en-US.rb +++ /dev/null @@ -1,53 +0,0 @@ -I18n.backend.store_translations :'en-US', { - :datetime => { - :distance_in_words => { - :half_a_minute => 'half a minute', - :less_than_x_seconds => ['less than 1 second', 'less than {{count}} seconds'], - :x_seconds => ['1 second', '{{count}} seconds'], - :less_than_x_minutes => ['less than a minute', 'less than {{count}} minutes'], - :x_minutes => ['1 minute', '{{count}} minutes'], - :about_x_hours => ['about 1 hour', 'about {{count}} hours'], - :x_days => ['1 day', '{{count}} days'], - :about_x_months => ['about 1 month', 'about {{count}} months'], - :x_months => ['1 month', '{{count}} months'], - :about_x_years => ['about 1 year', 'about {{count}} year'], - :over_x_years => ['over 1 year', 'over {{count}} years'] - } - }, - :number => { - :format => { - :precision => 3, - :separator => '.', - :delimiter => ',' - }, - :currency => { - :format => { - :unit => '$', - :precision => 2, - :format => '%u%n' - } - }, - :human => { - :format => { - :precision => 1, - :delimiter => '' - } - }, - :percentage => { - :format => { - :delimiter => '' - } - }, - :precision => { - :format => { - :delimiter => '' - } - } - }, - :active_record => { - :error => { - :header_message => ["1 error prohibited this {{object_name}} from being saved", "{{count}} errors prohibited this {{object_name}} from being saved"], - :message => "There were problems with the following fields:" - } - } -} diff --git a/actionpack/lib/action_view/locale/en-US.yml b/actionpack/lib/action_view/locale/en-US.yml new file mode 100644 index 0000000000..818f2f93bd --- /dev/null +++ b/actionpack/lib/action_view/locale/en-US.yml @@ -0,0 +1,91 @@ +"en-US": + number: + # Used in number_with_delimiter() + # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' + format: + # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) + separator: "." + # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three) + delimiter: "," + # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) + precision: 3 + + # Used in number_to_currency() + currency: + format: + # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) + format: "%u%n" + unit: "$" + # These three are to override number.format and are optional + separator: "." + delimiter: "," + precision: 2 + + # Used in number_to_percentage() + percentage: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + # precision: + + # Used in number_to_precision() + precision: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + # precision: + + # Used in number_to_human_size() + human: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + precision: 1 + + # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() + datetime: + distance_in_words: + half_a_minute: "half a minute" + less_than_x_seconds: + one: "less than 1 second" + other: "less than {{count}} seconds" + x_seconds: + one: "1 second" + other: "{{count}} seconds" + less_than_x_minutes: + one: "less than a minute" + other: "less than {{count}} minutes" + x_minutes: + one: "1 minute" + other: "{{count}} minutes" + about_x_hours: + one: "about 1 hour" + other: "about {{count}} hours" + x_days: + one: "1 day" + other: "{{count}} days" + about_x_months: + one: "about 1 month" + other: "about {{count}} months" + x_months: + one: "1 month" + other: "{{count}} months" + about_x_years: + one: "about 1 year" + other: "about {{count}} years" + over_x_years: + one: "over 1 year" + other: "over {{count}} years" + + activerecord: + errors: + template: + header: + one: "1 error prohibited this {{model}} from being saved" + other: "{{count}} errors prohibited this {{model}} from being saved" + # The variable :count is also available + body: "There were problems with the following fields:" + diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb index 894b88534c..443c49b870 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -1,14 +1,15 @@ module ActionView - # There's also a convenience method for rendering sub templates within the current controller that depends on a single object - # (we call this kind of sub templates for partials). It relies on the fact that partials should follow the naming convention of being - # prefixed with an underscore -- as to separate them from regular templates that could be rendered on their own. + # There's also a convenience method for rendering sub templates within the current controller that depends on a + # single object (we call this kind of sub templates for partials). It relies on the fact that partials should + # follow the naming convention of being prefixed with an underscore -- as to separate them from regular + # templates that could be rendered on their own. # # In a template for Advertiser#account: # # <%= render :partial => "account" %> # - # This would render "advertiser/_account.erb" and pass the instance variable @account in as a local variable +account+ to - # the template for display. + # This would render "advertiser/_account.erb" and pass the instance variable @account in as a local variable + # +account+ to the template for display. # # In another template for Advertiser#buy, we could have: # @@ -18,24 +19,24 @@ module ActionView # <%= render :partial => "ad", :locals => { :ad => ad } %> # <% end %> # - # This would first render "advertiser/_account.erb" with @buyer passed in as the local variable +account+, then render - # "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. + # This would first render "advertiser/_account.erb" with @buyer passed in as the local variable +account+, then + # render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. # # == Rendering a collection of partials # - # The example of partial use describes a familiar pattern where a template needs to iterate over an array and render a sub - # template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders - # a partial by the same name as the elements contained within. So the three-lined example in "Using partials" can be rewritten - # with a single line: + # The example of partial use describes a familiar pattern where a template needs to iterate over an array and + # render a sub template for each of the elements. This pattern has been implemented as a single method that + # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined + # example in "Using partials" can be rewritten with a single line: # # <%= render :partial => "ad", :collection => @advertisements %> # - # This will render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. An iteration counter - # will automatically be made available to the template with a name of the form +partial_name_counter+. In the case of the - # example above, the template would be fed +ad_counter+. + # This will render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. An + # iteration counter will automatically be made available to the template with a name of the form + # +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+. # - # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also just keep domain objects, - # like Active Records, in there. + # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also + # just keep domain objects, like Active Records, in there. # # == Rendering shared partials # @@ -47,8 +48,9 @@ module ActionView # # == Rendering partials with layouts # - # Partials can have their own layouts applied to them. These layouts are different than the ones that are specified globally - # for the entire action, but they work in a similar fashion. Imagine a list with two types of users: + # Partials can have their own layouts applied to them. These layouts are different than the ones that are + # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types + # of users: # # <%# app/views/users/index.html.erb &> # Here's the administrator: @@ -68,7 +70,7 @@ module ActionView # # <%# app/views/users/_editor.html.erb &> # <div id="editor"> - # Deadline: $<%= user.deadline %> + # Deadline: <%= user.deadline %> # <%= yield %> # </div> # @@ -82,7 +84,7 @@ module ActionView # # Here's the editor: # <div id="editor"> - # Deadline: $<%= user.deadline %> + # Deadline: <%= user.deadline %> # Name: <%= user.name %> # </div> # @@ -101,42 +103,83 @@ module ActionView # </div> # # As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout. + # + # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass + # an array to layout and treat it as an enumerable. + # + # <%# app/views/users/_user.html.erb &> + # <div class="user"> + # Budget: $<%= user.budget %> + # <%= yield user %> + # </div> + # + # <%# app/views/users/index.html.erb &> + # <% render :layout => @users do |user| %> + # Title: <%= user.title %> + # <% end %> + # + # This will render the layout for each user and yield to the block, passing the user, each time. + # + # You can also yield multiple times in one layout and use block arguments to differentiate the sections. + # + # <%# app/views/users/_user.html.erb &> + # <div class="user"> + # <%= yield user, :header %> + # Budget: $<%= user.budget %> + # <%= yield user, :footer %> + # </div> + # + # <%# app/views/users/index.html.erb &> + # <% render :layout => @users do |user, section| %> + # <%- case section when :header -%> + # Title: <%= user.title %> + # <%- when :footer -%> + # Deadline: <%= user.deadline %> + # <%- end -%> + # <% end %> module Partials extend ActiveSupport::Memoizable private - def render_partial(partial_path, object_assigns = nil, local_assigns = {}) #:nodoc: - local_assigns ||= {} + def render_partial(options = {}) #:nodoc: + local_assigns = options[:locals] || {} - case partial_path + case partial_path = options[:partial] when String, Symbol, NilClass - pick_template(find_partial_path(partial_path)).render_partial(self, object_assigns, local_assigns) + if options.has_key?(:collection) + render_partial_collection(options) + else + _pick_partial_template(partial_path).render_partial(self, options[:object], local_assigns) + end 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)) + local_assigns.merge!(builder_partial_path.to_sym => partial_path) + render_partial(:partial => builder_partial_path, :object => options[:object], :locals => local_assigns) when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope - if partial_path.any? - collection = partial_path - render_partial_collection(nil, collection, nil, local_assigns) - else - "" - end + render_partial_collection(options.except(:partial).merge(:collection => partial_path)) else - render_partial(ActionController::RecordIdentifier.partial_path(partial_path, controller.class.controller_path), partial_path, local_assigns) + object = partial_path + render_partial( + :partial => ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path), + :object => object, + :locals => local_assigns + ) end end - def render_partial_collection(partial_path, collection, partial_spacer_template = nil, local_assigns = {}, as = nil) #:nodoc: - return " " if collection.empty? + def render_partial_collection(options = {}) #:nodoc: + return nil if options[:collection].blank? - local_assigns = local_assigns ? local_assigns.clone : {} - spacer = partial_spacer_template ? render(:partial => partial_spacer_template) : '' + partial = options[:partial] + spacer = options[:spacer_template] ? render(:partial => options[:spacer_template]) : '' + local_assigns = options[:locals] ? options[:locals].clone : {} + as = options[:as] index = 0 - collection.map do |object| - _partial_path ||= partial_path || ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path) - path = find_partial_path(_partial_path) - template = pick_template(path) + options[:collection].map do |object| + _partial_path ||= partial || + ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path) + template = _pick_partial_template(_partial_path) local_assigns[template.counter_name] = index result = template.render_partial(self, object, local_assigns, as) index += 1 @@ -144,15 +187,17 @@ module ActionView end.join(spacer) end - def find_partial_path(partial_path) + def _pick_partial_template(partial_path) #:nodoc: if partial_path.include?('/') - File.join(File.dirname(partial_path), "_#{File.basename(partial_path)}") - elsif respond_to?(:controller) - "#{controller.class.controller_path}/_#{partial_path}" + path = File.join(File.dirname(partial_path), "_#{File.basename(partial_path)}") + elsif controller + path = "#{controller.class.controller_path}/_#{partial_path}" else - "_#{partial_path}" + path = "_#{partial_path}" end + + pick_template(path) end - memoize :find_partial_path + memoize :_pick_partial_template end end diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index d97f963540..d6bf2137af 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -1,9 +1,9 @@ module ActionView #:nodoc: - class PathSet < ActiveSupport::TypedArray #: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 " + + Base.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 " + @@ -15,6 +15,30 @@ module ActionView #:nodoc: end end + def initialize(*args) + super(*args).map! { |obj| self.class.type_cast(obj) } + end + + def <<(obj) + super(self.class.type_cast(obj)) + end + + def concat(array) + super(array.map! { |obj| self.class.type_cast(obj) }) + end + + def insert(index, obj) + super(index, self.class.type_cast(obj)) + end + + def push(*objs) + super(*objs.map { |obj| self.class.type_cast(obj) }) + end + + def unshift(*objs) + super(*objs.map { |obj| self.class.type_cast(obj) }) + end + class Path #:nodoc: def self.eager_load_templates! @eager_load_templates = true diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index 89ac500717..fa45edd436 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -31,7 +31,14 @@ module ActionView view.send(:evaluate_assigns) view.send(:set_controller_content_type, mime_type) if respond_to?(:mime_type) - view.send(:execute, method_name(local_assigns), local_assigns) + + view.send(method_name(local_assigns), local_assigns) do |*names| + if proc = view.instance_variable_get("@_proc_for_layout") + view.capture(*names, &proc) + else + view.instance_variable_get("@content_for_#{names.first || 'layout'}") + end + end end def method_name(local_assigns) @@ -65,7 +72,7 @@ module ActionView end_src begin - logger = ActionController::Base.logger + logger = defined?(ActionController) && Base.logger logger.debug "Compiling template #{render_symbol}" if logger ActionView::Base::CompiledTemplates.module_eval(source, filename, 0) diff --git a/actionpack/lib/action_view/renderable_partial.rb b/actionpack/lib/action_view/renderable_partial.rb index 342850f0f0..5203e57ead 100644 --- a/actionpack/lib/action_view/renderable_partial.rb +++ b/actionpack/lib/action_view/renderable_partial.rb @@ -16,7 +16,11 @@ module ActionView memoize :counter_name def render(view, local_assigns = {}) - ActionController::Base.benchmark("Rendered #{path_without_format_and_extension}", Logger::DEBUG, false) do + if defined? ActionController + ActionController::Base.benchmark("Rendered #{path_without_format_and_extension}", Logger::DEBUG, false) do + super + end + else super end end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 5dc6708431..64597b3d39 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -1,3 +1,5 @@ +require 'action_controller/mime_type' + module ActionView #:nodoc: class Template extend TemplateHandlers diff --git a/actionpack/lib/action_view/template_error.rb b/actionpack/lib/action_view/template_error.rb index 35fc07bdb2..2368662f31 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 + @base_path = template.base_path.to_s @assigns, @source, @original_exception = assigns.dup, template.source, original_exception @file_path = template.filename @backtrace = compute_backtrace diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 1a3c93c283..adbb37fd09 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -25,9 +25,7 @@ module ActionView end end - ActionView::Base.helper_modules.each do |helper_module| - include helper_module - end + include ActionView::Helpers include ActionController::PolymorphicRoutes include ActionController::RecordIdentifier |