actionpack-3.2.16/ 0000755 0001750 0001750 00000000000 12247655372 013260 5 ustar ondrej ondrej actionpack-3.2.16/CHANGELOG.md 0000644 0001750 0001750 00000065046 12247655372 015104 0 ustar ondrej ondrej * Deep Munge the parameters for GET and POST Fixes CVE-2013-6417
* Stop using i18n's built in HTML error handling. Fixes: CVE-2013-4491
* Escape the unit value provided to number_to_currency Fixes CVE-2013-6415
* Only use valid mime type symbols as cache keys CVE-2013-6414
## Rails 3.2.15 (Oct 16, 2013) ##
* Fix `ActionDispatch::RemoteIp::GetIp#calculate_ip` to only check for spoofing
attacks if both `HTTP_CLIENT_IP` and `HTTP_X_FORWARDED_FOR` are set.
Fixes #12410
Backports #10844
*Tamir Duberstein*
* Fix the assert_recognizes test method so that it works when there are
constraints on the querystring.
Issue/Pull Request #9368
Backport #5219
*Brian Hahn*
* Fix to render partial by context(#11605).
*Kassio Borges*
* Fix `ActionDispatch::Assertions::ResponseAssertions#assert_redirected_to`
does not show user-supplied message.
Issue: when `assert_redirected_to` fails due to the response redirect not
matching the expected redirect the user-supplied message (second parameter)
is not shown. This message is only shown if the response is not a redirect.
*Alexey Chernenkov*
## Rails 3.2.14 (Jul 22, 2013) ##
* Merge `:action` from routing scope and assign endpoint if both `:controller`
and `:action` are present. The endpoint assignment only occurs if there is
no `:to` present in the options hash so should only affect routes using the
shorthand syntax (i.e. endpoint is inferred from the the path).
Fixes #9856
*Yves Senn*, *Andrew White*
* Always escape the result of `link_to_unless` method.
Before:
link_to_unless(true, 'Showing', 'github.com')
# => "Showing"
After:
link_to_unless(true, 'Showing', 'github.com')
# => "<b>Showing</b>"
*dtaniwaki*
* Use a case insensitive URI Regexp for #asset_path.
This fix a problem where the same asset path using different case are generating
different URIs.
Before:
image_tag("HTTP://google.com")
# => ""
image_tag("http://google.com")
# => ""
After:
image_tag("HTTP://google.com")
# => ""
image_tag("http://google.com")
# => ""
*David Celis + Rafael Mendonça França*
* Fix explicit names on multiple file fields. If a file field tag has
the multiple option, it is turned into an array field (appending `[]`),
but if an explicit name is passed to `file_field` the `[]` is not
appended.
Fixes #9830.
*Ryan McGeary*
* Fix assets loading performance in 3.2.13.
Issue #8756 uses Sprockets for resolving files that already exist on disk,
for those files their extensions don't need to be rewritten.
Fixes #9803.
*Fred Wu*
* Fix `ActionController#action_missing` not being called.
Fixes #9799.
*Janko Luin*
* `ActionView::Helpers::NumberHelper#number_to_human` returns the number unaltered when
the units hash does not contain the needed key, e.g. when the number provided is less
than the largest key provided.
Examples:
number_to_human(123, units: {}) # => 123
number_to_human(123, units: { thousand: 'k' }) # => 123
Fixes #9269.
Backport #9347.
*Michael Hoffman*
* Include I18n locale fallbacks in view lookup.
Fixes GH#3512.
*Juan Barreneche*
* Fix `ActionDispatch::Request#formats` when the Accept request-header is an
empty string. Fix #7774 [Backport #8977, #9541]
*Soylent + Maxime Réty*
## Rails 3.2.13 (Mar 18, 2013) ##
* Fix incorrectly appended square brackets to a multiple select box
if an explicit name has been given and it already ends with "[]".
Before:
select(:category, [], {}, multiple: true, name: "post[category][]")
# =>
")
end
# Creates a Cycle object whose _to_s_ method cycles through elements of an
# 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 :name key will create a
# named cycle. The default name for a cycle without a +:name+ key is
# "default". 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...
# @items = [1,2,3,4]
#
# <% @items.each do |item| %>
#
">
#
item
#
# <% end %>
#
#
#
# # Cycle CSS classes for rows, and text colors for values within each row
# @items = x = [{:first => 'Robert', :middle => 'Daniel', :last => 'James'},
# {:first => 'Emily', :middle => 'Shannon', :maiden => 'Pike', :last => 'Hicks'},
# {:first => 'June', :middle => 'Dae', :last => 'Jones'}]
# <% @items.each do |item| %>
#
"row_class") -%>">
#
# <% item.values.each do |value| %>
# <%# Create a named cycle "colors" %>
# "colors") -%>">
# <%= value %>
#
# <% end %>
# <% reset_cycle("colors") %>
#
#
# <% end %>
def cycle(first_value, *values)
if (values.last.instance_of? Hash)
params = values.pop
name = params[:name]
else
name = "default"
end
values.unshift(first_value)
cycle = get_cycle(name)
unless cycle && cycle.values == values
cycle = set_cycle(name, Cycle.new(*values))
end
cycle.to_s
end
# Returns the current cycle string after a cycle has been started. Useful
# for complex table highlighting 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| %>
#
">
# <%= item %>
#
# <% end %>
def current_cycle(name = "default")
cycle = get_cycle(name)
cycle.current_value if cycle
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.
#
# ==== Example
# # Alternate CSS classes for even and odd numbers...
# @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]]
#
# <% @items.each do |item| %>
#
">
# <% item.each do |value| %>
# "colors") -%>">
# <%= value %>
#
# <% end %>
#
# <% reset_cycle("colors") %>
#
# <% end %>
#
def reset_cycle(name = "default")
cycle = get_cycle(name)
cycle.reset if cycle
end
class Cycle #:nodoc:
attr_reader :values
def initialize(first_value, *values)
@values = values.unshift(first_value)
reset
end
def reset
@index = 0
end
def current_value
@values[previous_index].to_s
end
def to_s
value = @values[@index].to_s
@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
# The cycle helpers need to store the cycles in a place that is
# guaranteed to be reset every time a page is rendered, so it
# uses an instance variable of ActionView::Base.
def get_cycle(name)
@_cycles = Hash.new unless defined?(@_cycles)
return @_cycles[name]
end
def set_cycle(name, cycle_object)
@_cycles = Hash.new unless defined?(@_cycles)
@_cycles[name] = cycle_object
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/javascript_helper.rb 0000644 0001750 0001750 00000010537 12247655372 024037 0 ustar ondrej ondrej require 'action_view/helpers/tag_helper'
require 'active_support/core_ext/string/encoding'
module ActionView
module Helpers
module JavaScriptHelper
JS_ESCAPE_MAP = {
'\\' => '\\\\',
'' => '<\/',
"\r\n" => '\n',
"\n" => '\n',
"\r" => '\n',
'"' => '\\"',
"'" => "\\'"
}
if "ruby".encoding_aware?
JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = ' '
else
JS_ESCAPE_MAP["\342\200\250"] = ' '
end
# Escapes carriage returns and single and double quotes for JavaScript segments.
#
# Also available through the alias j(). This is particularly helpful in JavaScript responses, like:
#
# $('some_element').replaceWith('<%=j render 'some/element_template' %>');
def escape_javascript(javascript)
if javascript
result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
javascript.html_safe? ? result.html_safe : result
else
''
end
end
alias_method :j, :escape_javascript
# Returns a JavaScript tag with the +content+ inside. Example:
# javascript_tag "alert('All is good')"
#
# Returns:
#
#
# +html_options+ may be a hash of attributes for the \
# tag. Example:
# javascript_tag "alert('All is good')", :defer => 'defer'
# # =>
#
# Instead of passing the content as an argument, you can also use a block
# in which case, you pass your +html_options+ as the first parameter.
# <%= javascript_tag :defer => 'defer' do -%>
# alert('All is good')
# <% end -%>
def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
content =
if block_given?
html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
capture(&block)
else
content_or_options_with_block
end
content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
end
def javascript_cdata_section(content) #:nodoc:
"\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
end
# Returns a button whose +onclick+ handler triggers the passed JavaScript.
#
# The helper receives a name, JavaScript code, and an optional hash of HTML options. The
# name is used as button label and the JavaScript code goes into its +onclick+ attribute.
# If +html_options+ has an :onclick, that one is put before +function+.
#
# button_to_function "Greeting", "alert('Hello world!')", :class => "ok"
# # =>
#
def button_to_function(name, function=nil, html_options={})
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
end
# Returns a link whose +onclick+ handler triggers the passed JavaScript.
#
# The helper receives a name, JavaScript code, and an optional hash of HTML options. The
# name is used as the link text and the JavaScript code goes into the +onclick+ attribute.
# If +html_options+ has an :onclick, that one is put before +function+. Once all
# the JavaScript is set, the helper appends "; return false;".
#
# The +href+ attribute of the tag is set to "#" unless +html_options+ has one.
#
# link_to_function "Greeting", "alert('Hello world!')", :class => "nav_link"
# # => Greeting
#
def link_to_function(name, function, html_options={})
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
href = html_options[:href] || '#'
content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/form_tag_helper.rb 0000644 0001750 0001750 00000100276 12247655372 023467 0 ustar ondrej ondrej require 'cgi'
require 'action_view/helpers/tag_helper'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/module/attribute_accessors'
module ActionView
# = Action View Form Tag Helpers
module Helpers
# Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like
# FormHelper does. Instead, you provide the names and values manually.
#
# NOTE: The HTML options disabled, readonly, and multiple can all be treated as booleans. So specifying
# :disabled => true will give disabled="disabled".
module FormTagHelper
extend ActiveSupport::Concern
include UrlHelper
include TextHelper
mattr_accessor :embed_authenticity_token_in_remote_forms
self.embed_authenticity_token_in_remote_forms = true
# Starts a form tag that points the action to an url configured with url_for_options just like
# ActionController::Base#url_for. The method for the form defaults to POST.
#
# ==== Options
# * :multipart - If set to true, the enctype is set to "multipart/form-data".
# * :method - The method to use when submitting the form, usually either "get" or "post".
# If "put", "delete", or another verb is used, a hidden input with name _method
# is added to simulate the verb over post.
# * :authenticity_token - Authenticity token to use in the form. Use only if you need to
# pass custom authenticity token string, or to not add authenticity_token field at all
# (by passing false). Remote forms may omit the embedded authenticity token
# by setting config.action_view.embed_authenticity_token_in_remote_forms = false.
# This is helpful when you're fragment-caching the form. Remote forms get the
# authenticity from the meta tag, so embedding is unnecessary unless you
# support browsers without JavaScript.
# * A list of parameters to feed to the URL the form will be posted to.
# * :remote - If set to true, will allow the Unobtrusive JavaScript drivers to control the
# submit behavior. By default this behavior is an ajax submit.
#
# ==== Examples
# form_tag('/posts')
# # =>
#
# <%= form_tag('/posts', :remote => true) %>
# # => ")
end
def token_tag(token)
if token == false || !protect_against_forgery?
''
else
token ||= form_authenticity_token
tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)
end
end
# see http://www.w3.org/TR/html4/types.html#type-name
def sanitize_to_id(name)
name.to_s.gsub(']','').gsub(/[^-a-zA-Z0-9:.]/, "_")
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/tag_helper.rb 0000644 0001750 0001750 00000015206 12247655372 022442 0 ustar ondrej ondrej require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
require 'set'
module ActionView
# = Action View Tag Helpers
module Helpers #:nodoc:
# Provides methods to generate HTML tags programmatically when you can't use
# a Builder. By default, they output XHTML compliant tags.
module TagHelper
extend ActiveSupport::Concern
include CaptureHelper
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer
autoplay controls loop selected hidden scoped async
defer reversed ismap seemless muted required
autofocus novalidate formnovalidate open pubdate).to_set
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })
PRE_CONTENT_STRINGS = {
:textarea => "\n"
}
# Returns an empty HTML tag of type +name+ which by default is XHTML
# compliant. Set +open+ to true to create an open tag compatible
# with HTML 4.0 and below. Add HTML attributes by passing an attributes
# hash to +options+. Set +escape+ to false to disable attribute value
# escaping.
#
# ==== Options
# You can use symbols or strings for the attribute names.
#
# Use +true+ with boolean attributes that can render with no value, like
# +disabled+ and +readonly+.
#
# HTML5 data-* attributes can be set with a single +data+ key
# pointing to a hash of sub-attributes.
#
# To play nicely with JavaScript conventions sub-attributes are dasherized.
# For example, a key +user_id+ would render as data-user-id and
# thus accessed as dataset.userId.
#
# Values are encoded to JSON, with the exception of strings and symbols.
# This may come in handy when using jQuery's HTML5-aware .data()
# from 1.4.3.
#
# ==== Examples
# tag("br")
# # =>
#
# tag("br", nil, true)
# # =>
#
# tag("input", :type => 'text', :disabled => true)
# # =>
#
# tag("img", :src => "open & shut.png")
# # =>
#
# tag("img", {:src => "open & shut.png"}, false, false)
# # =>
#
# tag("div", :data => {:name => 'Stephen', :city_state => %w(Chicago IL)})
# # =>
def tag(name, options = nil, open = false, escape = true)
"<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
end
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
# HTML attributes by passing an attributes hash to +options+.
# Instead of passing the content as an argument, you can also use a block
# in which case, you pass your +options+ as the second parameter.
# Set escape to false to disable attribute value escaping.
#
# ==== Options
# The +options+ hash is used with attributes with no value like (disabled and
# readonly), which you can give a value of true in the +options+ hash. You can use
# symbols or strings for the attribute names.
#
# ==== Examples
# content_tag(:p, "Hello world!")
# # =>
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
if block_given?
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
content_tag_string(name, capture(&block), options, escape)
else
content_tag_string(name, content_or_options_with_block, options, escape)
end
end
# Returns a CDATA section with the given +content+. CDATA sections
# are used to escape blocks of text containing characters which would
# otherwise be recognized as markup. CDATA sections begin with the string
# and end with (and may not contain) the string ]]>.
#
# ==== Examples
# cdata_section("")
# # => ]]>
#
# cdata_section(File.read("hello_world.txt"))
# # =>
def cdata_section(content)
"".html_safe
end
# Returns an escaped version of +html+ without affecting existing escaped entities.
#
# ==== Examples
# escape_once("1 < 2 & 3")
# # => "1 < 2 & 3"
#
# escape_once("<< Accept & Checkout")
# # => "<< Accept & Checkout"
def escape_once(html)
ActiveSupport::Multibyte.clean(html.to_s).gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] }
end
private
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{escape ? ERB::Util.h(content) : content}#{name}>".html_safe
end
def tag_options(options, escape = true)
unless options.blank?
attrs = []
options.each_pair do |key, value|
if key.to_s == 'data' && value.is_a?(Hash)
value.each do |k, v|
unless v.is_a?(String) || v.is_a?(Symbol) || v.is_a?(BigDecimal)
v = v.to_json
end
v = ERB::Util.html_escape(v) if escape
attrs << %(data-#{k.to_s.dasherize}="#{v}")
end
elsif BOOLEAN_ATTRIBUTES.include?(key)
attrs << %(#{key}="#{key}") if value
elsif !value.nil?
final_value = value.is_a?(Array) ? value.join(" ") : value
final_value = ERB::Util.html_escape(final_value) if escape
attrs << %(#{key}="#{final_value}")
end
end
" #{attrs.sort * ' '}".html_safe unless attrs.empty?
end
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/form_options_helper.rb 0000644 0001750 0001750 00000102256 12247655372 024407 0 ustar ondrej ondrej require 'cgi'
require 'erb'
require 'action_view/helpers/form_helper'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
module ActionView
# = Action View Form Option Helpers
module Helpers
# Provides a number of methods for turning different kinds of containers into a set of option tags.
# == Options
# The collection_select, select and time_zone_select methods take an options parameter, a hash:
#
# * :include_blank - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
#
# For example,
#
# select("post", "category", Post::CATEGORIES, {:include_blank => true})
#
# could become:
#
#
#
#
#
#
#
# Another common case is a select tag for an belongs_to-associated object.
#
# Example with @post.person_id => 2:
#
# select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {:include_blank => 'None'})
#
# could become:
#
#
#
#
#
#
#
#
# * :prompt - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
#
# Example:
#
# select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {:prompt => 'Select Person'})
#
# could become:
#
#
#
#
#
#
#
#
# Like the other form helpers, +select+ can accept an :index option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
# option to be in the +html_options+ parameter.
#
# Example:
#
# select("album[]", "genre", %w[rap rock country], {}, { :index => nil })
#
# becomes:
#
#
#
#
#
#
#
# * :disabled - can be a single value or an array of values that will be disabled options in the final output.
#
# Example:
#
# select("post", "category", Post::CATEGORIES, {:disabled => 'restricted'})
#
# could become:
#
#
#
#
#
#
#
#
# When used with the collection_select helper, :disabled can also be a Proc that identifies those options that should be disabled.
#
# Example:
#
# collection_select(:post, :category_id, Category.all, :id, :name, {:disabled => lambda{|category| category.archived? }})
#
# If the categories "2008 stuff" and "Christmas" return true when the method archived? is called, this would return:
#
#
#
#
#
#
#
module FormOptionsHelper
# ERB::Util can mask some helpers like textilize. Make sure to include them.
include TextHelper
# Create a select tag and a series of contained option tags for the provided object and method.
# The option currently held by the object will be selected, provided that the object is available.
#
# There are two possible formats for the choices parameter, corresponding to other helpers' output:
# * A flat collection: see options_for_select
# * A nested collection: see grouped_options_for_select
#
# Example with @post.person_id => 1:
# select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
#
# could become:
#
#
#
#
#
#
#
#
# This can be used to provide a default set of options in the standard way: before rendering the create form, a
# new model instance is assigned the default options and bound to @model_name. Usually this model is not saved
# to the database. Instead, a second model object is created when the create request is received.
# This allows the user to submit a form page more than once with the expected results of creating multiple records.
# In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
#
# By default, post.person_id is the selected option. Specify :selected => value to use a different selection
# or :selected => nil to leave all options unselected. Similarly, you can specify values to be disabled in the option
# tags by specifying the :disabled option. This can either be a single value or an array of values to be disabled.
#
# ==== Gotcha
#
# The HTML specification says when +multiple+ parameter passed to select and all options got deselected
# web browsers do not send any value to server. Unfortunately this introduces a gotcha:
# if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
# the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
# any mass-assignment idiom like
#
# @user.update_attributes(params[:user])
#
# wouldn't update roles.
#
# To prevent this the helper generates an auxiliary hidden field before
# every multiple select. The hidden field has the same name as multiple select and blank value.
#
# This way, the client either sends only the hidden field (representing
# the deselected multiple select box), or both fields. Since the HTML specification
# says key/value pairs have to be sent in the same order they appear in the
# form, and parameters extraction gets the last occurrence of any repeated
# key in the query string, that works for ordinary forms.
#
def select(object, method, choices, options = {}, html_options = {})
InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
end
# Returns and 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 :prompt
# or :include_blank in the +options+ hash.
#
# The :value_method and :text_method parameters are methods to be called on each member
# of +collection+. The return values are used as the +value+ attribute and contents of each
# tag, respectively.
#
# Example object structure for use with this method:
# class Post < ActiveRecord::Base
# belongs_to :author
# end
# class Author < ActiveRecord::Base
# has_many :posts
# def name_with_initial
# "#{first_name.first}. #{last_name}"
# end
# end
#
# Sample usage (selecting the associated Author for an instance of Post, @post):
# collection_select(:post, :author_id, Author.all, :id, :name_with_initial, :prompt => true)
#
# If @post.author_id is already 1, this would return:
#
#
#
#
#
#
def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
end
# Returns , and 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 :prompt
# or :include_blank 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 tags.
# * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
# array of child objects representing the 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 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 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 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:
#
#
#
#
#
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.
#
# In addition to the :include_blank option documented above,
# this method also supports a :model option, which defaults
# to ActiveSupport::TimeZone. This may be used by users to specify a
# different time zone model object. (See +time_zone_options_for_select+
# for more information.)
#
# You can also supply an array of ActiveSupport::TimeZone objects
# as +priority_zones+, so that they will be listed above the rest of the
# (long) list. (You can use ActiveSupport::TimeZone.us_zones as a convenience
# for obtaining a list of the US time zones, or a Regexp to select the zones
# of your choice)
#
# Finally, this method supports a :default option, which selects
# a default ActiveSupport::TimeZone if the object's time zone is +nil+.
#
# Examples:
# time_zone_select( "user", "time_zone", nil, :include_blank => true)
#
# time_zone_select( "user", "time_zone", nil, :default => "Pacific Time (US & Canada)" )
#
# time_zone_select( "user", 'time_zone', ActiveSupport::TimeZone.us_zones, :default => "Pacific Time (US & Canada)")
#
# time_zone_select( "user", 'time_zone', [ ActiveSupport::TimeZone['Alaska'], ActiveSupport::TimeZone['Hawaii'] ])
#
# time_zone_select( "user", 'time_zone', /Australia/)
#
# time_zone_select( "user", "time_zone", ActiveSupport::TimeZone.all.sort, :model => ActiveSupport::TimeZone)
def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
InstanceTag.new(object, method, self, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
end
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
# where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
# the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
# become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+
# may also be an array of values to be selected when using a multiple select.
#
# Examples (call, result):
# options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
# \n
#
# options_for_select([ "VISA", "MasterCard" ], "MasterCard")
# \n
#
# options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
# \n
#
# options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
# \n\n
#
# You can optionally provide html attributes as the last element of the array.
#
# Examples:
# options_for_select([ "Denmark", ["USA", {:class => 'bold'}], "Sweden" ], ["USA", "Sweden"])
# \n\n
#
# options_for_select([["Dollar", "$", {:class => "bold"}], ["Kroner", "DKK", {:onclick => "alert('HI');"}]])
# \n
#
# If you wish to specify disabled option tags, set +selected+ to be a hash, with :disabled being either a value
# or array of values to be disabled. In this case, you can use :selected to specify selected option tags.
#
# Examples:
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => "Super Platinum")
# \n\n\n
#
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => ["Advanced", "Super Platinum"])
# \n\n\n
#
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :selected => "Free", :disabled => "Super Platinum")
# \n\n\n
#
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
def options_for_select(container, selected = nil)
return container if String === container
selected, disabled = extract_selected_and_disabled(selected).map do | r |
Array.wrap(r).map { |item| item.to_s }
end
container.map do |element|
html_attributes = option_html_attributes(element)
text, value = option_text_and_value(element).map { |item| item.to_s }
selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
%()
end.join("\n").html_safe
end
# Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning
# the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
# Example:
# options_from_collection_for_select(@people, 'id', 'name')
# This will output the same HTML as if you did this:
#
#
# This is more often than not used inside a #select_tag like this example:
# select_tag 'person', options_from_collection_for_select(@people, 'id', 'name')
#
# If +selected+ is specified as a value or array of values, the element(s) returning a match on +value_method+
# will be selected option tag(s).
#
# If +selected+ is specified as a Proc, those members of the collection that return true for the anonymous
# function are the selected values.
#
# +selected+ can also be a hash, specifying both :selected and/or :disabled values as required.
#
# Be sure to specify the same class as the +value_method+ when specifying selected or disabled options.
# Failure to do this will produce undesired results. Example:
# options_from_collection_for_select(@people, 'id', 'name', '1')
# Will not select a person with the id of 1 because 1 (an Integer) is not the same as '1' (a string)
# options_from_collection_for_select(@people, 'id', 'name', 1)
# should produce the desired results.
def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
options = collection.map do |element|
[element.send(text_method), element.send(value_method)]
end
selected, disabled = extract_selected_and_disabled(selected)
select_deselect = {}
select_deselect[:selected] = extract_values_from_collection(collection, value_method, selected)
select_deselect[:disabled] = extract_values_from_collection(collection, value_method, disabled)
options_for_select(options, select_deselect)
end
# Returns a string of tags, like options_from_collection_for_select, but
# groups them by tags based on the object relationships of the arguments.
#
# Parameters:
# * +collection+ - An array of objects representing the tags.
# * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
# array of child objects representing the 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 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 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 tag.
# * +selected_key+ - A value equal to the +value+ attribute for one of the tags,
# which will have the +selected+ attribute set. Corresponds to the return value of one of the calls
# to +option_key_method+. If +nil+, no selection is made. Can also be a hash if disabled values are
# to be specified.
#
# 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
#
# Sample usage:
# option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3)
#
# Possible output:
#
#
#
# Note: Only the and tags are returned, so you still have to
# wrap the output in an appropriate tag.
def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
collection.map do |group|
group_label_string = eval("group.#{group_label_method}")
"'
end.join.html_safe
end
# Returns a string of tags, like options_for_select, but
# wraps them with tags.
#
# Parameters:
# * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
# label while the second value must be an array of options. The second value can be a
# nested array of text-value pairs. See options_for_select for more info.
# Ex. ["North America",[["United States","US"],["Canada","CA"]]]
# * +selected_key+ - A value equal to the +value+ attribute for one of the tags,
# which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
# as you might have the same option in multiple groups. Each will then get selected="selected".
# * +prompt+ - set to true or a prompt string. When the select element doesn't have a value yet, this
# prepends an option with a generic prompt - "Please select" - or the given prompt string.
#
# Sample usage (Array):
# grouped_options = [
# ['North America',
# [['United States','US'],'Canada']],
# ['Europe',
# ['Denmark','Germany','France']]
# ]
# grouped_options_for_select(grouped_options)
#
# Sample usage (Hash):
# grouped_options = {
# 'North America' => [['United States','US'], 'Canada'],
# 'Europe' => ['Denmark','Germany','France']
# }
# grouped_options_for_select(grouped_options)
#
# Possible output:
#
#
#
# Note: Only the and tags are returned, so you still have to
# wrap the output in an appropriate tag.
def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil)
body = ''
body << content_tag(:option, prompt, { :value => "" }, true) if prompt
grouped_options = grouped_options.sort if grouped_options.is_a?(Hash)
grouped_options.each do |group|
body << content_tag(:optgroup, options_for_select(group[1], selected_key), :label => group[0])
end
body.html_safe
end
# Returns a string of option tags for pretty much any time zone in the
# world. Supply a ActiveSupport::TimeZone name as +selected+ to have it
# marked as the selected option tag. You can also supply an array of
# ActiveSupport::TimeZone objects as +priority_zones+, so that they will
# be listed above the rest of the (long) list. (You can use
# ActiveSupport::TimeZone.us_zones as a convenience for obtaining a list
# of the US time zones, or a Regexp to select the zones of your choice)
#
# The +selected+ parameter must be either +nil+, or a string that names
# a ActiveSupport::TimeZone.
#
# By default, +model+ is the ActiveSupport::TimeZone constant (which can
# be obtained in Active Record as a value object). The only requirement
# is that the +model+ parameter be an object that responds to +all+, and
# returns an array of objects that represent time zones.
#
# NOTE: Only the option tags are returned, you have to wrap this call in
# a regular HTML select tag.
def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone)
zone_options = ""
zones = model.all
convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
if priority_zones
if priority_zones.is_a?(Regexp)
priority_zones = model.all.find_all {|z| z =~ priority_zones}
end
zone_options += options_for_select(convert_zones[priority_zones], selected)
zone_options += "\n"
zones = zones.reject { |z| priority_zones.include?( z ) }
end
zone_options += options_for_select(convert_zones[zones], selected)
zone_options.html_safe
end
private
def option_html_attributes(element)
return "" unless Array === element
html_attributes = []
element.select { |e| Hash === e }.reduce({}, :merge).each do |k, v|
html_attributes << " #{k}=\"#{ERB::Util.html_escape(v.to_s)}\""
end
html_attributes.join
end
def option_text_and_value(option)
# Options are [text, value] pairs or strings used for both.
case
when Array === option
option = option.reject { |e| Hash === e }
[option.first, option.last]
when !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
[option.first, option.last]
else
[option, option]
end
end
def option_value_selected?(value, selected)
if selected.respond_to?(:include?) && !selected.is_a?(String)
selected.include? value
else
value == selected
end
end
def extract_selected_and_disabled(selected)
if selected.is_a?(Proc)
[ selected, nil ]
else
selected = Array.wrap(selected)
options = selected.extract_options!.symbolize_keys
[ options.include?(:selected) ? options[:selected] : selected, options[:disabled] ]
end
end
def extract_values_from_collection(collection, value_method, selected)
if selected.is_a?(Proc)
collection.map do |element|
element.send(value_method) if selected.call(element)
end.compact
else
selected
end
end
end
class InstanceTag #:nodoc:
include FormOptionsHelper
def to_select_tag(choices, options, html_options)
selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
choices = choices.to_a if choices.is_a?(Range)
# Grouped choices look like this:
#
# [nil, []]
# { nil => [] }
#
if !choices.empty? && choices.first.respond_to?(:last) && Array === choices.first.last
option_tags = grouped_options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
else
option_tags = options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
end
select_content_tag(option_tags, options, html_options)
end
def to_collection_select_tag(collection, value_method, text_method, options, html_options)
selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
select_content_tag(
options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => options[:disabled]), options, html_options
)
end
def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
select_content_tag(
option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value(object)), options, html_options
)
end
def to_time_zone_select_tag(priority_zones, options, html_options)
select_content_tag(
time_zone_options_for_select(value(object) || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), options, html_options
)
end
private
def add_options(option_tags, options, value = nil)
if options[:include_blank]
option_tags = content_tag_string('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags
end
if value.blank? && options[:prompt]
prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
option_tags = content_tag_string('option', prompt, :value => '') + "\n" + option_tags
end
option_tags
end
def select_content_tag(option_tags, options, html_options)
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
if html_options["multiple"]
tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
else
select
end
end
end
class FormBuilder
def select(method, choices, options = {}, html_options = {})
@template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
end
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
@template.collection_select(@object_name, method, collection, value_method, text_method, 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
end
end
end
actionpack-3.2.16/lib/action_view/helpers/translation_helper.rb 0000644 0001750 0001750 00000007171 12247655372 024227 0 ustar ondrej ondrej require 'action_view/helpers/tag_helper'
require 'i18n/exceptions'
module ActionView
# = Action View Translation Helpers
module Helpers
module TranslationHelper
# Delegates to I18n#translate but also performs three additional functions.
#
# First, it will ensure that any thrown +MissingTranslation+ messages will be turned
# into inline spans that:
#
# * have a "translation-missing" class set,
# * contain the missing key as a title attribute and
# * a titleized version of the last key segment as a text.
#
# E.g. the value returned for a missing translation key :"blog.post.title" will be
# Title.
# This way your views will display rather reasonable strings but it will still
# be easy to spot missing translations.
#
# Second, it'll scope the key by the current partial if the key starts
# with a period. So if you call translate(".foo") from the
# people/index.html.erb template, you'll actually be calling
# I18n.translate("people.index.foo"). This makes it less repetitive
# to translate many keys within the same partials and gives you a simple framework
# for scoping them consistently. If you don't prepend the key with a period,
# nothing is converted.
#
# Third, it'll mark the translation as safe HTML if the key has the suffix
# "_html" or the last element of the key is the word "html". For example,
# calling translate("footer_html") or translate("footer.html") will return
# a safe HTML string that won't be escaped by other HTML helper methods. This
# naming convention helps to identify translations that include HTML tags so that
# you know what kind of output to expect when you call translate in a template.
def translate(key, options = {})
# If the user has specified rescue_format then pass it all through, otherwise use
# raise and do the work ourselves
options[:raise] = true unless options.key?(:raise) || options.key?(:rescue_format)
if html_safe_translation_key?(key)
html_safe_options = options.dup
options.except(*I18n::RESERVED_KEYS).each do |name, value|
unless name == :count && value.is_a?(Numeric)
html_safe_options[name] = ERB::Util.html_escape(value.to_s)
end
end
translation = I18n.translate(scope_key_by_partial(key), html_safe_options)
translation.respond_to?(:html_safe) ? translation.html_safe : translation
else
I18n.translate(scope_key_by_partial(key), options)
end
rescue I18n::MissingTranslationData => e
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")
end
alias :t :translate
# Delegates to I18n.localize with no additional functionality.
def localize(*args)
I18n.localize(*args)
end
alias :l :localize
private
def scope_key_by_partial(key)
if key.to_s.first == "."
if @virtual_path
@virtual_path.gsub(%r{/_?}, ".") + key.to_s
else
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
end
else
key
end
end
def html_safe_translation_key?(key)
key.to_s =~ /(\b|_|\.)html$/
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/capture_helper.rb 0000644 0001750 0001750 00000016521 12247655372 023333 0 ustar ondrej ondrej require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
module ActionView
# = Action View Capture Helper
module Helpers
# CaptureHelper exposes methods to let you extract generated markup which
# can be used in other parts of a template or layout file.
#
# It provides a method to capture blocks into variables through capture and
# a way to capture a block of markup for use in a layout through content_for.
module CaptureHelper
# The capture method allows you to extract part of a template into a
# variable. You can then use this variable anywhere in your templates or layout.
#
# ==== Examples
# The capture method can be used in ERB templates...
#
# <% @greeting = capture do %>
# Welcome to my shiny new web page! The date and time is
# <%= Time.now %>
# <% end %>
#
# ...and Builder (RXML) templates.
#
# @timestamp = capture do
# "The current timestamp is #{Time.now}."
# end
#
# You can then use that variable anywhere else. For example:
#
#
# <%= @greeting %>
#
# <%= @greeting %>
#
#
def capture(*args)
value = nil
buffer = with_output_buffer { value = yield(*args) }
if string = buffer.presence || value and string.is_a?(String)
ERB::Util.html_escape string
end
end
# Calling content_for stores a block of markup in an identifier for later use.
# You can make subsequent calls to the stored content in other templates, helper modules
# or the layout by passing the identifier as an argument to content_for.
#
# Note: yield can still be used to retrieve the stored content, but calling
# yield doesn't work in helper modules, while content_for does.
#
# ==== Examples
#
# <% content_for :not_authorized do %>
# alert('You are not authorized to do that!')
# <% end %>
#
# You can then use content_for :not_authorized anywhere in your templates.
#
# <%= content_for :not_authorized if current_user.nil? %>
#
# This is equivalent to:
#
# <%= yield :not_authorized if current_user.nil? %>
#
# content_for, however, can also be used in helper modules.
#
# module StorageHelper
# def stored_content
# content_for(:storage) || "Your storage is empty"
# end
# end
#
# This helper works just like normal helpers.
#
# <%= stored_content %>
#
# You can use the yield syntax alongside an existing call to yield in a layout. For example:
#
# <%# This is the layout %>
#
#
# My Website
# <%= yield :script %>
#
#
# <%= yield %>
#
#
#
# And now, we'll create a view that has a content_for call that
# creates the script identifier.
#
# <%# This is our view %>
# Please login!
#
# <% content_for :script do %>
#
# <% end %>
#
# Then, in another view, you could to do something like this:
#
# <%= link_to 'Logout', :action => 'logout', :remote => true %>
#
# <% content_for :script do %>
# <%= javascript_include_tag :defaults %>
# <% end %>
#
# That will place +script+ tags for your default set of JavaScript files on the page;
# this technique is useful if you'll only be using these scripts in a few views.
#
# Note that content_for concatenates the blocks it is given for a particular
# identifier in order. For example:
#
# <% content_for :navigation do %>
#
<%= link_to 'Home', :action => 'index' %>
# <% end %>
#
# <%# Add some other content, or use a different template: %>
#
# <% content_for :navigation do %>
#
<%= link_to 'Login', :action => 'login' %>
# <% end %>
#
# Then, in another template or layout, this code would render both links in order:
#
#
<%= content_for :navigation %>
#
# Lastly, simple content can be passed as a parameter:
#
# <% content_for :script, javascript_include_tag(:defaults) %>
#
# WARNING: content_for is ignored in caches. So you shouldn't use it
# for elements that will be fragment cached.
def content_for(name, content = nil, &block)
if content || block_given?
content = capture(&block) if block_given?
@view_flow.append(name, content) if content
nil
else
@view_flow.get(name)
end
end
# The same as +content_for+ but when used with streaming flushes
# straight back to the layout. In other words, if you want to
# concatenate several times to the same buffer when rendering a given
# template, you should use +content_for+, if not, use +provide+ to tell
# the layout to stop looking for more contents.
def provide(name, content = nil, &block)
content = capture(&block) if block_given?
result = @view_flow.append!(name, content) if content
result unless content
end
# content_for? simply checks whether any content has been captured yet using content_for
# Useful to render parts of your layout differently based on what is in your views.
#
# ==== Examples
#
# Perhaps you will use different css in you layout if no content_for :right_column
#
# <%# This is the layout %>
#
#
# My Website
# <%= yield :script %>
#
#
# <%= yield %>
# <%= yield :right_col %>
#
#
def content_for?(name)
@view_flow.get(name).present?
end
# Use an alternate output buffer for the duration of the block.
# Defaults to a new empty string.
def with_output_buffer(buf = nil) #:nodoc:
unless buf
buf = ActionView::OutputBuffer.new
buf.force_encoding(output_buffer.encoding) if output_buffer.respond_to?(:encoding) && buf.respond_to?(:force_encoding)
end
self.output_buffer, old_buffer = buf, output_buffer
yield
output_buffer
ensure
self.output_buffer = old_buffer
end
# Add the output buffer to the response body and start a new one.
def flush_output_buffer #:nodoc:
if output_buffer && !output_buffer.empty?
response.body_parts << output_buffer
self.output_buffer = output_buffer.respond_to?(:clone_empty) ? output_buffer.clone_empty : output_buffer[0, 0]
nil
end
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/form_helper.rb 0000644 0001750 0001750 00000172171 12247655372 022637 0 ustar ondrej ondrej require 'cgi'
require 'action_view/helpers/date_helper'
require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_tag_helper'
require 'action_view/helpers/active_model_helper'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/module/method_names'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/string/inflections'
module ActionView
# = Action View Form Helpers
module Helpers
# Form helpers are designed to make working with resources much easier
# compared to using vanilla HTML.
#
# Forms for models are created with +form_for+. That method yields a form
# builder that knows the model the form is about. The form builder is thus
# able to generate default values for input fields that correspond to model
# attributes, and also convenient names, IDs, endpoints, etc.
#
# Conventions in the generated field names allow controllers to receive form
# data nicely structured in +params+ with no effort on your side.
#
# For example, to create a new person you typically set up a new instance of
# +Person+ in the PeopleController#new action, @person, and
# pass it to +form_for+:
#
# <%= form_for @person do |f| %>
# <%= f.label :first_name %>:
# <%= f.text_field :first_name %>
#
# <%= f.label :last_name %>:
# <%= f.text_field :last_name %>
#
# <%= f.submit %>
# <% end %>
#
# The HTML generated for this would be (modulus formatting):
#
#
#
# As you see, the HTML reflects knowledge about the resource in several spots,
# like the path the form should be submitted to, or the names of the input fields.
#
# In particular, thanks to the conventions followed in the generated field names, the
# controller gets a nested hash params[:person] with the person attributes
# set in the form. That hash is ready to be passed to Person.create:
#
# if @person = Person.create(params[:person])
# # success
# else
# # error handling
# end
#
# Interestingly, the exact same view code in the previous example can be used to edit
# a person. If @person is an existing record with name "John Smith" and ID 256,
# the code above as is would yield instead:
#
#
#
# Note that the endpoint, default values, and submit button label are tailored for @person.
# That works that way because the involved helpers know whether the resource is a new record or not,
# and generate HTML accordingly.
#
# The controller would receive the form data again in params[:person], ready to be
# passed to Person#update_attributes:
#
# if @person.update_attributes(params[:person])
# # success
# else
# # error handling
# end
#
# That's how you typically work with resources.
module FormHelper
extend ActiveSupport::Concern
include FormTagHelper
include UrlHelper
# Converts the given object to an ActiveModel compliant one.
def convert_to_model(object)
object.respond_to?(:to_model) ? object.to_model : object
end
# Creates a form and a scope around a specific model object that is used
# as a base for questioning about values for the fields.
#
# Rails provides succinct resource-oriented form generation with +form_for+
# like this:
#
# <%= form_for @offer do |f| %>
# <%= f.label :version, 'Version' %>:
# <%= f.text_field :version %>
# <%= f.label :author, 'Author' %>:
# <%= f.text_field :author %>
# <%= f.submit %>
# <% end %>
#
# There, +form_for+ is able to generate the rest of RESTful form
# parameters based on introspection on the record, but to understand what
# it does we need to dig first into the alternative generic usage it is
# based upon.
#
# === Generic form_for
#
# The generic way to call +form_for+ yields a form builder around a
# model:
#
# <%= form_for :person do |f| %>
# First name: <%= f.text_field :first_name %>
# Last name : <%= f.text_field :last_name %>
# Biography : <%= f.text_area :biography %>
# Admin? : <%= f.check_box :admin %>
# <%= f.submit %>
# <% end %>
#
# There, the argument is a symbol or string with the name of the
# object the form is about.
#
# The form builder acts as a regular form helper that somehow carries the
# model. Thus, the idea is that
#
# <%= f.text_field :first_name %>
#
# gets expanded to
#
# <%= text_field :person, :first_name %>
#
# The rightmost argument to +form_for+ is an
# optional hash of options:
#
# * :url - The URL the form is submitted to. It takes the same
# fields you pass to +url_for+ or +link_to+. In particular you may pass
# here a named route directly as well. Defaults to the current action.
# * :namespace - A namespace for your form to ensure uniqueness of
# id attributes on form elements. The namespace attribute will be prefixed
# with underscore on the generated HTML id.
# * :html - Optional HTML attributes for the form tag.
#
# Also note that +form_for+ doesn't create an exclusive scope. It's still
# possible to use both the stand-alone FormHelper methods and methods
# from FormTagHelper. For example:
#
# <%= form_for @person do |f| %>
# First name: <%= f.text_field :first_name %>
# Last name : <%= f.text_field :last_name %>
# Biography : <%= text_area :person, :biography %>
# Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
# <%= f.submit %>
# <% end %>
#
# This also works for the methods in FormOptionHelper and DateHelper that
# are designed to work with an object as base, like
# FormOptionHelper#collection_select and DateHelper#datetime_select.
#
# === Resource-oriented style
#
# As we said above, in addition to manually configuring the +form_for+
# call, you can rely on automated resource identification, which will use
# the conventions and named routes of that approach. This is the
# preferred way to use +form_for+ nowadays.
#
# For example, if @post is an existing record you want to edit
#
# <%= form_for @post do |f| %>
# ...
# <% end %>
#
# is equivalent to something like:
#
# <%= form_for @post, :as => :post, :url => post_path(@post), :method => :put, :html => { :class => "edit_post", :id => "edit_post_45" } do |f| %>
# ...
# <% end %>
#
# And for new records
#
# <%= form_for(Post.new) do |f| %>
# ...
# <% end %>
#
# is equivalent to something like:
#
# <%= form_for @post, :as => :post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
# ...
# <% end %>
#
# You can also overwrite the individual conventions, like this:
#
# <%= form_for(@post, :url => super_posts_path) do |f| %>
# ...
# <% end %>
#
# You can also set the answer format, like this:
#
# <%= form_for(@post, :format => :json) do |f| %>
# ...
# <% end %>
#
# If you have an object that needs to be represented as a different
# parameter, like a Person that acts as a Client:
#
# <%= form_for(@person, :as => :client) do |f| %>
# ...
# <% end %>
#
# For namespaced routes, like +admin_post_url+:
#
# <%= form_for([:admin, @post]) do |f| %>
# ...
# <% end %>
#
# If your resource has associations defined, for example, you want to add comments
# to the document given that the routes are set correctly:
#
# <%= form_for([@document, @comment]) do |f| %>
# ...
# <% end %>
#
# Where @document = Document.find(params[:id]) and
# @comment = Comment.new.
#
# === Setting the method
#
# You can force the form to use the full array of HTTP verbs by setting
#
# :method => (:get|:post|:put|:delete)
#
# in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the
# form will be set to POST and a hidden input called _method will carry the intended verb for the server
# to interpret.
#
# === Unobtrusive JavaScript
#
# Specifying:
#
# :remote => true
#
# in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
# behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular
# POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor.
# Even though it's using JavaScript to serialize the form elements, the form submission will work just like
# a regular submission as viewed by the receiving side (all elements available in params).
#
# Example:
#
# <%= form_for(@post, :remote => true) do |f| %>
# ...
# <% end %>
#
# The HTML generated for this would be:
#
#
#
# === Removing hidden model id's
#
# The form_for method automatically includes the model id as a hidden field in the form.
# This is used to maintain the correlation between the form data and its associated model.
# Some ORM systems do not use IDs on nested models so in this case you want to be able
# to disable the hidden id.
#
# In the following example the Post model has many Comments stored within it in a NoSQL database,
# thus there is no primary key for comments.
#
# Example:
#
# <%= form_for(@post) do |f| %>
# <% f.fields_for(:comments, :include_id => false) do |cf| %>
# ...
# <% end %>
# <% end %>
#
# === Customized form builders
#
# You can also build forms using a customized FormBuilder class. Subclass
# FormBuilder and override or define some more helpers, then use your
# custom builder. For example, let's say you made a helper to
# automatically add labels to form inputs.
#
# <%= form_for @person, :url => { :action => "create" }, :builder => LabellingFormBuilder do |f| %>
# <%= f.text_field :first_name %>
# <%= f.text_field :last_name %>
# <%= f.text_area :biography %>
# <%= f.check_box :admin %>
# <%= f.submit %>
# <% end %>
#
# In this case, if you use this:
#
# <%= render f %>
#
# The rendered template is people/_labelling_form and the local
# variable referencing the form builder is called
# labelling_form.
#
# The custom FormBuilder class is automatically merged with the options
# of a nested fields_for call, unless it's explicitly set.
#
# In many cases you will want to wrap the above in another helper, so you
# could do something like the following:
#
# def labelled_form_for(record_or_name_or_array, *args, &block)
# options = args.extract_options!
# form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &block)
# end
#
# If you don't need to attach a form to a model instance, then check out
# FormTagHelper#form_tag.
#
# === Form to external resources
#
# When you build forms to external resources sometimes you need to set an authenticity token or just render a form
# without it, for example when you submit data to a payment gateway number and types of fields could be limited.
#
# To set an authenticity token you need to pass an :authenticity_token parameter
#
# <%= form_for @invoice, :url => external_url, :authenticity_token => 'external_token' do |f|
# ...
# <% end %>
#
# If you don't want to an authenticity token field be rendered at all just pass false:
#
# <%= form_for @invoice, :url => external_url, :authenticity_token => false do |f|
# ...
# <% end %>
def form_for(record, options = {}, &block)
raise ArgumentError, "Missing block" unless block_given?
options[:html] ||= {}
case record
when String, Symbol
object_name = record
object = nil
else
object = record.is_a?(Array) ? record.last : record
object_name = options[:as] || ActiveModel::Naming.param_key(object)
apply_form_for_options!(record, options)
end
options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote)
options[:html][:method] = options.delete(:method) if options.has_key?(:method)
options[:html][:authenticity_token] = options.delete(:authenticity_token)
builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &block)
output = capture(builder, &block)
default_options = builder.multipart? ? { :multipart => true } : {}
form_tag(options.delete(:url) || {}, default_options.merge!(options.delete(:html))) { output }
end
def apply_form_for_options!(object_or_array, options) #:nodoc:
object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
object = convert_to_model(object)
as = options[:as]
action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post]
options[:html].reverse_merge!(
:class => as ? "#{action}_#{as}" : dom_class(object, action),
:id => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
:method => method
)
options[:url] ||= polymorphic_path(object_or_array, :format => options.delete(:format))
end
private :apply_form_for_options!
# Creates a scope around a specific model object like form_for, but
# doesn't create the form tags themselves. This makes fields_for suitable
# for specifying additional model objects in the same form.
#
# === Generic Examples
#
# <%= form_for @person do |person_form| %>
# First name: <%= person_form.text_field :first_name %>
# Last name : <%= person_form.text_field :last_name %>
#
# <%= fields_for @person.permission do |permission_fields| %>
# Admin? : <%= permission_fields.check_box :admin %>
# <% end %>
#
# <%= f.submit %>
# <% end %>
#
# ...or if you have an object that needs to be represented as a different
# parameter, like a Client that acts as a Person:
#
# <%= fields_for :person, @client do |permission_fields| %>
# Admin?: <%= permission_fields.check_box :admin %>
# <% end %>
#
# ...or if you don't have an object, just a name of the parameter:
#
# <%= fields_for :person do |permission_fields| %>
# Admin?: <%= permission_fields.check_box :admin %>
# <% end %>
#
# Note: This also works for the methods in FormOptionHelper and
# DateHelper that are designed to work with an object as base, like
# FormOptionHelper#collection_select and DateHelper#datetime_select.
#
# === Nested Attributes Examples
#
# When the object belonging to the current scope has a nested attribute
# writer for a certain attribute, fields_for will yield a new scope
# for that attribute. This allows you to create forms that set or change
# the attributes of a parent object and its associations in one go.
#
# Nested attribute writers are normal setter methods named after an
# association. The most common way of defining these writers is either
# with +accepts_nested_attributes_for+ in a model definition or by
# defining a method with the proper name. For example: the attribute
# writer for the association :address is called
# address_attributes=.
#
# Whether a one-to-one or one-to-many style form builder will be yielded
# depends on whether the normal reader method returns a _single_ object
# or an _array_ of objects.
#
# ==== One-to-one
#
# Consider a Person class which returns a _single_ Address from the
# address reader method and responds to the
# address_attributes= writer method:
#
# class Person
# def address
# @address
# end
#
# def address_attributes=(attributes)
# # Process the attributes hash
# end
# end
#
# This model can now be used with a nested fields_for, like so:
#
# <%= form_for @person do |person_form| %>
# ...
# <%= person_form.fields_for :address do |address_fields| %>
# Street : <%= address_fields.text_field :street %>
# Zip code: <%= address_fields.text_field :zip_code %>
# <% end %>
# ...
# <% end %>
#
# When address is already an association on a Person you can use
# +accepts_nested_attributes_for+ to define the writer method for you:
#
# class Person < ActiveRecord::Base
# has_one :address
# accepts_nested_attributes_for :address
# end
#
# If you want to destroy the associated model through the form, you have
# to enable it first using the :allow_destroy option for
# +accepts_nested_attributes_for+:
#
# class Person < ActiveRecord::Base
# has_one :address
# accepts_nested_attributes_for :address, :allow_destroy => true
# end
#
# Now, when you use a form element with the _destroy parameter,
# with a value that evaluates to +true+, you will destroy the associated
# model (eg. 1, '1', true, or 'true'):
#
# <%= form_for @person do |person_form| %>
# ...
# <%= person_form.fields_for :address do |address_fields| %>
# ...
# Delete: <%= address_fields.check_box :_destroy %>
# <% end %>
# ...
# <% end %>
#
# ==== One-to-many
#
# Consider a Person class which returns an _array_ of Project instances
# from the projects reader method and responds to the
# projects_attributes= writer method:
#
# class Person
# def projects
# [@project1, @project2]
# end
#
# def projects_attributes=(attributes)
# # Process the attributes hash
# end
# end
#
# Note that the projects_attributes= writer method is in fact
# required for fields_for to correctly identify :projects as a
# collection, and the correct indices to be set in the form markup.
#
# When projects is already an association on Person you can use
# +accepts_nested_attributes_for+ to define the writer method for you:
#
# class Person < ActiveRecord::Base
# has_many :projects
# accepts_nested_attributes_for :projects
# end
#
# This model can now be used with a nested fields_for. The block given to
# the nested fields_for call will be repeated for each instance in the
# collection:
#
# <%= form_for @person do |person_form| %>
# ...
# <%= person_form.fields_for :projects do |project_fields| %>
# <% if project_fields.object.active? %>
# Name: <%= project_fields.text_field :name %>
# <% end %>
# <% end %>
# ...
# <% end %>
#
# It's also possible to specify the instance to be used:
#
# <%= form_for @person do |person_form| %>
# ...
# <% @person.projects.each do |project| %>
# <% if project.active? %>
# <%= person_form.fields_for :projects, project do |project_fields| %>
# Name: <%= project_fields.text_field :name %>
# <% end %>
# <% end %>
# <% end %>
# ...
# <% end %>
#
# Or a collection to be used:
#
# <%= form_for @person do |person_form| %>
# ...
# <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
# Name: <%= project_fields.text_field :name %>
# <% end %>
# ...
# <% end %>
#
# When projects is already an association on Person you can use
# +accepts_nested_attributes_for+ to define the writer method for you:
#
# class Person < ActiveRecord::Base
# has_many :projects
# accepts_nested_attributes_for :projects
# end
#
# If you want to destroy any of the associated models through the
# form, you have to enable it first using the :allow_destroy
# option for +accepts_nested_attributes_for+:
#
# class Person < ActiveRecord::Base
# has_many :projects
# accepts_nested_attributes_for :projects, :allow_destroy => true
# end
#
# This will allow you to specify which models to destroy in the
# attributes hash by adding a form element for the _destroy
# parameter with a value that evaluates to +true+
# (eg. 1, '1', true, or 'true'):
#
# <%= form_for @person do |person_form| %>
# ...
# <%= person_form.fields_for :projects do |project_fields| %>
# Delete: <%= project_fields.check_box :_destroy %>
# <% end %>
# ...
# <% end %>
def fields_for(record_name, record_object = nil, options = {}, &block)
builder = instantiate_builder(record_name, record_object, options, &block)
output = capture(builder, &block)
output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id?
output
end
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
# is found in the current I18n locale (through helpers.label..) or you specify it explicitly.
# Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
# onto the HTML as an HTML element attribute as in the example shown, except for the :value option, which is designed to
# target labels for radio_button tags (where the value is used in the ID of the input tag).
#
# ==== Examples
# label(:post, :title)
# # =>
#
# You can localize your labels based on model and attribute names.
# For example you can define the following in your locale (e.g. en.yml)
#
# helpers:
# label:
# post:
# body: "Write your entire text here"
#
# Which then will result in
#
# label(:post, :body)
# # =>
#
# Localization can also be based purely on the translation of the attribute-name
# (if you are using ActiveRecord):
#
# activerecord:
# attributes:
# post:
# cost: "Total cost"
#
# label(:post, :cost)
# # =>
#
# label(:post, :title, "A short title")
# # =>
#
# label(:post, :title, "A short title", :class => "title_label")
# # =>
#
# label(:post, :privacy, "Public Post", :value => "public")
# # =>
#
# label(:post, :terms) do
# 'Accept Terms.'.html_safe
# end
def label(object_name, method, content_or_options = nil, options = nil, &block)
options ||= {}
content_is_options = content_or_options.is_a?(Hash)
if content_is_options || block_given?
options.merge!(content_or_options) if content_is_options
text = nil
else
text = content_or_options
end
InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options, &block)
end
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
# shown.
#
# ==== Examples
# text_field(:post, :title, :size => 20)
# # =>
#
# text_field(:post, :title, :class => "create_input")
# # =>
#
# text_field(:session, :user, :onchange => "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
# # =>
#
# text_field(:snippet, :code, :size => 20, :class => 'code_input')
# # =>
#
def text_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
end
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
# shown.
#
# ==== Examples
# password_field(:login, :pass, :size => 20)
# # =>
#
# password_field(:account, :secret, :class => "form_input", :value => @account.secret)
# # =>
#
# password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
# # =>
#
# password_field(:account, :pin, :size => 20, :class => 'form_input')
# # =>
#
def password_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", { :value => nil }.merge!(options))
end
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
# shown.
#
# ==== Examples
# hidden_field(:signup, :pass_confirm)
# # =>
#
# hidden_field(:post, :tag_list)
# # =>
#
# hidden_field(:user, :token)
# # =>
def hidden_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
end
# Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
# shown.
#
# Using this method inside a +form_for+ block will set the enclosing form's encoding to multipart/form-data.
#
# ==== Examples
# file_field(:user, :avatar)
# # =>
#
# file_field(:post, :attached, :accept => 'text/html')
# # =>
#
# file_field(:attachment, :file, :class => 'file_input')
# # =>
#
def file_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options.update({:size => nil}))
end
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
# on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
# hash with +options+.
#
# ==== Examples
# text_area(:post, :body, :cols => 20, :rows => 40)
# # =>
#
# text_area(:comment, :text, :size => "20x30")
# # =>
#
# text_area(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input')
# # =>
#
# text_area(:entry, :body, :size => "20x20", :disabled => 'disabled')
# # =>
def text_area(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
end
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
# It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
# Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
# while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
#
# ==== Gotcha
#
# The HTML specification says unchecked check boxes are not successful, and
# thus web browsers do not send them. Unfortunately this introduces a gotcha:
# if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
# invoice the user unchecks its check box, no +paid+ parameter is sent. So,
# any mass-assignment idiom like
#
# @invoice.update_attributes(params[:invoice])
#
# wouldn't update the flag.
#
# To prevent this the helper generates an auxiliary hidden field before
# the very check box. The hidden field has the same name and its
# attributes mimic an unchecked check box.
#
# This way, the client either sends only the hidden field (representing
# the check box is unchecked), or both fields. Since the HTML specification
# says key/value pairs have to be sent in the same order they appear in the
# form, and parameters extraction gets the last occurrence of any repeated
# key in the query string, that works for ordinary forms.
#
# Unfortunately that workaround does not work when the check box goes
# within an array-like parameter, as in
#
# <%= fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
# <%= form.check_box :paid %>
# ...
# <% end %>
#
# because parameter name repetition is precisely what Rails seeks to distinguish
# the elements of the array. For each item with a checked check box you
# get an extra ghost item with only that attribute, assigned to "0".
#
# In that case it is preferable to either use +check_box_tag+ or to use
# hashes instead of arrays.
#
# ==== Examples
# # Let's say that @post.validated? is 1:
# check_box("post", "validated")
# # =>
# #
#
# # Let's say that @puppy.gooddog is "no":
# check_box("puppy", "gooddog", {}, "yes", "no")
# # =>
# #
#
# check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
# # =>
# #
#
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
end
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
# radio button will be checked.
#
# To force the radio button to be checked pass :checked => true in the
# +options+ hash. You may pass HTML options there as well.
#
# ==== Examples
# # Let's say that @post.category returns "rails":
# radio_button("post", "category", "rails")
# radio_button("post", "category", "java")
# # =>
# #
#
# radio_button("user", "receive_newsletter", "yes")
# radio_button("user", "receive_newsletter", "no")
# # =>
# #
def radio_button(object_name, method, tag_value, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
end
# Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
# some browsers.
#
# ==== Examples
#
# search_field(:user, :name)
# # =>
# search_field(:user, :name, :autosave => false)
# # =>
# search_field(:user, :name, :results => 3)
# # =>
# # Assume request.host returns "www.example.com"
# search_field(:user, :name, :autosave => true)
# # =>
# search_field(:user, :name, :onsearch => true)
# # =>
# search_field(:user, :name, :autosave => false, :onsearch => true)
# # =>
# search_field(:user, :name, :autosave => true, :onsearch => true)
# # =>
#
def search_field(object_name, method, options = {})
options = options.stringify_keys
if options["autosave"]
if options["autosave"] == true
options["autosave"] = request.host.split(".").reverse.join(".")
end
options["results"] ||= 10
end
if options["onsearch"]
options["incremental"] = true unless options.has_key?("incremental")
end
InstanceTag.new(object_name, method, self, options.delete("object")).to_input_field_tag("search", options)
end
# Returns a text_field of type "tel".
#
# telephone_field("user", "phone")
# # =>
#
def telephone_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("tel", options)
end
alias phone_field telephone_field
# Returns a text_field of type "url".
#
# url_field("user", "homepage")
# # =>
#
def url_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("url", options)
end
# Returns a text_field of type "email".
#
# email_field("user", "address")
# # =>
#
def email_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("email", options)
end
# Returns an input tag of type "number".
#
# ==== Options
# * Accepts same options as number_field_tag
def number_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("number", options)
end
# Returns an input tag of type "range".
#
# ==== Options
# * Accepts same options as range_field_tag
def range_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("range", options)
end
private
def instantiate_builder(record_name, record_object, options, &block)
case record_name
when String, Symbol
object = record_object
object_name = record_name
else
object = record_name
object_name = ActiveModel::Naming.param_key(object)
end
builder = options[:builder] || default_form_builder
builder.new(object_name, object, self, options, block)
end
def default_form_builder
builder = ActionView::Base.default_form_builder
builder.respond_to?(:constantize) ? builder.constantize : builder
end
end
class InstanceTag
include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
attr_reader :object, :method_name, :object_name
DEFAULT_FIELD_OPTIONS = { "size" => 30 }
DEFAULT_RADIO_OPTIONS = { }
DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }
def initialize(object_name, method_name, template_object, object = nil)
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
@template_object = template_object
@object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
@object = retrieve_object(object)
@auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
end
def to_label_tag(text = nil, options = {}, &block)
options = options.stringify_keys
tag_value = options.delete("value")
name_and_id = options.dup
if name_and_id["for"]
name_and_id["id"] = name_and_id["for"]
else
name_and_id.delete("id")
end
add_default_name_and_id_for_value(tag_value, name_and_id)
options.delete("index")
options.delete("namespace")
options["for"] ||= name_and_id["id"]
if block_given?
@template_object.label_tag(name_and_id["id"], options, &block)
else
content = if text.blank?
object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name
if object.respond_to?(:to_model)
key = object.class.model_name.i18n_key
i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
end
i18n_default ||= ""
I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
else
text.to_s
end
content ||= if object && object.class.respond_to?(:human_attribute_name)
object.class.human_attribute_name(method_name)
end
content ||= method_name.humanize
label_tag(name_and_id["id"], content, options)
end
end
def to_input_field_tag(field_type, options = {})
options = options.stringify_keys
options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
options = DEFAULT_FIELD_OPTIONS.merge(options)
if field_type == "hidden"
options.delete("size")
end
options["type"] ||= field_type
options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
options["value"] &&= ERB::Util.html_escape(options["value"])
add_default_name_and_id(options)
tag("input", options)
end
def to_number_field_tag(field_type, options = {})
options = options.stringify_keys
options['size'] ||= nil
if range = options.delete("in") || options.delete("within")
options.update("min" => range.min, "max" => range.max)
end
to_input_field_tag(field_type, options)
end
def to_radio_button_tag(tag_value, options = {})
options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
options["type"] = "radio"
options["value"] = tag_value
if options.has_key?("checked")
cv = options.delete "checked"
checked = cv == true || cv == "checked"
else
checked = self.class.radio_button_checked?(value(object), tag_value)
end
options["checked"] = "checked" if checked
add_default_name_and_id_for_value(tag_value, options)
tag("input", options)
end
def to_text_area_tag(options = {})
options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
add_default_name_and_id(options)
if size = options.delete("size")
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
end
content_tag("textarea", options.delete('value') || value_before_type_cast(object), options)
end
def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
options = options.stringify_keys
options["type"] = "checkbox"
options["value"] = checked_value
if options.has_key?("checked")
cv = options.delete "checked"
checked = cv == true || cv == "checked"
else
checked = self.class.check_box_checked?(value(object), checked_value)
end
options["checked"] = "checked" if checked
if options["multiple"]
add_default_name_and_id_for_value(checked_value, options)
options.delete("multiple")
else
add_default_name_and_id(options)
end
hidden = unchecked_value ? tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value, "disabled" => options["disabled"]) : ""
checkbox = tag("input", options)
(hidden + checkbox).html_safe
end
def to_boolean_select_tag(options = {})
options = options.stringify_keys
add_default_name_and_id(options)
value = value(object)
tag_text = ""
end
def to_content_tag(tag_name, options = {})
content_tag(tag_name, value(object), options)
end
def retrieve_object(object)
if object
object
elsif @template_object.instance_variable_defined?("@#{@object_name}")
@template_object.instance_variable_get("@#{@object_name}")
end
rescue NameError
# As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
nil
end
def retrieve_autoindex(pre_match)
object = self.object || @template_object.instance_variable_get("@#{pre_match}")
if object && object.respond_to?(:to_param)
object.to_param
else
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
end
end
def value(object)
self.class.value(object, @method_name)
end
def value_before_type_cast(object)
self.class.value_before_type_cast(object, @method_name)
end
class << self
def value(object, method_name)
object.send method_name if object
end
def value_before_type_cast(object, method_name)
unless object.nil?
object.respond_to?(method_name + "_before_type_cast") ?
object.send(method_name + "_before_type_cast") :
object.send(method_name)
end
end
def check_box_checked?(value, checked_value)
case value
when TrueClass, FalseClass
value
when NilClass
false
when Integer
value != 0
when String
value == checked_value
when Array
value.include?(checked_value)
else
value.to_i != 0
end
end
def radio_button_checked?(value, checked_value)
value.to_s == checked_value.to_s
end
end
private
def add_default_name_and_id_for_value(tag_value, options)
unless tag_value.nil?
pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
specified_id = options["id"]
add_default_name_and_id(options)
options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present?
else
add_default_name_and_id(options)
end
end
def add_default_name_and_id(options)
if options.has_key?("index")
options["name"] ||= tag_name_with_index(options["index"], options["multiple"])
options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
options.delete("index")
elsif defined?(@auto_index)
options["name"] ||= tag_name_with_index(@auto_index, options["multiple"])
options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
else
options["name"] ||= tag_name(options["multiple"])
options["id"] = options.fetch("id"){ tag_id }
end
options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
end
def tag_name(multiple = false)
"#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
end
def tag_name_with_index(index, multiple = false)
"#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
end
def tag_id
"#{sanitized_object_name}_#{sanitized_method_name}"
end
def tag_id_with_index(index)
"#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
end
def sanitized_object_name
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
end
def sanitized_method_name
@sanitized_method_name ||= @method_name.sub(/\?$/,"")
end
end
class FormBuilder
# The methods which wrap a form helper call.
class_attribute :field_helpers
self.field_helpers = FormHelper.instance_method_names - %w(form_for convert_to_model)
attr_accessor :object_name, :object, :options
attr_reader :multipart, :parent_builder
alias :multipart? :multipart
def multipart=(multipart)
@multipart = multipart
parent_builder.multipart = multipart if parent_builder
end
def self._to_partial_path
@_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '')
end
def to_partial_path
self.class._to_partial_path
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
@parent_builder = options[:parent_builder]
@default_options = @options ? @options.slice(:index, :namespace) : {}
if @object_name.to_s.match(/\[\]$/)
if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
@auto_index = object.to_param
else
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
end
end
@multipart = nil
end
(field_helpers - %w(label check_box radio_button fields_for hidden_field file_field)).each do |selector|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{selector}(method, options = {}) # def text_field(method, options = {})
@template.send( # @template.send(
#{selector.inspect}, # "text_field",
@object_name, # @object_name,
method, # method,
objectify_options(options)) # objectify_options(options))
end # end
RUBY_EVAL
end
def fields_for(record_name, record_object = nil, fields_options = {}, &block)
fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
fields_options[:builder] ||= options[:builder]
fields_options[:parent_builder] = self
fields_options[:namespace] = fields_options[:parent_builder].options[:namespace]
case record_name
when String, Symbol
if nested_attributes_association?(record_name)
return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
end
else
record_object = record_name.is_a?(Array) ? record_name.last : record_name
record_name = ActiveModel::Naming.param_key(record_object)
end
index = if options.has_key?(:index)
"[#{options[:index]}]"
elsif defined?(@auto_index)
self.object_name = @object_name.to_s.sub(/\[\]$/,"")
"[#{@auto_index}]"
end
record_name = "#{object_name}#{index}[#{record_name}]"
@template.fields_for(record_name, record_object, fields_options, &block)
end
def label(method, text = nil, options = {}, &block)
@template.label(@object_name, method, text, objectify_options(options), &block)
end
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
@template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
end
def radio_button(method, tag_value, options = {})
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
end
def hidden_field(method, options = {})
@emitted_hidden_id = true if method == :id
@template.hidden_field(@object_name, method, objectify_options(options))
end
def file_field(method, options = {})
self.multipart = true
@template.file_field(@object_name, method, objectify_options(options))
end
# Add the submit button for the given form. When no value is given, it checks
# if the object is a new resource or not to create the proper label:
#
# <%= form_for @post do |f| %>
# <%= f.submit %>
# <% end %>
#
# In the example above, if @post is a new record, it will use "Create Post" as
# submit button label, otherwise, it uses "Update Post".
#
# Those labels can be customized using I18n, under the helpers.submit key and accept
# the %{model} as translation interpolation:
#
# en:
# helpers:
# submit:
# create: "Create a %{model}"
# update: "Confirm changes to %{model}"
#
# It also searches for a key specific for the given object:
#
# en:
# helpers:
# submit:
# post:
# create: "Add %{model}"
#
def submit(value=nil, options={})
value, options = nil, value if value.is_a?(Hash)
value ||= submit_default_value
@template.submit_tag(value, options)
end
# Add the submit button for the given form. When no value is given, it checks
# if the object is a new resource or not to create the proper label:
#
# <%= form_for @post do |f| %>
# <%= f.button %>
# <% end %>
#
# In the example above, if @post is a new record, it will use "Create Post" as
# submit button label, otherwise, it uses "Update Post".
#
# Those labels can be customized using I18n, under the helpers.submit key and accept
# the %{model} as translation interpolation:
#
# en:
# helpers:
# button:
# create: "Create a %{model}"
# update: "Confirm changes to %{model}"
#
# It also searches for a key specific for the given object:
#
# en:
# helpers:
# button:
# post:
# create: "Add %{model}"
#
def button(value=nil, options={})
value, options = nil, value if value.is_a?(Hash)
value ||= submit_default_value
@template.button_tag(value, options)
end
def emitted_hidden_id?
@emitted_hidden_id ||= nil
end
private
def objectify_options(options)
@default_options.merge(options.merge(:object => @object))
end
def submit_default_value
object = convert_to_model(@object)
key = object ? (object.persisted? ? :update : :create) : :submit
model = if object.class.respond_to?(:model_name)
object.class.model_name.human
else
@object_name.to_s.humanize
end
defaults = []
defaults << :"helpers.submit.#{object_name}.#{key}"
defaults << :"helpers.submit.#{key}"
defaults << "#{key.to_s.humanize} #{model}"
I18n.t(defaults.shift, :model => model, :default => defaults)
end
def nested_attributes_association?(association_name)
@object.respond_to?("#{association_name}_attributes=")
end
def fields_for_with_nested_attributes(association_name, association, options, block)
name = "#{object_name}[#{association_name}_attributes]"
association = convert_to_model(association)
if association.respond_to?(:persisted?)
association = [association] if @object.send(association_name).is_a?(Array)
elsif !association.respond_to?(:to_ary)
association = @object.send(association_name)
end
if association.respond_to?(:to_ary)
explicit_child_index = options[:child_index]
output = ActiveSupport::SafeBuffer.new
association.each do |child|
output << fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, options, block)
end
output
elsif association
fields_for_nested_model(name, association, options, block)
end
end
def fields_for_nested_model(name, object, options, block)
object = convert_to_model(object)
parent_include_id = self.options.fetch(:include_id, true)
include_id = options.fetch(:include_id, parent_include_id)
options[:hidden_field_id] = object.persisted? && include_id
@template.fields_for(name, object, options, &block)
end
def nested_child_index(name)
@nested_child_index[name] ||= -1
@nested_child_index[name] += 1
end
def convert_to_model(object)
object.respond_to?(:to_model) ? object.to_model : object
end
end
end
ActiveSupport.on_load(:action_view) do
class ActionView::Base
cattr_accessor :default_form_builder
@@default_form_builder = ::ActionView::Helpers::FormBuilder
end
end
end
actionpack-3.2.16/lib/action_view/helpers/asset_tag_helper.rb 0000644 0001750 0001750 00000056451 12247655372 023650 0 ustar ondrej ondrej require 'action_view/helpers/asset_tag_helpers/javascript_tag_helpers'
require 'action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers'
require 'action_view/helpers/asset_tag_helpers/asset_paths'
require 'action_view/helpers/tag_helper'
module ActionView
# = Action View Asset Tag Helpers
module Helpers #:nodoc:
# This module provides methods for generating HTML that links views to assets such
# as images, javascripts, stylesheets, and feeds. These methods do not verify
# the assets exist before linking to them:
#
# image_tag("rails.png")
# # =>
# stylesheet_link_tag("application")
# # =>
#
# === Using asset hosts
#
# By default, Rails links to these assets on the current host in the public
# folder, but you can direct Rails to link to assets from a dedicated asset
# server by setting ActionController::Base.asset_host in the application
# configuration, typically in config/environments/production.rb.
# For example, you'd define assets.example.com to be your asset
# host this way:
#
# ActionController::Base.asset_host = "assets.example.com"
#
# Helpers take that into account:
#
# image_tag("rails.png")
# # =>
# stylesheet_link_tag("application")
# # =>
#
# Browsers typically open at most two simultaneous connections to a single
# host, which means your assets often have to wait for other assets to finish
# downloading. You can alleviate this by using a %d wildcard in the
# +asset_host+. For example, "assets%d.example.com". If that wildcard is
# present Rails distributes asset requests among the corresponding four hosts
# "assets0.example.com", ..., "assets3.example.com". With this trick browsers
# will open eight simultaneous connections rather than two.
#
# image_tag("rails.png")
# # =>
# stylesheet_link_tag("application")
# # =>
#
# To do this, you can either setup four actual hosts, or you can use wildcard
# DNS to CNAME the wildcard to a single asset host. You can read more about
# setting up your DNS CNAME records from your ISP.
#
# Note: This is purely a browser performance optimization and is not meant
# for server load balancing. See http://www.die.net/musings/page_load_time/
# for background.
#
# Alternatively, you can exert more control over the asset host by setting
# +asset_host+ to a proc like this:
#
# ActionController::Base.asset_host = Proc.new { |source|
# "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com"
# }
# image_tag("rails.png")
# # =>
# stylesheet_link_tag("application")
# # =>
#
# The example above generates "http://assets1.example.com" and
# "http://assets2.example.com". This option is useful for example if
# you need fewer/more than four hosts, custom host names, etc.
#
# As you see the proc takes a +source+ parameter. That's a string with the
# absolute path of the asset with any extensions and timestamps in place,
# for example "/images/rails.png?1230601161".
#
# ActionController::Base.asset_host = Proc.new { |source|
# if source.starts_with?('/images')
# "http://images.example.com"
# else
# "http://assets.example.com"
# end
# }
# image_tag("rails.png")
# # =>
# stylesheet_link_tag("application")
# # =>
#
# Alternatively you may ask for a second parameter +request+. That one is
# particularly useful for serving assets from an SSL-protected page. The
# example proc below disables asset hosting for HTTPS connections, while
# still sending assets for plain HTTP requests from asset hosts. If you don't
# have SSL certificates for each of the asset hosts this technique allows you
# to avoid warnings in the client about mixed media.
#
# ActionController::Base.asset_host = Proc.new { |source, request|
# if request.ssl?
# "#{request.protocol}#{request.host_with_port}"
# else
# "#{request.protocol}assets.example.com"
# end
# }
#
# You can also implement a custom asset host object that responds to +call+
# and takes either one or two parameters just like the proc.
#
# config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
# "http://asset%d.example.com", "https://asset1.example.com"
# )
#
# === Customizing the asset path
#
# By default, Rails appends asset's timestamps to all asset paths. This allows
# you to set a cache-expiration date for the asset far into the future, but
# still be able to instantly invalidate it by simply updating the file (and
# hence updating the timestamp, which then updates the URL as the timestamp
# is part of that, which in turn busts the cache).
#
# It's the responsibility of the web server you use to set the far-future
# expiration date on cache assets that you need to take advantage of this
# feature. Here's an example for Apache:
#
# # Asset Expiration
# ExpiresActive On
#
# ExpiresDefault "access plus 1 year"
#
#
# Also note that in order for this to work, all your application servers must
# return the same timestamps. This means that they must have their clocks
# synchronized. If one of them drifts out of sync, you'll see different
# timestamps at random and the cache won't work. In that case the browser
# will request the same assets over and over again even thought they didn't
# change. You can use something like Live HTTP Headers for Firefox to verify
# that the cache is indeed working.
#
# This strategy works well enough for most server setups and requires the
# least configuration, but if you deploy several application servers at
# different times - say to handle a temporary spike in load - then the
# asset time stamps will be out of sync. In a setup like this you may want
# to set the way that asset paths are generated yourself.
#
# Altering the asset paths that Rails generates can be done in two ways.
# The easiest is to define the RAILS_ASSET_ID environment variable. The
# contents of this variable will always be used in preference to
# calculated timestamps. A more complex but flexible way is to set
# ActionController::Base.config.asset_path to a proc
# that takes the unmodified asset path and returns the path needed for
# your asset caching to work. Typically you'd do something like this in
# config/environments/production.rb:
#
# # Normally you'd calculate RELEASE_NUMBER at startup.
# RELEASE_NUMBER = 12345
# config.action_controller.asset_path = proc { |asset_path|
# "/release-#{RELEASE_NUMBER}#{asset_path}"
# }
#
# This example would cause the following behavior on all servers no
# matter when they were deployed:
#
# image_tag("rails.png")
# # =>
# stylesheet_link_tag("application")
# # =>
#
# Changing the asset_path does require that your web servers have
# knowledge of the asset template paths that you rewrite to so it's not
# suitable for out-of-the-box use. To use the example given above you
# could use something like this in your Apache VirtualHost configuration:
#
#
# # Some browsers still send conditional-GET requests if there's a
# # Last-Modified header or an ETag header even if they haven't
# # reached the expiry date sent in the Expires header.
# Header unset Last-Modified
# Header unset ETag
# FileETag None
#
# # Assets requested using a cache-busting filename should be served
# # only once and then cached for a really long time. The HTTP/1.1
# # spec frowns on hugely-long expiration times though and suggests
# # that assets which never expire be served with an expiration date
# # 1 year from access.
# ExpiresActive On
# ExpiresDefault "access plus 1 year"
#
#
# # We use cached-busting location names with the far-future expires
# # headers to ensure that if a file does change it can force a new
# # request. The actual asset filenames are still the same though so we
# # need to rewrite the location from the cache-busting location to the
# # real asset location so that we can serve it.
# RewriteEngine On
# RewriteRule ^/release-\d+/(images|javascripts|stylesheets)/(.*)$ /$1/$2 [L]
module AssetTagHelper
include TagHelper
include JavascriptTagHelpers
include StylesheetTagHelpers
# Returns a link tag that browsers and news readers can use to auto-detect
# an RSS or ATOM feed. The +type+ can either be :rss (default) or
# :atom. Control the link options in url_for format using the
# +url_options+. You can modify the LINK tag itself in +tag_options+.
#
# ==== Options
# * :rel - Specify the relation of this link, defaults to "alternate"
# * :type - Override the auto-generated mime type
# * :title - Specify the title of the link, defaults to the +type+
#
# ==== Examples
# auto_discovery_link_tag # =>
#
# auto_discovery_link_tag(:atom) # =>
#
# auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
#
# auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
#
# auto_discovery_link_tag(:rss, {:controller => "news", :action => "feed"}) # =>
#
# auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "Example RSS"}) # =>
#
def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
tag(
"link",
"rel" => tag_options[:rel] || "alternate",
"type" => tag_options[:type] || Mime::Type.lookup_by_extension(type.to_s).to_s,
"title" => tag_options[:title] || type.to_s.upcase,
"href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
)
end
# <%= favicon_link_tag %>
#
# generates
#
#
#
# You may specify a different file in the first argument:
#
# <%= favicon_link_tag '/myicon.ico' %>
#
# That's passed to +path_to_image+ as is, so it gives
#
#
#
# The helper accepts an additional options hash where you can override "rel" and "type".
#
# For example, Mobile Safari looks for a different LINK tag, pointing to an image that
# will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad.
# The following call would generate such a tag:
#
# <%= favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png' %>
#
def favicon_link_tag(source='/favicon.ico', options={})
tag('link', {
:rel => 'shortcut icon',
:type => 'image/vnd.microsoft.icon',
:href => path_to_image(source)
}.merge(options.symbolize_keys))
end
# Computes the path to an image asset in the public images directory.
# Full paths from the document root will be passed through.
# Used internally by +image_tag+ to build the image path:
#
# image_path("edit") # => "/images/edit"
# image_path("edit.png") # => "/images/edit.png"
# image_path("icons/edit.png") # => "/images/icons/edit.png"
# image_path("/icons/edit.png") # => "/icons/edit.png"
# image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
#
# If you have images as application resources this method may conflict with their named routes.
# The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
# plugin authors are encouraged to do so.
def image_path(source)
source.present? ? asset_paths.compute_public_path(source, 'images') : ""
end
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
# Computes the path to a video asset in the public videos directory.
# Full paths from the document root will be passed through.
# Used internally by +video_tag+ to build the video path.
#
# ==== Examples
# video_path("hd") # => /videos/hd
# video_path("hd.avi") # => /videos/hd.avi
# video_path("trailers/hd.avi") # => /videos/trailers/hd.avi
# video_path("/trailers/hd.avi") # => /trailers/hd.avi
# video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi
def video_path(source)
asset_paths.compute_public_path(source, 'videos')
end
alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
# Computes the path to an audio asset in the public audios directory.
# Full paths from the document root will be passed through.
# Used internally by +audio_tag+ to build the audio path.
#
# ==== Examples
# audio_path("horse") # => /audios/horse
# audio_path("horse.wav") # => /audios/horse.wav
# audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav
# audio_path("/sounds/horse.wav") # => /sounds/horse.wav
# audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav
def audio_path(source)
asset_paths.compute_public_path(source, 'audios')
end
alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
# Computes the path to a font asset in the public fonts directory.
# Full paths from the document root will be passed through.
#
# ==== Examples
# font_path("font") # => /fonts/font
# font_path("font.ttf") # => /fonts/font.ttf
# font_path("dir/font.ttf") # => /fonts/dir/font.ttf
# font_path("/dir/font.ttf") # => /dir/font.ttf
# font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
def font_path(source)
asset_paths.compute_public_path(source, 'fonts')
end
alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route
# Returns an html image tag for the +source+. The +source+ can be a full
# path or a file that exists in your public images directory.
#
# ==== Options
# You can add HTML attributes using the +options+. The +options+ supports
# three additional keys for convenience and conformance:
#
# * :alt - If no alt text is given, the file name part of the
# +source+ is used (capitalized and without the extension)
# * :size - Supplied as "{Width}x{Height}", so "30x45" becomes
# width="30" and height="45". :size will be ignored if the
# value is not in the correct format.
# * :mouseover - Set an alternate image to be used when the onmouseover
# event is fired, and sets the original image to be replaced onmouseout.
# This can be used to implement an easy image toggle that fires on onmouseover.
#
# ==== Examples
# image_tag("icon") # =>
#
# image_tag("icon.png") # =>
#
# image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # =>
#
# image_tag("/icons/icon.gif", :size => "16x16") # =>
#
# image_tag("/icons/icon.gif", :height => '32', :width => '32') # =>
#
# image_tag("/icons/icon.gif", :class => "menu_icon") # =>
#
# image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
#
# image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
#
def image_tag(source, options = {})
options.symbolize_keys!
src = options[:src] = path_to_image(source)
unless src =~ /^(?:cid|data):/ || src.blank?
options[:alt] = options.fetch(:alt){ image_alt(src) }
end
if size = options.delete(:size)
options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
end
if mouseover = options.delete(:mouseover)
options[:onmouseover] = "this.src='#{path_to_image(mouseover)}'"
options[:onmouseout] = "this.src='#{src}'"
end
tag("img", options)
end
def image_alt(src)
File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').capitalize
end
# Returns an html video tag for the +sources+. If +sources+ is a string,
# a single video tag will be returned. If +sources+ is an array, a video
# tag with nested source tags for each source will be returned. The
# +sources+ can be full paths or files that exists in your public videos
# directory.
#
# ==== Options
# You can add HTML attributes using the +options+. The +options+ supports
# two additional keys for convenience and conformance:
#
# * :poster - Set an image (like a screenshot) to be shown
# before the video loads. The path is calculated like the +src+ of +image_tag+.
# * :size - Supplied as "{Width}x{Height}", so "30x45" becomes
# width="30" and height="45". :size will be ignored if the
# value is not in the correct format.
#
# ==== Examples
# video_tag("trailer") # =>
#
# video_tag("trailer.ogg") # =>
#
# video_tag("trailer.ogg", :controls => true, :autobuffer => true) # =>
#
# video_tag("trailer.m4v", :size => "16x10", :poster => "screenshot.png") # =>
#
# video_tag("/trailers/hd.avi", :size => "16x16") # =>
#
# video_tag("/trailers/hd.avi", :height => '32', :width => '32') # =>
#
# video_tag(["trailer.ogg", "trailer.flv"]) # =>
#
# video_tag(["trailer.ogg", "trailer.flv"] :size => "160x120") # =>
#
def video_tag(sources, options = {})
options.symbolize_keys!
options[:poster] = path_to_image(options[:poster]) if options[:poster]
if size = options.delete(:size)
options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
end
if sources.is_a?(Array)
content_tag("video", options) do
sources.map { |source| tag("source", :src => source) }.join.html_safe
end
else
options[:src] = path_to_video(sources)
tag("video", options)
end
end
# Returns an html audio tag for the +source+.
# The +source+ can be full path or file that exists in
# your public audios directory.
#
# ==== Examples
# audio_tag("sound") # =>
#
# audio_tag("sound.wav") # =>
#
# audio_tag("sound.wav", :autoplay => true, :controls => true) # =>
#
def audio_tag(source, options = {})
options.symbolize_keys!
options[:src] = path_to_audio(source)
tag("audio", options)
end
private
def asset_paths
@asset_paths ||= AssetTagHelper::AssetPaths.new(config, controller)
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/asset_tag_helpers/ 0000755 0001750 0001750 00000000000 12247655372 023473 5 ustar ondrej ondrej actionpack-3.2.16/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb 0000644 0001750 0001750 00000017240 12247655372 030572 0 ustar ondrej ondrej require 'active_support/concern'
require 'active_support/core_ext/file'
require 'action_view/helpers/asset_tag_helpers/asset_include_tag'
module ActionView
module Helpers
module AssetTagHelper
class StylesheetIncludeTag < AssetIncludeTag
def asset_name
'stylesheet'
end
def extension
'css'
end
def asset_tag(source, options)
# We force the :request protocol here to avoid a double-download bug in IE7 and IE8
tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => path_to_asset(source, :protocol => :request) }.merge(options))
end
def custom_dir
config.stylesheets_dir
end
end
module StylesheetTagHelpers
extend ActiveSupport::Concern
module ClassMethods
# Register one or more stylesheet files to be included when symbol
# is passed to stylesheet_link_tag. This method is typically intended
# to be called from plugin initialization to register stylesheet files
# that the plugin installed in public/stylesheets.
#
# ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"]
#
# stylesheet_link_tag :monkey # =>
#
#
#
def register_stylesheet_expansion(expansions)
style_expansions = StylesheetIncludeTag.expansions
expansions.each do |key, values|
style_expansions[key] = (style_expansions[key] || []) | Array(values)
end
end
end
# Computes the path to a stylesheet asset in the public stylesheets directory.
# If the +source+ filename has no extension, .css will be appended (except for explicit URIs).
# Full paths from the document root will be passed through.
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
#
# ==== Examples
# stylesheet_path "style" # => /stylesheets/style.css
# stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
# stylesheet_path "/dir/style.css" # => /dir/style.css
# stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
# stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
def stylesheet_path(source)
asset_paths.compute_public_path(source, 'stylesheets', :ext => 'css', :protocol => :request)
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
# Returns a stylesheet link tag for the sources specified as arguments. If
# you don't specify an extension, .css will be appended automatically.
# You can modify the link attributes by passing a hash as the last argument.
# For historical reasons, the 'media' attribute will always be present and defaults
# to "screen", so you must explicitely set it to "all" for the stylesheet(s) to
# apply to all media types.
#
# ==== Examples
# stylesheet_link_tag "style" # =>
#
#
# stylesheet_link_tag "style.css" # =>
#
#
# stylesheet_link_tag "http://www.example.com/style.css" # =>
#
#
# stylesheet_link_tag "style", :media => "all" # =>
#
#
# stylesheet_link_tag "style", :media => "print" # =>
#
#
# stylesheet_link_tag "random.styles", "/css/stylish" # =>
#
#
#
# You can also include all styles in the stylesheets directory using :all as the source:
#
# stylesheet_link_tag :all # =>
#
#
#
#
# If you want Rails to search in all the subdirectories under stylesheets, you should explicitly set :recursive:
#
# stylesheet_link_tag :all, :recursive => true
#
# == Caching multiple stylesheets into one
#
# You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
# compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching
# is set to true (which is the case by default for the Rails production environment, but not for the development
# environment). Examples:
#
# ==== Examples
# stylesheet_link_tag :all, :cache => true # when config.perform_caching is false =>
#
#
#
#
# stylesheet_link_tag :all, :cache => true # when config.perform_caching is true =>
#
#
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is false =>
#
#
#
#
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is true =>
#
#
# The :recursive option is also available for caching:
#
# stylesheet_link_tag :all, :cache => true, :recursive => true
#
# To force concatenation (even in development mode) set :concat to true. This is useful if
# you have too many stylesheets for IE to load.
#
# stylesheet_link_tag :all, :concat => true
#
def stylesheet_link_tag(*sources)
@stylesheet_include ||= StylesheetIncludeTag.new(config, asset_paths)
@stylesheet_include.include_tag(*sources)
end
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb 0000644 0001750 0001750 00000011371 12247655372 027500 0 ustar ondrej ondrej require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/file'
require 'action_view/helpers/tag_helper'
module ActionView
module Helpers
module AssetTagHelper
class AssetIncludeTag
include TagHelper
attr_reader :config, :asset_paths
class_attribute :expansions
def self.inherited(base)
base.expansions = { }
end
def initialize(config, asset_paths)
@config = config
@asset_paths = asset_paths
end
def asset_name
raise NotImplementedError
end
def extension
raise NotImplementedError
end
def custom_dir
raise NotImplementedError
end
def asset_tag(source, options)
raise NotImplementedError
end
def include_tag(*sources)
options = sources.extract_options!.stringify_keys
concat = options.delete("concat")
cache = concat || options.delete("cache")
recursive = options.delete("recursive")
if concat || (config.perform_caching && cache)
joined_name = (cache == true ? "all" : cache) + ".#{extension}"
joined_path = File.join((joined_name[/^#{File::SEPARATOR}/] ? config.assets_dir : custom_dir), joined_name)
unless config.perform_caching && File.exists?(joined_path)
write_asset_file_contents(joined_path, compute_paths(sources, recursive))
end
asset_tag(joined_name, options)
else
sources = expand_sources(sources, recursive)
ensure_sources!(sources) if cache
sources.collect { |source| asset_tag(source, options) }.join("\n").html_safe
end
end
private
def path_to_asset(source, options = {})
asset_paths.compute_public_path(source, asset_name.to_s.pluralize, options.merge(:ext => extension))
end
def path_to_asset_source(source)
asset_paths.compute_source_path(source, asset_name.to_s.pluralize, extension)
end
def compute_paths(*args)
expand_sources(*args).collect { |source| path_to_asset_source(source) }
end
def expand_sources(sources, recursive)
if sources.first == :all
collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}")
else
sources.inject([]) do |list, source|
determined_source = determine_source(source, expansions)
update_source_list(list, determined_source)
end
end
end
def update_source_list(list, source)
case source
when String
list.delete(source)
list << source
when Array
updated_sources = source - list
list.concat(updated_sources)
end
end
def ensure_sources!(sources)
sources.each do |source|
asset_file_path!(path_to_asset_source(source))
end
end
def collect_asset_files(*path)
dir = path.first
Dir[File.join(*path.compact)].collect do |file|
file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '')
end.sort
end
def determine_source(source, collection)
case source
when Symbol
collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
else
source
end
end
def join_asset_file_contents(paths)
paths.collect { |path| File.read(asset_file_path!(path, true)) }.join("\n\n")
end
def write_asset_file_contents(joined_asset_path, asset_paths)
FileUtils.mkdir_p(File.dirname(joined_asset_path))
File.atomic_write(joined_asset_path) { |cache| cache.write(join_asset_file_contents(asset_paths)) }
# 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(asset_file_path!(p)) }.max
File.utime(mt, mt, joined_asset_path)
end
def asset_file_path!(absolute_path, error_if_file_is_uri = false)
if asset_paths.is_uri?(absolute_path)
raise(Errno::ENOENT, "Asset file #{path} is uri and cannot be merged into single file") if error_if_file_is_uri
else
raise(Errno::ENOENT, "Asset file not found at '#{absolute_path}'" ) unless File.exist?(absolute_path)
return absolute_path
end
end
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb 0000644 0001750 0001750 00000005625 12247655372 026346 0 ustar ondrej ondrej require 'thread'
require 'active_support/core_ext/file'
require 'active_support/core_ext/module/attribute_accessors'
module ActionView
module Helpers
module AssetTagHelper
class AssetPaths < ::ActionView::AssetPaths #:nodoc:
# You can enable or disable the asset tag ids cache.
# With the cache enabled, the asset tag helper methods will make fewer
# expensive file system calls (the default implementation checks the file
# system timestamp). However this prevents you from modifying any asset
# files while the server is running.
#
# ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids = false
mattr_accessor :cache_asset_ids
# Add or change an asset id in the asset id cache. This can be used
# for SASS on Heroku.
# :api: public
def add_to_asset_ids_cache(source, asset_id)
self.asset_ids_cache_guard.synchronize do
self.asset_ids_cache[source] = asset_id
end
end
private
def rewrite_extension(source, dir, ext)
source_ext = File.extname(source)
source_with_ext = if source_ext.empty?
"#{source}.#{ext}"
elsif ext != source_ext[1..-1]
with_ext = "#{source}.#{ext}"
with_ext if File.exist?(File.join(config.assets_dir, dir, with_ext))
end
source_with_ext || source
end
# Break out the asset path rewrite in case plugins wish to put the asset id
# someplace other than the query string.
def rewrite_asset_path(source, dir, options = nil)
source = "/#{dir}/#{source}" unless source[0] == ?/
path = config.asset_path
if path && path.respond_to?(:call)
return path.call(source)
elsif path && path.is_a?(String)
return path % [source]
end
asset_id = rails_asset_id(source)
if asset_id.empty?
source
else
"#{source}?#{asset_id}"
end
end
mattr_accessor :asset_ids_cache
self.asset_ids_cache = {}
mattr_accessor :asset_ids_cache_guard
self.asset_ids_cache_guard = Mutex.new
# Use the RAILS_ASSET_ID environment variable or the source's
# modification time as its cache-busting asset id.
def rails_asset_id(source)
if asset_id = ENV["RAILS_ASSET_ID"]
asset_id
else
if self.cache_asset_ids && (asset_id = self.asset_ids_cache[source])
asset_id
else
path = File.join(config.assets_dir, source)
asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
if self.cache_asset_ids
add_to_asset_ids_cache(source, asset_id)
end
asset_id
end
end
end
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb 0000644 0001750 0001750 00000023252 12247655372 030547 0 ustar ondrej ondrej require 'active_support/concern'
require 'active_support/core_ext/file'
require 'action_view/helpers/asset_tag_helpers/asset_include_tag'
module ActionView
module Helpers
module AssetTagHelper
class JavascriptIncludeTag < AssetIncludeTag
def asset_name
'javascript'
end
def extension
'js'
end
def asset_tag(source, options)
content_tag("script", "", { "type" => Mime::JS, "src" => path_to_asset(source) }.merge(options))
end
def custom_dir
config.javascripts_dir
end
private
def expand_sources(sources, recursive = false)
if sources.include?(:all)
all_asset_files = (collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}") - ['application'])
add_application_js(all_asset_files, sources)
((determine_source(:defaults, expansions).dup & all_asset_files) + all_asset_files).uniq
else
expanded_sources = sources.inject([]) do |list, source|
determined_source = determine_source(source, expansions)
update_source_list(list, determined_source)
end
add_application_js(expanded_sources, sources)
expanded_sources
end
end
def add_application_js(expanded_sources, sources)
if (sources.include?(:defaults) || sources.include?(:all)) && File.exist?(File.join(custom_dir, "application.#{extension}"))
expanded_sources.delete('application')
expanded_sources << "application"
end
end
end
module JavascriptTagHelpers
extend ActiveSupport::Concern
module ClassMethods
# Register one or more javascript files to be included when symbol
# is passed to javascript_include_tag. This method is typically intended
# to be called from plugin initialization to register javascript files
# that the plugin installed in public/javascripts.
#
# ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"]
#
# javascript_include_tag :monkey # =>
#
#
#
def register_javascript_expansion(expansions)
js_expansions = JavascriptIncludeTag.expansions
expansions.each do |key, values|
js_expansions[key] = (js_expansions[key] || []) | Array(values)
end
end
end
# Computes the path to a javascript asset in the public javascripts directory.
# If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
# Full paths from the document root will be passed through.
# Used internally by javascript_include_tag to build the script path.
#
# ==== Examples
# javascript_path "xmlhr" # => /javascripts/xmlhr.js
# javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
# javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
# javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
def javascript_path(source)
asset_paths.compute_public_path(source, 'javascripts', :ext => 'js')
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
# Returns an HTML script tag for each of the +sources+ provided.
#
# Sources may be paths to JavaScript files. Relative paths are assumed to be relative
# to public/javascripts, full paths are assumed to be relative to the document
# root. Relative paths are idiomatic, use absolute paths only when needed.
#
# When passing paths, the ".js" extension is optional.
#
# If the application is not using the asset pipeline, to include the default JavaScript
# expansion pass :defaults as source. By default, :defaults loads jQuery,
# and that can be overridden in config/application.rb:
#
# config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js)
#
# When using :defaults or :all, if an application.js file exists
# in public/javascripts it will be included as well at the end.
#
# You can modify the HTML attributes of the script tag by passing a hash as the
# last argument.
#
# ==== Examples
# javascript_include_tag "xmlhr"
# # =>
#
# javascript_include_tag "xmlhr.js"
# # =>
#
# javascript_include_tag "common.javascript", "/elsewhere/cools"
# # =>
# #
#
# javascript_include_tag "http://www.example.com/xmlhr"
# # =>
#
# javascript_include_tag "http://www.example.com/xmlhr.js"
# # =>
#
# javascript_include_tag :defaults
# # =>
# #
# #
#
# Note: The application.js file is only referenced if it exists
#
# You can also include all JavaScripts in the +javascripts+ directory using :all as the source:
#
# javascript_include_tag :all
# # =>
# #
# #
# #
# #
#
# Note that your defaults of choice will be included first, so they will be available to all subsequently
# included files.
#
# If you want Rails to search in all the subdirectories under public/javascripts, you should
# explicitly set :recursive:
#
# javascript_include_tag :all, :recursive => true
#
# == Caching multiple JavaScripts into one
#
# You can also cache multiple JavaScripts into one file, which requires less HTTP connections to download
# and can better be compressed by gzip (leading to faster transfers). Caching will only happen if
# config.perform_caching is set to true (which is the case by default for the Rails
# production environment, but not for the development environment).
#
# ==== Examples
#
# # assuming config.perform_caching is false
# javascript_include_tag :all, :cache => true
# # =>
# #
# #
# #
# #
#
# # assuming config.perform_caching is true
# javascript_include_tag :all, :cache => true
# # =>
#
# # assuming config.perform_caching is false
# javascript_include_tag "jquery", "cart", "checkout", :cache => "shop"
# # =>
# #
# #
#
# # assuming config.perform_caching is true
# javascript_include_tag "jquery", "cart", "checkout", :cache => "shop"
# # =>
#
# The :recursive option is also available for caching:
#
# javascript_include_tag :all, :cache => true, :recursive => true
def javascript_include_tag(*sources)
@javascript_include ||= JavascriptIncludeTag.new(config, asset_paths)
@javascript_include.include_tag(*sources)
end
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/sanitize_helper.rb 0000644 0001750 0001750 00000022423 12247655372 023514 0 ustar ondrej ondrej require 'active_support/core_ext/object/try'
require 'action_controller/vendor/html-scanner'
module ActionView
# = Action View Sanitize Helpers
module Helpers #:nodoc:
# The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
# These helper methods extend Action View making them callable within your template files.
module SanitizeHelper
extend ActiveSupport::Concern
# 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
# :attributes or :tags options:
#
# Normal Use
#
# <%= sanitize @article.body %>
#
# 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
#
# class Application < Rails::Application
# config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
# end
#
# Remove tags to the default allowed tags
#
# class Application < Rails::Application
# config.after_initialize do
# ActionView::Base.sanitized_allowed_tags.delete 'div'
# end
# end
#
# Change allowed default attributes
#
# class Application < Rails::Application
# 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
# confuse browsers.
#
def sanitize(html, options = {})
self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe)
end
# Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
def sanitize_css(style)
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
# that of html-scanner.
#
# ==== Examples
#
# strip_tags("Strip these tags!")
# # => Strip these tags!
#
# strip_tags("Bold no more! See more here...")
# # => Bold no more! See more here...
#
# strip_tags("
Welcome to my website!
")
# # => Welcome to my website!
def strip_tags(html)
self.class.full_sanitizer.sanitize(html)
end
# Strips all link tags from +text+ leaving just the link text.
#
# ==== Examples
# strip_links('Ruby on Rails')
# # => Ruby on Rails
#
# strip_links('Please e-mail me at me@email.com.')
# # => Please e-mail me at me@email.com.
#
# strip_links('Blog: Visit.')
# # => Blog: Visit.
def strip_links(html)
self.class.link_sanitizer.sanitize(html)
end
module ClassMethods #:nodoc:
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+.
#
# class Application < Rails::Application
# config.action_view.full_sanitizer = MySpecialSanitizer.new
# end
#
def full_sanitizer
@full_sanitizer ||= HTML::FullSanitizer.new
end
# Gets the HTML::LinkSanitizer instance used by +strip_links+. Replace with
# any object that responds to +sanitize+.
#
# class Application < Rails::Application
# config.action_view.link_sanitizer = MySpecialSanitizer.new
# end
#
def link_sanitizer
@link_sanitizer ||= HTML::LinkSanitizer.new
end
# Gets the HTML::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
# Replace with any object that responds to +sanitize+.
#
# class Application < Rails::Application
# config.action_view.white_list_sanitizer = MySpecialSanitizer.new
# end
#
def white_list_sanitizer
@white_list_sanitizer ||= HTML::WhiteListSanitizer.new
end
# Adds valid HTML attributes that the +sanitize+ helper checks for URIs.
#
# class Application < Rails::Application
# config.action_view.sanitized_uri_attributes = 'lowsrc', 'target'
# end
#
def sanitized_uri_attributes=(attributes)
HTML::WhiteListSanitizer.uri_attributes.merge(attributes)
end
# Adds to the Set of 'bad' tags for the +sanitize+ helper.
#
# class Application < Rails::Application
# config.action_view.sanitized_bad_tags = 'embed', 'object'
# end
#
def sanitized_bad_tags=(attributes)
HTML::WhiteListSanitizer.bad_tags.merge(attributes)
end
# Adds to the Set of allowed tags for the +sanitize+ helper.
#
# class Application < Rails::Application
# config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
# end
#
def sanitized_allowed_tags=(attributes)
HTML::WhiteListSanitizer.allowed_tags.merge(attributes)
end
# Adds to the Set of allowed HTML attributes for the +sanitize+ helper.
#
# class Application < Rails::Application
# config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc'
# end
#
def sanitized_allowed_attributes=(attributes)
HTML::WhiteListSanitizer.allowed_attributes.merge(attributes)
end
# Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers.
#
# class Application < Rails::Application
# config.action_view.sanitized_allowed_css_properties = 'expression'
# end
#
def sanitized_allowed_css_properties=(attributes)
HTML::WhiteListSanitizer.allowed_css_properties.merge(attributes)
end
# Adds to the Set of allowed CSS keywords for the +sanitize+ and +sanitize_css+ helpers.
#
# class Application < Rails::Application
# config.action_view.sanitized_allowed_css_keywords = 'expression'
# end
#
def sanitized_allowed_css_keywords=(attributes)
HTML::WhiteListSanitizer.allowed_css_keywords.merge(attributes)
end
# Adds to the Set of allowed shorthand CSS properties for the +sanitize+ and +sanitize_css+ helpers.
#
# class Application < Rails::Application
# config.action_view.sanitized_shorthand_css_properties = 'expression'
# end
#
def sanitized_shorthand_css_properties=(attributes)
HTML::WhiteListSanitizer.shorthand_css_properties.merge(attributes)
end
# Adds to the Set of allowed protocols for the +sanitize+ helper.
#
# class Application < Rails::Application
# config.action_view.sanitized_allowed_protocols = 'ssh', 'feed'
# end
#
def sanitized_allowed_protocols=(attributes)
HTML::WhiteListSanitizer.allowed_protocols.merge(attributes)
end
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/asset_paths.rb 0000644 0001750 0001750 00000000323 12247655372 022640 0 ustar ondrej ondrej ActiveSupport::Deprecation.warn "ActionView::Helpers::AssetPaths is deprecated. Please use ActionView::AssetPaths instead."
module ActionView
module Helpers
AssetPaths = ::ActionView::AssetPaths
end
end actionpack-3.2.16/lib/action_view/helpers/debug_helper.rb 0000644 0001750 0001750 00000002467 12247655372 022762 0 ustar ondrej ondrej module ActionView
# = Action View Debug Helper
#
# Provides a set of methods for making it easier to debug Rails objects.
module Helpers
module DebugHelper
# Returns a YAML representation of +object+ wrapped with
and
.
# If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
# Useful for inspecting an object at the time of rendering.
#
# ==== Example
#
# @user = User.new({ :username => 'testing', :password => 'xyz', :age => 42}) %>
# debug(@user)
# # =>
#
".html_safe
rescue Exception # errors from Marshal or YAML
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
"#{h(object.inspect)}".html_safe
end
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/rendering_helper.rb 0000644 0001750 0001750 00000006330 12247655372 023642 0 ustar ondrej ondrej module ActionView
module Helpers
# = Action View Rendering
#
# Implements methods that allow rendering from a view context.
# In order to use this module, all you need is to implement
# view_renderer that returns an ActionView::Renderer object.
module RenderingHelper
# Returns the result of a render that's dictated by the options hash. The primary options are:
#
# * :partial - See ActionView::PartialRenderer.
# * :file - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
# * :inline - Renders an inline template similar to how it's done in the controller.
# * :text - Renders the text passed in out.
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
# as the locals hash.
def render(options = {}, locals = {}, &block)
case options
when Hash
if block_given?
view_renderer.render_partial(self, options.merge(:partial => options[:layout]), &block)
else
view_renderer.render(self, options)
end
else
view_renderer.render_partial(self, :partial => options, :locals => locals)
end
end
# Overwrites _layout_for in the context object so it supports the case a block is
# passed to a partial. Returns the contents that are yielded to a layout, given a
# name or a block.
#
# You can think of a layout as a method that is called with a block. If the user calls
# yield :some_name, the block, by default, returns content_for(:some_name).
# If the user calls simply +yield+, the default block returns content_for(:layout).
#
# The user can override this default by passing a block to the layout:
#
# # The template
# <%= render :layout => "my_layout" do %>
# Content
# <% end %>
#
# # The layout
#
# <%= yield %>
#
#
# In this case, instead of the default block, which would return content_for(:layout),
# this method returns the block that was passed in to render :layout, and the response
# would be
#
#
# Content
#
#
# Finally, the block can take block arguments, which can be passed in by +yield+:
#
# # The template
# <%= render :layout => "my_layout" do |customer| %>
# Hello <%= customer.name %>
# <% end %>
#
# # The layout
#
# <%= yield Struct.new(:name).new("David") %>
#
#
# In this case, the layout would receive the block passed into render :layout,
# and the struct specified would be passed into the block as an argument. The result
# would be
#
#
# Hello David
#
#
def _layout_for(*args, &block)
name = args.first
if block && !name.is_a?(Symbol)
capture(*args, &block)
else
super
end
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/date_helper.rb 0000644 0001750 0001750 00000151456 12247655372 022614 0 ustar ondrej ondrej require 'date'
require 'action_view/helpers/tag_helper'
require 'active_support/core_ext/date/conversions'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/with_options'
module ActionView
module Helpers
# = Action View Date Helpers
#
# The Date Helper primarily creates select/option tags for different kinds of dates and times or date and time
# elements. All of the select-type methods share a number of common options that are as follows:
#
# * :prefix - overwrites the default prefix of "date" used for the select names. So specifying "birthday"
# would give birthday[month] instead of date[month] if passed to the select_month method.
# * :include_blank - set to true if it should be possible to set an empty date.
# * :discard_type - set to true if you want to discard the type part of the select name. If set to true,
# the select_month method would use simply "date" (which can be overwritten using :prefix) instead
# of "date[month]".
module DateHelper
# Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds.
# Set include_seconds to true if you want more detailed approximations when distance < 1 min, 29 secs.
# Distances are reported based on the following table:
#
# 0 <-> 29 secs # => less than a minute
# 30 secs <-> 1 min, 29 secs # => 1 minute
# 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
# 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
# 89 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
# 23 hrs, 59 mins, 30 secs <-> 41 hrs, 59 mins, 29 secs # => 1 day
# 41 hrs, 59 mins, 30 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
# 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
# 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
# 1 yr <-> 1 yr, 3 months # => about 1 year
# 1 yr, 3 months <-> 1 yr, 9 months # => over 1 year
# 1 yr, 9 months <-> 2 yr minus 1 sec # => almost 2 years
# 2 yrs <-> max time or date # => (same rules as 1 yr)
#
# With include_seconds = true and the difference < 1 minute 29 seconds:
# 0-4 secs # => less than 5 seconds
# 5-9 secs # => less than 10 seconds
# 10-19 secs # => less than 20 seconds
# 20-39 secs # => half a minute
# 40-59 secs # => less than a minute
# 60-89 secs # => 1 minute
#
# ==== Examples
# from_time = Time.now
# distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
# distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
# distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
# distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
# distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years
# distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days
# distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute
# distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
# distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
# distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
# distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years
# distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years
#
# to_time = Time.now + 6.years + 19.days
# distance_of_time_in_words(from_time, to_time, true) # => about 6 years
# distance_of_time_in_words(to_time, from_time, true) # => about 6 years
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
#
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {})
options = {
:scope => :'datetime.distance_in_words',
}.merge!(options)
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
distance = (to_time.to_f - from_time.to_f).abs
distance_in_minutes = (distance / 60.0).round
distance_in_seconds = distance.round
I18n.with_options :locale => options[:locale], :scope => options[:scope] do |locale|
case distance_in_minutes
when 0..1
return distance_in_minutes == 0 ?
locale.t(:less_than_x_minutes, :count => 1) :
locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds
case distance_in_seconds
when 0..4 then locale.t :less_than_x_seconds, :count => 5
when 5..9 then locale.t :less_than_x_seconds, :count => 10
when 10..19 then locale.t :less_than_x_seconds, :count => 20
when 20..39 then locale.t :half_a_minute
when 40..59 then locale.t :less_than_x_minutes, :count => 1
else locale.t :x_minutes, :count => 1
end
when 2..44 then locale.t :x_minutes, :count => distance_in_minutes
when 45..89 then locale.t :about_x_hours, :count => 1
when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
when 1440..2519 then locale.t :x_days, :count => 1
when 2520..43199 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
when 43200..86399 then locale.t :about_x_months, :count => 1
when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
else
fyear = from_time.year
fyear += 1 if from_time.month >= 3
tyear = to_time.year
tyear -= 1 if to_time.month < 3
leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)}
minute_offset_for_leap_year = leap_years * 1440
# Discount the leap year days when calculating year distance.
# e.g. if there are 20 leap year days between 2 dates having the same day
# and month then the based on 365 days calculation
# the distance in years will come out to over 80 years when in written
# english it would read better as about 80 years.
minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
remainder = (minutes_with_offset % 525600)
distance_in_years = (minutes_with_offset.div 525600)
if remainder < 131400
locale.t(:about_x_years, :count => distance_in_years)
elsif remainder < 394200
locale.t(:over_x_years, :count => distance_in_years)
else
locale.t(:almost_x_years, :count => distance_in_years + 1)
end
end
end
end
# Like distance_of_time_in_words, but where to_time is fixed to Time.now.
#
# ==== Examples
# time_ago_in_words(3.minutes.from_now) # => 3 minutes
# time_ago_in_words(Time.now - 15.hours) # => about 15 hours
# time_ago_in_words(Time.now) # => less than a minute
#
# from_time = Time.now - 3.days - 14.minutes - 25.seconds
# time_ago_in_words(from_time) # => 3 days
#
def time_ago_in_words(from_time, include_seconds = false, options = {})
distance_of_time_in_words(from_time, Time.now, include_seconds, options)
end
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
# Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based
# attribute (identified by +method+) on an object assigned to the template (identified by +object+).
#
#
# ==== Options
# * :use_month_numbers - Set to true if you want to use month numbers rather than month names (e.g.
# "2" instead of "February").
# * :use_short_month - Set to true if you want to use abbreviated month names instead of full
# month names (e.g. "Feb" instead of "February").
# * :add_month_numbers - Set to true if you want to use both month numbers and month names (e.g.
# "2 - February" instead of "February").
# * :use_month_names - Set to an array with 12 month names if you want to customize month names.
# Note: You can also use Rails' i18n functionality for this.
# * :date_separator - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
# * :start_year - Set the start year for the year select. Default is Time.now.year - 5.
# * :end_year - Set the end year for the year select. Default is Time.now.year + 5.
# * :discard_day - Set to true if you don't want to show a day select. This includes the day
# as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
# first of the given month in order to not create invalid dates like 31 February.
# * :discard_month - Set to true if you don't want to show a month select. This includes the month
# as a hidden field instead of showing a select field. Also note that this implicitly sets :discard_day to true.
# * :discard_year - Set to true if you don't want to show a year select. This includes the year
# as a hidden field instead of showing a select field.
# * :order - Set to an array containing :day, :month and :year to
# customize the order in which the select fields are shown. If you leave out any of the symbols, the respective
# select will not be shown (like when you set :discard_xxx => true. Defaults to the order defined in
# the respective locale (e.g. [:year, :month, :day] in the en locale that ships with Rails).
# * :include_blank - Include a blank option in every select field so it's possible to set empty
# dates.
# * :default - Set a default date if the affected date isn't set or is nil.
# * :disabled - Set to true if you want show the select fields as disabled.
# * :prompt - Set to true (for a generic prompt), a prompt string or a hash of prompt strings
# for :year, :month, :day, :hour, :minute and :second.
# Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds)
# or the given prompt string.
#
# If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
#
# NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed.
#
# ==== Examples
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute.
# date_select("article", "written_on")
#
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
# # with the year in the year drop down box starting at 1995.
# date_select("article", "written_on", :start_year => 1995)
#
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
# # with the year in the year drop down box starting at 1995, numbers used for months instead of words,
# # and without a day select box.
# date_select("article", "written_on", :start_year => 1995, :use_month_numbers => true,
# :discard_day => true, :include_blank => true)
#
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
# # with the fields ordered as day, month, year rather than month, day, year.
# date_select("article", "written_on", :order => [:day, :month, :year])
#
# # Generates a date select that when POSTed is stored in the user variable, in the birthday attribute
# # lacking a year field.
# date_select("user", "birthday", :order => [:month, :day])
#
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
# # which is initially set to the date 3 days from the current date
# date_select("article", "written_on", :default => 3.days.from_now)
#
# # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute
# # that will have a default day of 20.
# date_select("credit_card", "bill_due", :default => { :day => 20 })
#
# # Generates a date select with custom prompts.
# date_select("article", "written_on", :prompt => { :day => 'Select day', :month => 'Select month', :year => 'Select year' })
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
# all month choices are valid.
def date_select(object_name, method, options = {}, html_options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options)
end
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a
# specified time-based attribute (identified by +method+) on an object assigned to the template (identified by
# +object+). You can include the seconds with :include_seconds. You can get hours in the AM/PM format
# with :ampm option.
#
# This method will also generate 3 input hidden tags, for the actual year, month and day unless the option
# :ignore_date is set to +true+. If you set the :ignore_date to +true+, you must have a
# +date_select+ on the same method within the form otherwise an exception will be raised.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
# # Creates a time select tag that, when POSTed, will be stored in the article variable in the sunrise attribute.
# time_select("article", "sunrise")
#
# # Creates a time select tag with a seconds field that, when POSTed, will be stored in the article variables in
# # the sunrise attribute.
# time_select("article", "start_time", :include_seconds => true)
#
# # You can set the :minute_step to 15 which will give you: 00, 15, 30 and 45.
# time_select 'game', 'game_time', {:minute_step => 15}
#
# # Creates a time select tag with a custom prompt. Use :prompt => true for generic prompts.
# time_select("article", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'})
# time_select("article", "written_on", :prompt => {:hour => true}) # generic prompt for hours
# time_select("article", "written_on", :prompt => true) # generic prompts for all
#
# # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM.
# time_select 'game', 'game_time', {:ampm => true}
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
# all month choices are valid.
def time_select(object_name, method, options = {}, html_options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_time_select_tag(options, html_options)
end
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a
# specified datetime-based attribute (identified by +method+) on an object assigned to the template (identified
# by +object+).
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
# # Generates a datetime select that, when POSTed, will be stored in the article variable in the written_on
# # attribute.
# datetime_select("article", "written_on")
#
# # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
# # article variable in the written_on attribute.
# datetime_select("article", "written_on", :start_year => 1995)
#
# # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will
# # be stored in the trip variable in the departing attribute.
# datetime_select("trip", "departing", :default => 3.days.from_now)
#
# # Generate a datetime select with hours in the AM/PM format
# datetime_select("article", "written_on", :ampm => true)
#
# # Generates a datetime select that discards the type that, when POSTed, will be stored in the article variable
# # as the written_on attribute.
# datetime_select("article", "written_on", :discard_type => true)
#
# # Generates a datetime select with a custom prompt. Use :prompt => true for generic prompts.
# datetime_select("article", "written_on", :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
# datetime_select("article", "written_on", :prompt => {:hour => true}) # generic prompt for hours
# datetime_select("article", "written_on", :prompt => true) # generic prompts for all
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
def datetime_select(object_name, method, options = {}, html_options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
end
# Returns a set of html select-tags (one for year, month, day, hour, minute, and second) pre-selected with the
# +datetime+. It's also possible to explicitly set the order of the tags using the :order option with
# an array of symbols :year, :month and :day in the desired order. If you do not
# supply a Symbol, it will be appended onto the :order passed in. You can also add
# :date_separator, :datetime_separator and :time_separator keys to the +options+ to
# control visual display of the elements.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
# my_date_time = Time.now + 4.days
#
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today).
# select_datetime(my_date_time)
#
# # Generates a datetime select that defaults to today (no specified datetime)
# select_datetime()
#
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
# # with the fields ordered year, month, day rather than month, day, year.
# select_datetime(my_date_time, :order => [:year, :month, :day])
#
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
# # with a '/' between each date field.
# select_datetime(my_date_time, :date_separator => '/')
#
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
# # with a date fields separated by '/', time fields separated by '' and the date and time fields
# # separated by a comma (',').
# select_datetime(my_date_time, :date_separator => '/', :time_separator => '', :datetime_separator => ',')
#
# # Generates a datetime select that discards the type of the field and defaults to the datetime in
# # my_date_time (four days after today)
# select_datetime(my_date_time, :discard_type => true)
#
# # Generate a datetime field with hours in the AM/PM format
# select_datetime(my_date_time, :ampm => true)
#
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
# # prefixed with 'payday' rather than 'date'
# select_datetime(my_date_time, :prefix => 'payday')
#
# # Generates a datetime select with a custom prompt. Use :prompt => true for generic prompts.
# select_datetime(my_date_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
# select_datetime(my_date_time, :prompt => {:hour => true}) # generic prompt for hours
# select_datetime(my_date_time, :prompt => true) # generic prompts for all
#
def select_datetime(datetime = Time.current, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_datetime
end
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
# It's possible to explicitly set the order of the tags using the :order option with an array of
# symbols :year, :month and :day in the desired order.
# If the array passed to the :order option does not contain all the three symbols, all tags will be hidden.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
# my_date = Time.now + 6.days
#
# # Generates a date select that defaults to the date in my_date (six days after today).
# select_date(my_date)
#
# # Generates a date select that defaults to today (no specified date).
# select_date()
#
# # Generates a date select that defaults to the date in my_date (six days after today)
# # with the fields ordered year, month, day rather than month, day, year.
# select_date(my_date, :order => [:year, :month, :day])
#
# # Generates a date select that discards the type of the field and defaults to the date in
# # my_date (six days after today).
# select_date(my_date, :discard_type => true)
#
# # Generates a date select that defaults to the date in my_date,
# # which has fields separated by '/'.
# select_date(my_date, :date_separator => '/')
#
# # Generates a date select that defaults to the datetime in my_date (six days after today)
# # prefixed with 'payday' rather than 'date'.
# select_date(my_date, :prefix => 'payday')
#
# # Generates a date select with a custom prompt. Use :prompt => true for generic prompts.
# select_date(my_date, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
# select_date(my_date, :prompt => {:hour => true}) # generic prompt for hours
# select_date(my_date, :prompt => true) # generic prompts for all
#
def select_date(date = Date.current, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_date
end
# Returns a set of html select-tags (one for hour and minute).
# You can set :time_separator key to format the output, and
# the :include_seconds option to include an input for seconds.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
# my_time = Time.now + 5.days + 7.hours + 3.minutes + 14.seconds
#
# # Generates a time select that defaults to the time in my_time.
# select_time(my_time)
#
# # Generates a time select that defaults to the current time (no specified time).
# select_time()
#
# # Generates a time select that defaults to the time in my_time,
# # which has fields separated by ':'.
# select_time(my_time, :time_separator => ':')
#
# # Generates a time select that defaults to the time in my_time,
# # that also includes an input for seconds.
# select_time(my_time, :include_seconds => true)
#
# # Generates a time select that defaults to the time in my_time, that has fields
# # separated by ':' and includes an input for seconds.
# select_time(my_time, :time_separator => ':', :include_seconds => true)
#
# # Generate a time select field with hours in the AM/PM format
# select_time(my_time, :ampm => true)
#
# # Generates a time select with a custom prompt. Use :prompt to true for generic prompts.
# select_time(my_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
# select_time(my_time, :prompt => {:hour => true}) # generic prompt for hours
# select_time(my_time, :prompt => true) # generic prompts for all
#
def select_time(datetime = Time.current, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_time
end
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
# The datetime can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the :field_name option, 'second' by default.
#
# ==== Examples
# my_time = Time.now + 16.minutes
#
# # Generates a select field for seconds that defaults to the seconds for the time in my_time.
# select_second(my_time)
#
# # Generates a select field for seconds that defaults to the number given.
# select_second(33)
#
# # Generates a select field for seconds that defaults to the seconds for the time in my_time
# # that is named 'interval' rather than 'second'.
# select_second(my_time, :field_name => 'interval')
#
# # Generates a select field for seconds with a custom prompt. Use :prompt => true for a
# # generic prompt.
# select_second(14, :prompt => 'Choose seconds')
#
def select_second(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_second
end
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
# Also can return a select tag with options by minute_step from 0 through 59 with the 00 minute
# selected. The datetime can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the :field_name option, 'minute' by default.
#
# ==== Examples
# my_time = Time.now + 6.hours
#
# # Generates a select field for minutes that defaults to the minutes for the time in my_time.
# select_minute(my_time)
#
# # Generates a select field for minutes that defaults to the number given.
# select_minute(14)
#
# # Generates a select field for minutes that defaults to the minutes for the time in my_time
# # that is named 'moment' rather than 'minute'.
# select_minute(my_time, :field_name => 'moment')
#
# # Generates a select field for minutes with a custom prompt. Use :prompt => true for a
# # generic prompt.
# select_minute(14, :prompt => 'Choose minutes')
#
def select_minute(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_minute
end
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
# The datetime can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the :field_name option, 'hour' by default.
#
# ==== Examples
# my_time = Time.now + 6.hours
#
# # Generates a select field for hours that defaults to the hour for the time in my_time.
# select_hour(my_time)
#
# # Generates a select field for hours that defaults to the number given.
# select_hour(13)
#
# # Generates a select field for hours that defaults to the hour for the time in my_time
# # that is named 'stride' rather than 'hour'.
# select_hour(my_time, :field_name => 'stride')
#
# # Generates a select field for hours with a custom prompt. Use :prompt => true for a
# # generic prompt.
# select_hour(13, :prompt => 'Choose hour')
#
# # Generate a select field for hours in the AM/PM format
# select_hour(my_time, :ampm => true)
#
def select_hour(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_hour
end
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
# The date can also be substituted for a day number.
# Override the field name using the :field_name option, 'day' by default.
#
# ==== Examples
# my_date = Time.now + 2.days
#
# # Generates a select field for days that defaults to the day for the date in my_date.
# select_day(my_time)
#
# # Generates a select field for days that defaults to the number given.
# select_day(5)
#
# # Generates a select field for days that defaults to the day for the date in my_date
# # that is named 'due' rather than 'day'.
# select_day(my_time, :field_name => 'due')
#
# # Generates a select field for days with a custom prompt. Use :prompt => true for a
# # generic prompt.
# select_day(5, :prompt => 'Choose day')
#
def select_day(date, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_day
end
# Returns a select tag with options for each of the months January through December with the current month
# selected. The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are
# used as values (what's submitted to the server). It's also possible to use month numbers for the presentation
# instead of names -- set the :use_month_numbers key in +options+ to true for this to happen. If you
# want both numbers and names, set the :add_month_numbers key in +options+ to true. If you would prefer
# to show month names as abbreviations, set the :use_short_month key in +options+ to true. If you want
# to use your own month names, set the :use_month_names key in +options+ to an array of 12 month names.
# Override the field name using the :field_name option, 'month' by default.
#
# ==== Examples
# # Generates a select field for months that defaults to the current month that
# # will use keys like "January", "March".
# select_month(Date.today)
#
# # Generates a select field for months that defaults to the current month that
# # is named "start" rather than "month".
# select_month(Date.today, :field_name => 'start')
#
# # Generates a select field for months that defaults to the current month that
# # will use keys like "1", "3".
# select_month(Date.today, :use_month_numbers => true)
#
# # Generates a select field for months that defaults to the current month that
# # will use keys like "1 - January", "3 - March".
# select_month(Date.today, :add_month_numbers => true)
#
# # Generates a select field for months that defaults to the current month that
# # will use keys like "Jan", "Mar".
# select_month(Date.today, :use_short_month => true)
#
# # Generates a select field for months that defaults to the current month that
# # will use keys like "Januar", "Marts."
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
#
# # Generates a select field for months with a custom prompt. Use :prompt => true for a
# # generic prompt.
# select_month(14, :prompt => 'Choose month')
#
def select_month(date, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_month
end
# Returns a select tag with options for each of the five years on each side of the current, which is selected.
# The five year radius can be changed using the :start_year and :end_year keys in the
# +options+. Both ascending and descending year lists are supported by making :start_year less than or
# greater than :end_year. The date can also be substituted for a year given as a number.
# Override the field name using the :field_name option, 'year' by default.
#
# ==== Examples
# # Generates a select field for years that defaults to the current year that
# # has ascending year values.
# select_year(Date.today, :start_year => 1992, :end_year => 2007)
#
# # Generates a select field for years that defaults to the current year that
# # is named 'birth' rather than 'year'.
# select_year(Date.today, :field_name => 'birth')
#
# # Generates a select field for years that defaults to the current year that
# # has descending year values.
# select_year(Date.today, :start_year => 2005, :end_year => 1900)
#
# # Generates a select field for years that defaults to the year 2006 that
# # has ascending year values.
# select_year(2006, :start_year => 2000, :end_year => 2010)
#
# # Generates a select field for years with a custom prompt. Use :prompt => true for a
# # generic prompt.
# select_year(14, :prompt => 'Choose year')
#
def select_year(date, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_year
end
# Returns an html time tag for the given date or time.
#
# ==== Examples
# time_tag Date.today # =>
#
# time_tag Time.now # =>
#
# time_tag Date.yesterday, 'Yesterday' # =>
#
# time_tag Date.today, :pubdate => true # =>
#
#
def time_tag(date_or_time, *args)
options = args.extract_options!
format = options.delete(:format) || :long
content = args.first || I18n.l(date_or_time, :format => format)
datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.rfc3339
content_tag(:time, content, options.reverse_merge(:datetime => datetime))
end
end
class DateTimeSelector #:nodoc:
include ActionView::Helpers::TagHelper
DEFAULT_PREFIX = 'date'.freeze
POSITION = {
:year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6
}.freeze
AMPM_TRANSLATION = Hash[
[[0, "12 AM"], [1, "01 AM"], [2, "02 AM"], [3, "03 AM"],
[4, "04 AM"], [5, "05 AM"], [6, "06 AM"], [7, "07 AM"],
[8, "08 AM"], [9, "09 AM"], [10, "10 AM"], [11, "11 AM"],
[12, "12 PM"], [13, "01 PM"], [14, "02 PM"], [15, "03 PM"],
[16, "04 PM"], [17, "05 PM"], [18, "06 PM"], [19, "07 PM"],
[20, "08 PM"], [21, "09 PM"], [22, "10 PM"], [23, "11 PM"]]
].freeze
def initialize(datetime, options = {}, html_options = {})
@options = options.dup
@html_options = html_options.dup
@datetime = datetime
@options[:datetime_separator] ||= ' — '
@options[:time_separator] ||= ' : '
end
def select_datetime
order = date_order.dup
order -= [:hour, :minute, :second]
@options[:discard_year] ||= true unless order.include?(:year)
@options[:discard_month] ||= true unless order.include?(:month)
@options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
@options[:discard_minute] ||= true if @options[:discard_hour]
@options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute]
# If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
# valid (otherwise it could be 31 and February wouldn't be a valid date)
if @datetime && @options[:discard_day] && !@options[:discard_month]
@datetime = @datetime.change(:day => 1)
end
if @options[:tag] && @options[:ignore_date]
select_time
else
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
order += [:hour, :minute, :second] unless @options[:discard_hour]
build_selects_from_types(order)
end
end
def select_date
order = date_order.dup
@options[:discard_hour] = true
@options[:discard_minute] = true
@options[:discard_second] = true
@options[:discard_year] ||= true unless order.include?(:year)
@options[:discard_month] ||= true unless order.include?(:month)
@options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
# If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
# valid (otherwise it could be 31 and February wouldn't be a valid date)
if @datetime && @options[:discard_day] && !@options[:discard_month]
@datetime = @datetime.change(:day => 1)
end
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
build_selects_from_types(order)
end
def select_time
order = []
@options[:discard_month] = true
@options[:discard_year] = true
@options[:discard_day] = true
@options[:discard_second] ||= true unless @options[:include_seconds]
order += [:year, :month, :day] unless @options[:ignore_date]
order += [:hour, :minute]
order << :second if @options[:include_seconds]
build_selects_from_types(order)
end
def select_second
if @options[:use_hidden] || @options[:discard_second]
build_hidden(:second, sec) if @options[:include_seconds]
else
build_options_and_select(:second, sec)
end
end
def select_minute
if @options[:use_hidden] || @options[:discard_minute]
build_hidden(:minute, min)
else
build_options_and_select(:minute, min, :step => @options[:minute_step])
end
end
def select_hour
if @options[:use_hidden] || @options[:discard_hour]
build_hidden(:hour, hour)
else
build_options_and_select(:hour, hour, :end => 23, :ampm => @options[:ampm])
end
end
def select_day
if @options[:use_hidden] || @options[:discard_day]
build_hidden(:day, day || 1)
else
build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false, :use_two_digit_numbers => @options[:use_two_digit_numbers])
end
end
def select_month
if @options[:use_hidden] || @options[:discard_month]
build_hidden(:month, month || 1)
else
month_options = []
1.upto(12) do |month_number|
options = { :value => month_number }
options[:selected] = "selected" if month == month_number
month_options << content_tag(:option, month_name(month_number), options) + "\n"
end
build_select(:month, month_options.join)
end
end
def select_year
if !@datetime || @datetime == 0
val = '1'
middle_year = Date.today.year
else
val = middle_year = year
end
if @options[:use_hidden] || @options[:discard_year]
build_hidden(:year, val)
else
options = {}
options[:start] = @options[:start_year] || middle_year - 5
options[:end] = @options[:end_year] || middle_year + 5
options[:step] = options[:start] < options[:end] ? 1 : -1
options[:leading_zeros] = false
options[:max_years_allowed] = @options[:max_years_allowed] || 1000
if (options[:end] - options[:start]).abs > options[:max_years_allowed]
raise ArgumentError, "There're too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter"
end
build_options_and_select(:year, val, options)
end
end
private
%w( sec min hour day month year ).each do |method|
define_method(method) do
@datetime.kind_of?(Numeric) ? @datetime : @datetime.send(method) if @datetime
end
end
# Returns translated month names, but also ensures that a custom month
# name array has a leading nil element.
def month_names
@month_names ||= begin
month_names = @options[:use_month_names] || translated_month_names
month_names.unshift(nil) if month_names.size < 13
month_names
end
end
# Returns translated month names.
# => [nil, "January", "February", "March",
# "April", "May", "June", "July",
# "August", "September", "October",
# "November", "December"]
#
# If :use_short_month option is set
# => [nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun",
# "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
def translated_month_names
key = @options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names'
I18n.translate(key, :locale => @options[:locale])
end
# Lookup month name for number.
# month_name(1) => "January"
#
# If :use_month_numbers option is passed
# month_name(1) => 1
#
# If :add_month_numbers option is passed
# month_name(1) => "1 - January"
def month_name(number)
if @options[:use_month_numbers]
number
elsif @options[:use_two_digit_numbers]
sprintf "%02d", number
elsif @options[:add_month_numbers]
"#{number} - #{month_names[number]}"
else
month_names[number]
end
end
def date_order
@date_order ||= @options[:order] || translated_date_order
end
def translated_date_order
I18n.translate(:'date.order', :locale => @options[:locale]) || []
end
# Build full select tag from date type and options.
def build_options_and_select(type, selected, options = {})
build_select(type, build_options(selected, options))
end
# Build select option html from date value and options.
# build_options(15, :start => 1, :end => 31)
# => "
#
# ..."
#
# If :step options is passed
# build_options(15, :start => 1, :end => 31, :step => 2)
# => "
#
# ..."
def build_options(selected, options = {})
start = options.delete(:start) || 0
stop = options.delete(:end) || 59
step = options.delete(:step) || 1
options.reverse_merge!({:leading_zeros => true, :ampm => false, :use_two_digit_numbers => false})
leading_zeros = options.delete(:leading_zeros)
select_options = []
start.step(stop, step) do |i|
value = leading_zeros ? sprintf("%02d", i) : i
tag_options = { :value => value }
tag_options[:selected] = "selected" if selected == i
text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
text = options[:ampm] ? AMPM_TRANSLATION[i] : text
select_options << content_tag(:option, text, tag_options)
end
(select_options.join("\n") + "\n").html_safe
end
# Builds select tag from date type and html select options.
# build_select(:month, "...")
# => "
# ...
# "
def build_select(type, select_options_as_html)
select_options = {
:id => input_id_from_type(type),
:name => input_name_from_type(type)
}.merge(@html_options)
select_options.merge!(:disabled => 'disabled') if @options[:disabled]
select_html = "\n"
select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
select_html << select_options_as_html
(content_tag(:select, select_html.html_safe, select_options) + "\n").html_safe
end
# Builds a prompt option tag with supplied options or from default options.
# prompt_option_tag(:month, :prompt => 'Select month')
# => ""
def prompt_option_tag(type, options)
prompt = case options
when Hash
default_options = {:year => false, :month => false, :day => false, :hour => false, :minute => false, :second => false}
default_options.merge!(options)[type.to_sym]
when String
options
else
I18n.translate(:"datetime.prompts.#{type}", :locale => @options[:locale])
end
prompt ? content_tag(:option, prompt, :value => '') : ''
end
# Builds hidden input tag for date part and value.
# build_hidden(:year, 2008)
# => ""
def build_hidden(type, value)
select_options = {
:type => "hidden",
:id => input_id_from_type(type),
:name => input_name_from_type(type),
:value => value
}.merge(@html_options.slice(:disabled))
select_options.merge!(:disabled => 'disabled') if @options[:disabled]
tag(:input, select_options) + "\n".html_safe
end
# Returns the name attribute for the input tag.
# => post[written_on(1i)]
def input_name_from_type(type)
prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX
prefix += "[#{@options[:index]}]" if @options.has_key?(:index)
field_name = @options[:field_name] || type
if @options[:include_position]
field_name += "(#{ActionView::Helpers::DateTimeSelector::POSITION[type]}i)"
end
@options[:discard_type] ? prefix : "#{prefix}[#{field_name}]"
end
# Returns the id attribute for the input tag.
# => "post_written_on_1i"
def input_id_from_type(type)
id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
id = @options[:namespace] + '_' + id if @options[:namespace]
id
end
# Given an ordering of datetime components, create the selection HTML
# and join them with their appropriate separators.
def build_selects_from_types(order)
select = ''
first_visible = order.find { |type| !@options[:"discard_#{type}"] }
order.reverse.each do |type|
separator = separator(type) unless type == first_visible # don't add before first visible field
select.insert(0, separator.to_s + send("select_#{type}").to_s)
end
select.html_safe
end
# Returns the separator for a given datetime component.
def separator(type)
case type
when :year
@options[:discard_year] ? "" : @options[:date_separator]
when :month
@options[:discard_month] ? "" : @options[:date_separator]
when :day
@options[:discard_day] ? "" : @options[:date_separator]
when :hour
(@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
when :minute
@options[:discard_minute] ? "" : @options[:time_separator]
when :second
@options[:include_seconds] ? @options[:time_separator] : ""
end
end
end
module DateHelperInstanceTag
def to_date_select_tag(options = {}, html_options = {})
datetime_selector(options, html_options).select_date.html_safe
end
def to_time_select_tag(options = {}, html_options = {})
datetime_selector(options, html_options).select_time.html_safe
end
def to_datetime_select_tag(options = {}, html_options = {})
datetime_selector(options, html_options).select_datetime.html_safe
end
private
def datetime_selector(options, html_options)
datetime = value(object) || default_datetime(options)
@auto_index ||= nil
options = options.dup
options[:field_name] = @method_name
options[:include_position] = true
options[:prefix] ||= @object_name
options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
DateTimeSelector.new(datetime, options, html_options)
end
def default_datetime(options)
return if options[:include_blank] || options[:prompt]
case options[:default]
when nil
Time.current
when Date, Time
options[:default]
else
default = options[:default].dup
# Rename :minute and :second to :min and :sec
default[:min] ||= default[:minute]
default[:sec] ||= default[:second]
time = Time.current
[:year, :month, :day, :hour, :min, :sec].each do |key|
default[key] ||= time.send(key)
end
Time.utc_time(
default[:year], default[:month], default[:day],
default[:hour], default[:min], default[:sec]
)
end
end
end
class InstanceTag #:nodoc:
include DateHelperInstanceTag
end
class FormBuilder
def date_select(method, options = {}, html_options = {})
@template.date_select(@object_name, method, objectify_options(options), html_options)
end
def time_select(method, options = {}, html_options = {})
@template.time_select(@object_name, method, objectify_options(options), html_options)
end
def datetime_select(method, options = {}, html_options = {})
@template.datetime_select(@object_name, method, objectify_options(options), html_options)
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/atom_feed_helper.rb 0000644 0001750 0001750 00000020125 12247655372 023606 0 ustar ondrej ondrej require 'set'
module ActionView
# = Action View Atom Feed Helpers
module Helpers #:nodoc:
module AtomFeedHelper
# Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
# template languages).
#
# Full usage example:
#
# config/routes.rb:
# Basecamp::Application.routes.draw do
# resources :posts
# root :to => "posts#index"
# end
#
# app/controllers/posts_controller.rb:
# class PostsController < ApplicationController::Base
# # GET /posts.html
# # GET /posts.atom
# def index
# @posts = Post.all
#
# respond_to do |format|
# format.html
# format.atom
# end
# end
# end
#
# app/views/posts/index.atom.builder:
# atom_feed do |feed|
# feed.title("My great blog!")
# feed.updated(@posts[0].created_at) if @posts.length > 0
#
# @posts.each do |post|
# feed.entry(post) do |entry|
# entry.title(post.title)
# entry.content(post.body, :type => 'html')
#
# entry.author do |author|
# author.name("DHH")
# end
# end
# end
# end
#
# The options for atom_feed are:
#
# * :language: Defaults to "en-US".
# * :root_url: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
# * :url: The URL for this feed. Defaults to the current URL.
# * :id: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}"
# * :schema_date: 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).
# * :instruct: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]}
#
# Other namespaces can be added to the root element:
#
# app/views/posts/index.atom.builder:
# atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app',
# 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed|
# feed.title("My great blog!")
# feed.updated((@posts.first.created_at))
# feed.tag!(openSearch:totalResults, 10)
#
# @posts.each do |post|
# feed.entry(post) do |entry|
# entry.title(post.title)
# entry.content(post.body, :type => 'html')
# entry.tag!('app:edited', Time.now)
#
# entry.author do |author|
# author.name("DHH")
# end
# end
# end
# end
#
# The Atom spec defines five elements (content rights title subtitle
# summary) which may directly contain xhtml content if :type => 'xhtml'
# is specified as an attribute. If so, this helper will take care of
# the enclosing div and xhtml namespace declaration. Example usage:
#
# entry.summary :type => 'xhtml' do |xhtml|
# xhtml.p pluralize(order.line_items.count, "line item")
# xhtml.p "Shipped to #{order.address}"
# xhtml.p "Paid by #{order.pay_type}"
# end
#
#
# atom_feed yields an +AtomFeedBuilder+ instance. Nested elements yield
# an +AtomBuilder+ instance.
def atom_feed(options = {}, &block)
if options[:schema_date]
options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime)
else
options[:schema_date] = "2005" # The Atom spec copyright date
end
xml = options.delete(:xml) || eval("xml", block.binding)
xml.instruct!
if options[:instruct]
options[:instruct].each do |target,attrs|
if attrs.respond_to?(:keys)
xml.instruct!(target, attrs)
elsif attrs.respond_to?(:each)
attrs.each { |attr_group| xml.instruct!(target, attr_group) }
end
end
end
feed_opts = {"xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom'}
feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)}
xml.feed(feed_opts) do
xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.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
class AtomBuilder
XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set
def initialize(xml)
@xml = xml
end
private
# Delegate to xml builder, first wrapping the element in a xhtml
# namespaced div element if the method and arguments indicate
# that an xhtml_block? is desired.
def method_missing(method, *arguments, &block)
if xhtml_block?(method, arguments)
@xml.__send__(method, *arguments) do
@xml.div(:xmlns => 'http://www.w3.org/1999/xhtml') do |xhtml|
block.call(xhtml)
end
end
else
@xml.__send__(method, *arguments, &block)
end
end
# True if the method name matches one of the five elements defined
# in the Atom spec as potentially containing XHTML content and
# if :type => 'xhtml' is, in fact, specified.
def xhtml_block?(method, arguments)
if XHTML_TAG_NAMES.include?(method.to_s)
last = arguments.last
last.is_a?(Hash) && last[:type].to_s == 'xhtml'
end
end
end
class AtomFeedBuilder < AtomBuilder
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)
end
# Creates an entry tag for a specific record and prefills the id using class and id.
#
# Options:
#
# * :published: Time first published. Defaults to the created_at attribute on the record if one such exists.
# * :updated: Time of update. Defaults to the updated_at attribute on the record if one such exists.
# * :url: The URL for this entry. Defaults to the polymorphic_url for the record.
# * :id: 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(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)
end
if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at)
@xml.updated((options[:updated] || record.updated_at).xmlschema)
end
@xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:url] || @view.polymorphic_url(record))
yield AtomBuilder.new(@xml)
end
end
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/csrf_helper.rb 0000644 0001750 0001750 00000002015 12247655372 022616 0 ustar ondrej ondrej require 'active_support/core_ext/string/strip'
module ActionView
# = Action View CSRF Helper
module Helpers
module CsrfHelper
# Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site
# request forgery protection parameter and token, respectively.
#
#
# <%= csrf_meta_tags %>
#
#
# These are used to generate the dynamic forms that implement non-remote links with
# :method.
#
# Note that regular forms generate hidden fields, and that Ajax calls are whitelisted,
# so they do not use these tags.
def csrf_meta_tags
if protect_against_forgery?
[
tag('meta', :name => 'csrf-param', :content => request_forgery_protection_token),
tag('meta', :name => 'csrf-token', :content => form_authenticity_token)
].join("\n").html_safe
end
end
# For backwards compatibility.
alias csrf_meta_tag csrf_meta_tags
end
end
end
actionpack-3.2.16/lib/action_view/helpers/number_helper.rb 0000644 0001750 0001750 00000070065 12247655372 023163 0 ustar ondrej ondrej # encoding: utf-8
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/big_decimal/conversions'
require 'active_support/core_ext/float/rounding'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
module ActionView
# = Action View Number Helpers
module Helpers #:nodoc:
# Provides methods for converting numbers into formatted strings.
# Methods are provided for phone numbers, currency, percentage,
# precision, positional notation, file size and pretty printing.
#
# Most methods expect a +number+ argument, and will return it
# unchanged if can't be converted into a valid number.
module NumberHelper
DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",",
:precision => 2, :significant => false, :strip_insignificant_zeros => false }
# Raised when argument +number+ param given to the helpers is invalid and
# the option :raise is set to +true+.
class InvalidNumberError < StandardError
attr_accessor :number
def initialize(number)
@number = number
end
end
# Formats a +number+ into a US phone number (e.g., (555)
# 123-9876). You can customize the format in the +options+ hash.
#
# ==== Options
#
# * :area_code - Adds parentheses around the area code.
# * :delimiter - Specifies the delimiter to use
# (defaults to "-").
# * :extension - Specifies an extension to add to the
# end of the generated number.
# * :country_code - Sets the country code for the phone
# number.
# * :raise - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
# ==== Examples
#
# number_to_phone(5551234) # => 555-1234
# number_to_phone("5551234") # => 555-1234
# number_to_phone(1235551234) # => 123-555-1234
# number_to_phone(1235551234, :area_code => true) # => (123) 555-1234
# number_to_phone(1235551234, :delimiter => " ") # => 123 555 1234
# number_to_phone(1235551234, :area_code => true, :extension => 555) # => (123) 555-1234 x 555
# number_to_phone(1235551234, :country_code => 1) # => +1-123-555-1234
# number_to_phone("123a456") # => 123a456
#
# number_to_phone("1234a567", :raise => true) # => InvalidNumberError
#
# number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".")
# # => +1.123.555.1234 x 1343
def number_to_phone(number, options = {})
return unless number
begin
Float(number)
rescue ArgumentError, TypeError
raise InvalidNumberError, number
end if options[:raise]
number = number.to_s.strip
options = options.symbolize_keys
area_code = options[:area_code]
delimiter = options[:delimiter] || "-"
extension = options[:extension]
country_code = options[:country_code]
if area_code
number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
else
number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
number.slice!(0, 1) if number.starts_with?(delimiter) && !delimiter.blank?
end
str = []
str << "+#{country_code}#{delimiter}" unless country_code.blank?
str << number
str << " x #{extension}" unless extension.blank?
ERB::Util.html_escape(str.join)
end
# Formats a +number+ into a currency string (e.g., $13.65). You
# can customize the format in the +options+ hash.
#
# ==== Options
#
# * :locale - Sets the locale to be used for formatting
# (defaults to current locale).
# * :precision - Sets the level of precision (defaults
# to 2).
# * :unit - Sets the denomination of the currency
# (defaults to "$").
# * :separator - Sets the separator between the units
# (defaults to ".").
# * :delimiter - Sets the thousands delimiter (defaults
# to ",").
# * :format - Sets the format for non-negative numbers
# (defaults to "%u%n"). Fields are %u for the
# currency, and %n for the number.
# * :negative_format - Sets the format for negative
# numbers (defaults to prepending an hyphen to the formatted
# number given by :format). Accepts the same fields
# than :format, except %n is here the
# absolute value of the number.
# * :raise - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
# ==== Examples
#
# number_to_currency(1234567890.50) # => $1,234,567,890.50
# number_to_currency(1234567890.506) # => $1,234,567,890.51
# number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506
# number_to_currency(1234567890.506, :locale => :fr) # => 1 234 567 890,51 €
# number_to_currency("123a456") # => $123a456
#
# number_to_currency("123a456", :raise => true) # => InvalidNumberError
#
# number_to_currency(-1234567890.50, :negative_format => "(%u%n)")
# # => ($1,234,567,890.50)
# number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "")
# # => £1234567890,50
# number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u")
# # => 1234567890,50 £
def number_to_currency(number, options = {})
return unless number
options.symbolize_keys!
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :default => {})
currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
defaults = DEFAULT_CURRENCY_VALUES.merge(defaults).merge!(currency)
defaults[:negative_format] = "-" + options[:format] if options[:format]
options = defaults.merge!(options)
unit = options.delete(:unit)
format = options.delete(:format)
if number.to_f < 0
format = options.delete(:negative_format)
number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
end
begin
value = number_with_precision(number, options.merge(:raise => true))
format.gsub(/%n/, ERB::Util.html_escape(value)).gsub(/%u/, ERB::Util.html_escape(unit)).html_safe
rescue InvalidNumberError => e
if options[:raise]
raise
else
formatted_number = format.gsub(/%n/, e.number).gsub(/%u/, unit)
e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
end
end
end
# Formats a +number+ as a percentage string (e.g., 65%). You can
# customize the format in the +options+ hash.
#
# ==== Options
#
# * :locale - Sets the locale to be used for formatting
# (defaults to current locale).
# * :precision - Sets the precision of the number
# (defaults to 3).
# * :significant - If +true+, precision will be the #
# of significant_digits. If +false+, the # of fractional
# digits (defaults to +false+).
# * :separator - Sets the separator between the
# fractional and integer digits (defaults to ".").
# * :delimiter - Sets the thousands delimiter (defaults
# to "").
# * :strip_insignificant_zeros - If +true+ removes
# insignificant zeros after the decimal separator (defaults to
# +false+).
# * :raise - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
# ==== Examples
#
# number_to_percentage(100) # => 100.000%
# number_to_percentage("98") # => 98.000%
# number_to_percentage(100, :precision => 0) # => 100%
# number_to_percentage(1000, :delimiter => '.', :separator => ',') # => 1.000,000%
# number_to_percentage(302.24398923423, :precision => 5) # => 302.24399%
# number_to_percentage(1000, :locale => :fr) # => 1 000,000%
# number_to_percentage("98a") # => 98a%
#
# number_to_percentage("98a", :raise => true) # => InvalidNumberError
def number_to_percentage(number, options = {})
return unless number
options.symbolize_keys!
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :default => {})
defaults = defaults.merge(percentage)
options = options.reverse_merge(defaults)
begin
"#{number_with_precision(number, options.merge(:raise => true))}%".html_safe
rescue InvalidNumberError => e
if options[:raise]
raise
else
e.number.to_s.html_safe? ? "#{e.number}%".html_safe : "#{e.number}%"
end
end
end
# Formats a +number+ with grouped thousands using +delimiter+
# (e.g., 12,324). You can customize the format in the +options+
# hash.
#
# ==== Options
#
# * :locale - Sets the locale to be used for formatting
# (defaults to current locale).
# * :delimiter - Sets the thousands delimiter (defaults
# to ",").
# * :separator - Sets the separator between the
# fractional and integer digits (defaults to ".").
# * :raise - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
# ==== Examples
#
# number_with_delimiter(12345678) # => 12,345,678
# number_with_delimiter("123456") # => 123,456
# number_with_delimiter(12345678.05) # => 12,345,678.05
# number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678
# number_with_delimiter(12345678, :delimiter => ",") # => 12,345,678
# number_with_delimiter(12345678.05, :separator => " ") # => 12,345,678 05
# number_with_delimiter(12345678.05, :locale => :fr) # => 12 345 678,05
# number_with_delimiter("112a") # => 112a
# number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",")
# # => 98 765 432,98
#
# number_with_delimiter("112a", :raise => true) # => raise InvalidNumberError
def number_with_delimiter(number, options = {})
options.symbolize_keys!
begin
Float(number)
rescue ArgumentError, TypeError
if options[:raise]
raise InvalidNumberError, number
else
return number
end
end
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
options = options.reverse_merge(defaults)
parts = number.to_s.to_str.split('.')
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
parts.join(options[:separator]).html_safe
end
# Formats a +number+ with the specified level of
# :precision (e.g., 112.32 has a precision of 2 if
# +:significant+ is +false+, and 5 if +:significant+ is +true+).
# You can customize the format in the +options+ hash.
#
# ==== Options
#
# * :locale - Sets the locale to be used for formatting
# (defaults to current locale).
# * :precision - Sets the precision of the number
# (defaults to 3).
# * :significant - If +true+, precision will be the #
# of significant_digits. If +false+, the # of fractional
# digits (defaults to +false+).
# * :separator - Sets the separator between the
# fractional and integer digits (defaults to ".").
# * :delimiter - Sets the thousands delimiter (defaults
# to "").
# * :strip_insignificant_zeros - If +true+ removes
# insignificant zeros after the decimal separator (defaults to
# +false+).
# * :raise - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
# ==== Examples
#
# number_with_precision(111.2345) # => 111.235
# number_with_precision(111.2345, :precision => 2) # => 111.23
# number_with_precision(13, :precision => 5) # => 13.00000
# number_with_precision(389.32314, :precision => 0) # => 389
# number_with_precision(111.2345, :significant => true) # => 111
# number_with_precision(111.2345, :precision => 1, :significant => true) # => 100
# number_with_precision(13, :precision => 5, :significant => true) # => 13.000
# number_with_precision(111.234, :locale => :fr) # => 111,234
#
# number_with_precision(13, :precision => 5, :significant => true, :strip_insignificant_zeros => true)
# # => 13
#
# number_with_precision(389.32314, :precision => 4, :significant => true) # => 389.3
# number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
# # => 1.111,23
def number_with_precision(number, options = {})
options.symbolize_keys!
number = begin
Float(number)
rescue ArgumentError, TypeError
if options[:raise]
raise InvalidNumberError, number
else
return number
end
end
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale], :default => {})
defaults = defaults.merge(precision_defaults)
options = options.reverse_merge(defaults) # Allow the user to unset default values: Eg.: :significant => false
precision = options.delete :precision
significant = options.delete :significant
strip_insignificant_zeros = options.delete :strip_insignificant_zeros
if significant and precision > 0
if number == 0
digits, rounded_number = 1, 0
else
digits = (Math.log10(number.abs) + 1).floor
rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision)
digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
end
precision -= digits
precision = precision > 0 ? precision : 0 #don't let it be negative
else
rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
end
formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options)
if strip_insignificant_zeros
escaped_separator = Regexp.escape(options[:separator])
formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '').html_safe
else
formatted_number
end
end
STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
# Formats the bytes in +number+ into a more understandable
# representation (e.g., giving it 1500 yields 1.5 KB). This
# method is useful for reporting file sizes to users. You can
# customize the format in the +options+ hash.
#
# See number_to_human if you want to pretty-print a
# generic number.
#
# ==== Options
#
# * :locale - Sets the locale to be used for formatting
# (defaults to current locale).
# * :precision - Sets the precision of the number
# (defaults to 3).
# * :significant - If +true+, precision will be the #
# of significant_digits. If +false+, the # of fractional
# digits (defaults to +true+)
# * :separator - Sets the separator between the
# fractional and integer digits (defaults to ".").
# * :delimiter - Sets the thousands delimiter (defaults
# to "").
# * :strip_insignificant_zeros - If +true+ removes
# insignificant zeros after the decimal separator (defaults to
# +true+)
# * :prefix - If +:si+ formats the number using the SI
# prefix (defaults to :binary)
# * :raise - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
# ==== Examples
#
# number_to_human_size(123) # => 123 Bytes
# number_to_human_size(1234) # => 1.21 KB
# number_to_human_size(12345) # => 12.1 KB
# number_to_human_size(1234567) # => 1.18 MB
# number_to_human_size(1234567890) # => 1.15 GB
# number_to_human_size(1234567890123) # => 1.12 TB
# number_to_human_size(1234567, :precision => 2) # => 1.2 MB
# number_to_human_size(483989, :precision => 2) # => 470 KB
# number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB
#
# Non-significant zeros after the fractional separator are
# stripped out by default (set
# :strip_insignificant_zeros to +false+ to change
# that):
# number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
# number_to_human_size(524288000, :precision => 5) # => "500 MB"
def number_to_human_size(number, options = {})
options.symbolize_keys!
number = begin
Float(number)
rescue ArgumentError, TypeError
if options[:raise]
raise InvalidNumberError, number
else
return number
end
end
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
defaults = defaults.merge(human)
options = options.reverse_merge(defaults)
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
base = options[:prefix] == :si ? 1000 : 1024
if number.to_i < base
unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit).html_safe
else
max_exp = STORAGE_UNITS.size - 1
exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
number /= base ** exponent
unit_key = STORAGE_UNITS[exponent]
unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
formatted_number = number_with_precision(number, options)
storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).html_safe
end
end
DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
-1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}
# Pretty prints (formats and approximates) a number in a way it
# is more readable by humans (eg.: 1200000000 becomes "1.2
# Billion"). This is useful for numbers that can get very large
# (and too hard to read).
#
# See number_to_human_size if you want to print a file
# size.
#
# You can also define you own unit-quantifier names if you want
# to use other decimal units (eg.: 1500 becomes "1.5
# kilometers", 0.150 becomes "150 milliliters", etc). You may
# define a wide range of unit quantifiers, even fractional ones
# (centi, deci, mili, etc).
#
# ==== Options
#
# * :locale - Sets the locale to be used for formatting
# (defaults to current locale).
# * :precision - Sets the precision of the number
# (defaults to 3).
# * :significant - If +true+, precision will be the #
# of significant_digits. If +false+, the # of fractional
# digits (defaults to +true+)
# * :separator - Sets the separator between the
# fractional and integer digits (defaults to ".").
# * :delimiter - Sets the thousands delimiter (defaults
# to "").
# * :strip_insignificant_zeros - If +true+ removes
# insignificant zeros after the decimal separator (defaults to
# +true+)
# * :units - A Hash of unit quantifier names. Or a
# string containing an i18n scope where to find this hash. It
# might have the following keys:
# * *integers*: :unit, :ten,
# *:hundred, :thousand, :million,
# *:billion, :trillion,
# *:quadrillion
# * *fractionals*: :deci, :centi,
# *:mili, :micro, :nano,
# *:pico, :femto
# * :format - Sets the format of the output string
# (defaults to "%n %u"). The field types are:
# * %u - The quantifier (ex.: 'thousand')
# * %n - The number
# * :raise - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
# ==== Examples
#
# number_to_human(123) # => "123"
# number_to_human(1234) # => "1.23 Thousand"
# number_to_human(12345) # => "12.3 Thousand"
# number_to_human(1234567) # => "1.23 Million"
# number_to_human(1234567890) # => "1.23 Billion"
# number_to_human(1234567890123) # => "1.23 Trillion"
# number_to_human(1234567890123456) # => "1.23 Quadrillion"
# number_to_human(1234567890123456789) # => "1230 Quadrillion"
# number_to_human(489939, :precision => 2) # => "490 Thousand"
# number_to_human(489939, :precision => 4) # => "489.9 Thousand"
# number_to_human(1234567, :precision => 4,
# :significant => false) # => "1.2346 Million"
# number_to_human(1234567, :precision => 1,
# :separator => ',',
# :significant => false) # => "1,2 Million"
#
# Non-significant zeros after the decimal separator are stripped
# out by default (set :strip_insignificant_zeros to
# +false+ to change that):
# number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion"
# number_to_human(500000000, :precision => 5) # => "500 Million"
#
# ==== Custom Unit Quantifiers
#
# You can also use your own custom unit quantifiers:
# number_to_human(500000, :units => {:unit => "ml", :thousand => "lt"}) # => "500 lt"
#
# If in your I18n locale you have:
# distance:
# centi:
# one: "centimeter"
# other: "centimeters"
# unit:
# one: "meter"
# other: "meters"
# thousand:
# one: "kilometer"
# other: "kilometers"
# billion: "gazillion-distance"
#
# Then you could do:
#
# number_to_human(543934, :units => :distance) # => "544 kilometers"
# number_to_human(54393498, :units => :distance) # => "54400 kilometers"
# number_to_human(54393498000, :units => :distance) # => "54.4 gazillion-distance"
# number_to_human(343, :units => :distance, :precision => 1) # => "300 meters"
# number_to_human(1, :units => :distance) # => "1 meter"
# number_to_human(0.34, :units => :distance) # => "34 centimeters"
#
def number_to_human(number, options = {})
options.symbolize_keys!
number = begin
Float(number)
rescue ArgumentError, TypeError
if options[:raise]
raise InvalidNumberError, number
else
return number
end
end
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
defaults = defaults.merge(human)
options = options.reverse_merge(defaults)
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
inverted_du = DECIMAL_UNITS.invert
units = options.delete :units
unit_exponents = case units
when Hash
units
when String, Symbol
I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
when nil
I18n.translate(:"number.human.decimal_units.units", :locale => options[:locale], :raise => true)
else
raise ArgumentError, ":units must be a Hash or String translation scope."
end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e}
number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
number /= 10 ** display_exponent
unit = case units
when Hash
units[DECIMAL_UNITS[display_exponent]] || ''
when String, Symbol
I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
else
I18n.translate(:"number.human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
end
decimal_format = options[:format] || I18n.translate(:'number.human.decimal_units.format', :locale => options[:locale], :default => "%n %u")
formatted_number = number_with_precision(number, options)
decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip.html_safe
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/record_tag_helper.rb 0000644 0001750 0001750 00000010215 12247655372 023773 0 ustar ondrej ondrej require 'action_controller/record_identifier'
module ActionView
# = Action View Record Tag Helpers
module Helpers
module RecordTagHelper
include ActionController::RecordIdentifier
# Produces a wrapper DIV element with id and class parameters that
# relate to the specified Active Record object. Usage example:
#
# <%= div_for(@person, :class => "foo") do %>
# <%= @person.name %>
# <% end %>
#
# produces:
#
#
Joe Bloggs
#
# You can also pass an array of Active Record objects, which will then
# get iterated over and yield each record as an argument for the block.
# For example:
#
# <%= div_for(@people, :class => "foo") do |person| %>
# <%= person.name %>
# <% end %>
#
# produces:
#
#
Joe Bloggs
#
Jane Bloggs
#
def div_for(record, *args, &block)
content_tag_for(:div, record, *args, &block)
end
# content_tag_for creates an HTML element with id and class parameters
# that relate to the specified Active Record object. For example:
#
# <%= content_tag_for(:tr, @person) do %>
#
<%= @person.first_name %>
#
<%= @person.last_name %>
# <% end %>
#
# would produce the following HTML (assuming @person is an instance of
# a Person object, with an id value of 123):
#
#
....
#
# If you require the HTML id attribute to have a prefix, you can specify it:
#
# <%= content_tag_for(:tr, @person, :foo) do %> ...
#
# produces:
#
#
...
#
# You can also pass an array of objects which this method will loop through
# and yield the current object to the supplied block, reducing the need for
# having to iterate through the object (using each) beforehand.
# For example (assuming @people is an array of Person objects):
#
# <%= content_tag_for(:tr, @people) do |person| %>
#
<%= person.first_name %>
#
<%= person.last_name %>
# <% end %>
#
# produces:
#
#
...
#
...
#
# content_tag_for also accepts a hash of options, which will be converted to
# additional HTML attributes. If you specify a :class value, it will be combined
# with the default class name for your object. For example:
#
# <%= content_tag_for(:li, @person, :class => "bar") %>...
#
# produces:
#
#
...
#
def content_tag_for(tag_name, single_or_multiple_records, prefix = nil, options = nil, &block)
if single_or_multiple_records.respond_to?(:to_ary)
single_or_multiple_records.to_ary.map do |single_record|
capture { content_tag_for_single_record(tag_name, single_record, prefix, options, &block) }
end.join("\n").html_safe
else
content_tag_for_single_record(tag_name, single_or_multiple_records, prefix, options, &block)
end
end
private
# Called by content_tag_for internally to render a content tag
# for each record.
def content_tag_for_single_record(tag_name, record, prefix, options, &block)
options, prefix = prefix, nil if prefix.is_a?(Hash)
options = options ? options.dup : {}
options.merge!(:class => "#{dom_class(record, prefix)} #{options[:class]}".strip, :id => dom_id(record, prefix))
if !block_given?
content_tag(tag_name, "", options)
elsif block.arity == 0
content_tag(tag_name, capture(&block), options)
else
content_tag(tag_name, capture(record, &block), options)
end
end
end
end
end
actionpack-3.2.16/lib/action_view/helpers/output_safety_helper.rb 0000644 0001750 0001750 00000002464 12247655372 024604 0 ustar ondrej ondrej require 'active_support/core_ext/string/output_safety'
module ActionView #:nodoc:
# = Action View Raw Output Helper
module Helpers #:nodoc:
module OutputSafetyHelper
# This method outputs without escaping a string. Since escaping tags is
# now default, this can be used when you don't want Rails to automatically
# escape tags. This is not recommended if the data is coming from the user's
# input.
#
# For example:
#
# <%=raw @user.name %>
def raw(stringish)
stringish.to_s.html_safe
end
# This method returns a html safe string similar to what Array#join
# would return. All items in the array, including the supplied separator, are
# html escaped unless they are html safe, and the returned string is marked
# as html safe.
#
# safe_join(["
foo
".html_safe, "
bar
"], " ")
# # => "
foo
<br /><p>bar</p>"
#
# safe_join(["
foo
".html_safe, "
bar
".html_safe], " ".html_safe)
# # => "
foo
bar
"
#
def safe_join(array, sep=$,)
sep ||= "".html_safe
sep = ERB::Util.html_escape(sep)
array.map { |i| ERB::Util.html_escape(i) }.join(sep).html_safe
end
end
end
end actionpack-3.2.16/lib/action_view/helpers/cache_helper.rb 0000644 0001750 0001750 00000004040 12247655372 022724 0 ustar ondrej ondrej module ActionView
# = Action View Cache Helper
module Helpers
module CacheHelper
# This helper exposes a method for caching fragments of a view
# rather than an entire action or page. This technique is useful
# caching pieces like menus, lists of newstopics, static HTML
# fragments, and so on. This method takes a block that contains
# the content you wish to cache.
#
# See ActionController::Caching::Fragments for usage instructions.
#
# ==== Examples
# If you want to cache a navigation menu, you can do following:
#
# <% cache do %>
# <%= render :partial => "menu" %>
# <% end %>
#
# You can also cache static content:
#
# <% cache do %>
#
Hello users! Welcome to our website!
# <% end %>
#
# Static content with embedded ruby content can be cached as
# well:
#
# <% cache do %>
# Topics:
# <%= render :partial => "topics", :collection => @topic_list %>
# Topics listed alphabetically
# <% end %>
def cache(name = {}, options = nil, &block)
if controller.perform_caching
safe_concat(fragment_for(name, options, &block))
else
yield
end
nil
end
private
# TODO: Create an object that has caching read/write on it
def fragment_for(name = {}, options = nil, &block) #:nodoc:
if fragment = controller.read_fragment(name, options)
fragment
else
# VIEW TODO: Make #capture usable outside of ERB
# This dance is needed because Builder can't use capture
pos = output_buffer.length
yield
output_safe = output_buffer.html_safe?
fragment = output_buffer.slice!(pos..-1)
if output_safe
self.output_buffer = output_buffer.class.new(output_buffer)
end
controller.write_fragment(name, fragment, options)
end
end
end
end
end
actionpack-3.2.16/lib/action_view/railtie.rb 0000644 0001750 0001750 00000003447 12247655372 020323 0 ustar ondrej ondrej require "action_view"
require "rails"
module ActionView
# = Action View Railtie
class Railtie < Rails::Railtie
config.action_view = ActiveSupport::OrderedOptions.new
config.action_view.stylesheet_expansions = {}
config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) }
config.action_view.embed_authenticity_token_in_remote_forms = true
initializer "action_view.embed_authenticity_token_in_remote_forms" do |app|
ActiveSupport.on_load(:action_view) do
ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms =
app.config.action_view.delete(:embed_authenticity_token_in_remote_forms)
end
end
initializer "action_view.cache_asset_ids" do |app|
unless app.config.cache_classes
ActiveSupport.on_load(:action_view) do
ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids = false
end
end
end
initializer "action_view.javascript_expansions" do |app|
ActiveSupport.on_load(:action_view) do
ActionView::Helpers::AssetTagHelper.register_javascript_expansion(
app.config.action_view.delete(:javascript_expansions)
)
ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion(
app.config.action_view.delete(:stylesheet_expansions)
)
end
end
initializer "action_view.set_configs" do |app|
ActiveSupport.on_load(:action_view) do
app.config.action_view.each do |k,v|
send "#{k}=", v
end
end
end
initializer "action_view.caching" do |app|
ActiveSupport.on_load(:action_view) do
if app.config.action_view.cache_template_loading.nil?
ActionView::Resolver.caching = app.config.cache_classes
end
end
end
end
end
actionpack-3.2.16/lib/action_view/template/ 0000755 0001750 0001750 00000000000 12247655372 020150 5 ustar ondrej ondrej actionpack-3.2.16/lib/action_view/template/resolver.rb 0000644 0001750 0001750 00000020465 12247655372 022345 0 ustar ondrej ondrej require "pathname"
require "active_support/core_ext/class"
require "active_support/core_ext/io"
require "action_view/template"
module ActionView
# = Action View Resolver
class Resolver
# Keeps all information about view path and builds virtual path.
class Path < String
attr_reader :name, :prefix, :partial, :virtual
alias_method :partial?, :partial
def self.build(name, prefix, partial)
virtual = ""
virtual << "#{prefix}/" unless prefix.empty?
virtual << (partial ? "_#{name}" : name)
new name, prefix, partial, virtual
end
def initialize(name, prefix, partial, virtual)
@name, @prefix, @partial = name, prefix, partial
super(virtual)
end
end
cattr_accessor :caching
self.caching = true
class << self
alias :caching? :caching
end
def initialize
@cached = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2|
h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } }
end
def clear_cache
@cached.clear
end
# Normalizes the arguments and passes it on to find_template.
def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
cached(key, [name, prefix, partial], details, locals) do
find_templates(name, prefix, partial, details)
end
end
private
delegate :caching?, :to => "self.class"
# This is what child classes implement. No defaults are needed
# because Resolver guarantees that the arguments are present and
# normalized.
def find_templates(name, prefix, partial, details)
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details) method"
end
# Helpers that builds a path. Useful for building virtual paths.
def build_path(name, prefix, partial)
Path.build(name, prefix, partial)
end
# Handles templates caching. If a key is given and caching is on
# always check the cache before hitting the resolver. Otherwise,
# it always hits the resolver but check if the resolver is fresher
# before returning it.
def cached(key, path_info, details, locals) #:nodoc:
name, prefix, partial = path_info
locals = locals.map { |x| x.to_s }.sort!
if key && caching?
@cached[key][name][prefix][partial][locals] ||= decorate(yield, path_info, details, locals)
else
fresh = decorate(yield, path_info, details, locals)
return fresh unless key
scope = @cached[key][name][prefix][partial]
cache = scope[locals]
mtime = cache && cache.map(&:updated_at).max
if !mtime || fresh.empty? || fresh.any? { |t| t.updated_at > mtime }
scope[locals] = fresh
else
cache
end
end
end
# Ensures all the resolver information is set in the template.
def decorate(templates, path_info, details, locals) #:nodoc:
cached = nil
templates.each do |t|
t.locals = locals
t.formats = details[:formats] || [:html] if t.formats.empty?
t.virtual_path ||= (cached ||= build_path(*path_info))
end
end
end
# An abstract class that implements a Resolver with path semantics.
class PathResolver < Resolver #:nodoc:
EXTENSIONS = [:locale, :formats, :handlers]
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}"
def initialize(pattern=nil)
@pattern = pattern || DEFAULT_PATTERN
super()
end
private
def find_templates(name, prefix, partial, details)
path = Path.build(name, prefix, partial)
query(path, details, details[:formats])
end
def query(path, details, formats)
query = build_query(path, details)
# deals with case-insensitive file systems.
sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
template_paths = Dir[query].reject { |filename|
File.directory?(filename) ||
!sanitizer[File.dirname(filename)].include?(filename)
}
template_paths.map { |template|
handler, format = extract_handler_and_format(template, formats)
contents = File.binread template
Template.new(contents, File.expand_path(template), handler,
:virtual_path => path.virtual,
:format => format,
:updated_at => mtime(template))
}
end
# Helper for building query glob string based on resolver's pattern.
def build_query(path, details)
query = @pattern.dup
prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
query.gsub!(/\:prefix(\/)?/, prefix)
partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
query.gsub!(/\:action/, partial)
details.each do |ext, variants|
query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
end
File.expand_path(query, @path)
end
def escape_entry(entry)
entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
end
# Returns the file mtime from the filesystem.
def mtime(p)
File.mtime(p)
end
# Extract handler and formats from path. If a format cannot be a found neither
# from the path, or the handler, we should return the array of formats given
# to the resolver.
def extract_handler_and_format(path, default_formats)
pieces = File.basename(path).split(".")
pieces.shift
handler = Template.handler_for_extension(pieces.pop)
format = pieces.last && Mime[pieces.last]
[handler, format]
end
end
# A resolver that loads files from the filesystem. It allows to set your own
# resolving pattern. Such pattern can be a glob string supported by some variables.
#
# ==== Examples
#
# Default pattern, loads views the same way as previous versions of rails, eg. when you're
# looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}`
#
# FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}")
#
# This one allows you to keep files with different formats in seperated subdirectories,
# eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
# `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
#
# FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}")
#
# If you don't specify pattern then the default will be used.
#
# In order to use any of the customized resolvers above in a Rails application, you just need
# to configure ActionController::Base.view_paths in an initializer, for example:
#
# ActionController::Base.view_paths = FileSystemResolver.new(
# Rails.root.join("app/views"),
# ":prefix{/:locale}/:action{.:formats,}{.:handlers,}"
# )
#
# ==== Pattern format and variables
#
# Pattern have to be a valid glob string, and it allows you to use the
# following variables:
#
# * :prefix - usualy the controller path
# * :action - name of the action
# * :locale - possible locale versions
# * :formats - possible request formats (for example html, json, xml...)
# * :handlers - possible handlers (for example erb, haml, builder...)
#
class FileSystemResolver < PathResolver
def initialize(path, pattern=nil)
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
super(pattern)
@path = File.expand_path(path)
end
def to_s
@path.to_s
end
alias :to_path :to_s
def eql?(resolver)
self.class.equal?(resolver.class) && to_path == resolver.to_path
end
alias :== :eql?
end
# An Optimized resolver for Rails' most common case.
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
def build_query(path, details)
exts = EXTENSIONS.map { |ext| details[ext] }
query = escape_entry(File.join(@path, path))
query + exts.map { |ext|
"{#{ext.compact.uniq.map { |e| ".#{e}," }.join}}"
}.join
end
end
# The same as FileSystemResolver but does not allow templates to store
# a virtual path since it is invalid for such resolvers.
class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
def self.instances
[new(""), new("/")]
end
def decorate(*)
super.each { |t| t.virtual_path = nil }
end
end
end
actionpack-3.2.16/lib/action_view/template/handlers/ 0000755 0001750 0001750 00000000000 12247655372 021750 5 ustar ondrej ondrej actionpack-3.2.16/lib/action_view/template/handlers/builder.rb 0000644 0001750 0001750 00000001051 12247655372 023720 0 ustar ondrej ondrej module ActionView
module Template::Handlers
class Builder
# Default format used by Builder.
class_attribute :default_format
self.default_format = Mime::XML
def call(template)
require_engine
"xml = ::Builder::XmlMarkup.new(:indent => 2);" +
"self.output_buffer = xml.target!;" +
template.source +
";xml.target!;"
end
protected
def require_engine
@required ||= begin
require "builder"
true
end
end
end
end
end
actionpack-3.2.16/lib/action_view/template/handlers/erb.rb 0000644 0001750 0001750 00000007212 12247655372 023047 0 ustar ondrej ondrej require 'action_dispatch/http/mime_type'
require 'active_support/core_ext/class/attribute'
require 'erubis'
module ActionView
class Template
module Handlers
class Erubis < ::Erubis::Eruby
def add_preamble(src)
src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
end
def add_text(src, text)
return if text.empty?
src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
end
# Erubis toggles <%= and <%== behavior when escaping is enabled.
# We override to always treat <%== as escaped.
def add_expr(src, code, indicator)
case indicator
when '=='
add_expr_escaped(src, code)
else
super
end
end
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
def add_expr_literal(src, code)
if code =~ BLOCK_EXPR
src << '@output_buffer.append= ' << code
else
src << '@output_buffer.append= (' << code << ');'
end
end
def add_expr_escaped(src, code)
if code =~ BLOCK_EXPR
src << "@output_buffer.safe_append= " << code
else
src << "@output_buffer.safe_concat((" << code << ").to_s);"
end
end
def add_postamble(src)
src << '@output_buffer.to_s'
end
end
class ERB
# Specify trim mode for the ERB compiler. Defaults to '-'.
# See ERB documentation for suitable values.
class_attribute :erb_trim_mode
self.erb_trim_mode = '-'
# Default implementation used.
class_attribute :erb_implementation
self.erb_implementation = Erubis
# Do not escape templates of these mime types.
class_attribute :escape_whitelist
self.escape_whitelist = ["text/plain"]
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
def self.call(template)
new.call(template)
end
def supports_streaming?
true
end
def handles_encoding?
true
end
def call(template)
if template.source.encoding_aware?
# First, convert to BINARY, so in case the encoding is
# wrong, we can still find an encoding tag
# (<%# encoding %>) inside the String using a regular
# expression
template_source = template.source.dup.force_encoding("BINARY")
erb = template_source.gsub(ENCODING_TAG, '')
encoding = $2
erb.force_encoding valid_encoding(template.source.dup, encoding)
# Always make sure we return a String in the default_internal
erb.encode!
else
erb = template.source.dup
end
self.class.erb_implementation.new(
erb,
:escape => (self.class.escape_whitelist.include? template.mime_type),
:trim => (self.class.erb_trim_mode == "-")
).src
end
private
def valid_encoding(string, encoding)
# If a magic encoding comment was found, tag the
# String with this encoding. This is for a case
# where the original String was assumed to be,
# for instance, UTF-8, but a magic comment
# proved otherwise
string.force_encoding(encoding) if encoding
# If the String is valid, return the encoding we found
return string.encoding if string.valid_encoding?
# Otherwise, raise an exception
raise WrongEncodingError.new(string, string.encoding)
end
end
end
end
end
actionpack-3.2.16/lib/action_view/template/handlers.rb 0000644 0001750 0001750 00000003353 12247655372 022301 0 ustar ondrej ondrej module ActionView #:nodoc:
# = Action View Template Handlers
class Template
module Handlers #:nodoc:
autoload :ERB, 'action_view/template/handlers/erb'
autoload :Builder, 'action_view/template/handlers/builder'
def self.extended(base)
base.register_default_template_handler :erb, ERB.new
base.register_template_handler :builder, Builder.new
end
@@template_handlers = {}
@@default_template_handlers = nil
def self.extensions
@@template_extensions ||= @@template_handlers.keys
end
# Register a class that knows how to handle template files with the given
# extension. This can be used to implement new template types.
# The constructor for the class must take the ActiveView::Base instance
# as a parameter, and the class must implement a +render+ method that
# takes the contents of the template to render as well as the Hash of
# local assigns available to the template. The +render+ method ought to
# return the rendered template as a string.
def register_template_handler(extension, klass)
@@template_handlers[extension.to_sym] = klass
@@template_extensions = nil
end
def template_handler_extensions
@@template_handlers.keys.map {|key| key.to_s }.sort
end
def registered_template_handler(extension)
extension && @@template_handlers[extension.to_sym]
end
def register_default_template_handler(extension, klass)
register_template_handler(extension, klass)
@@default_template_handlers = klass
end
def handler_for_extension(extension)
registered_template_handler(extension) || @@default_template_handlers
end
end
end
end
actionpack-3.2.16/lib/action_view/template/text.rb 0000644 0001750 0001750 00000001053 12247655372 021460 0 ustar ondrej ondrej module ActionView #:nodoc:
# = Action View Text Template
class Template
class Text < String #:nodoc:
attr_accessor :mime_type
def initialize(string, mime_type = nil)
super(string.to_s)
@mime_type = Mime[mime_type] || mime_type if mime_type
@mime_type ||= Mime::TEXT
end
def identifier
'text template'
end
def inspect
'text template'
end
def render(*args)
to_s
end
def formats
[@mime_type.to_sym]
end
end
end
end
actionpack-3.2.16/lib/action_view/template/error.rb 0000644 0001750 0001750 00000007115 12247655372 021632 0 ustar ondrej ondrej require "active_support/core_ext/array/wrap"
require "active_support/core_ext/enumerable"
module ActionView
# = Action View Errors
class ActionViewError < StandardError #:nodoc:
end
class EncodingError < StandardError #:nodoc:
end
class WrongEncodingError < EncodingError #:nodoc:
def initialize(string, encoding)
@string, @encoding = string, encoding
end
def message
@string.force_encoding("BINARY")
"Your template was not saved as valid #{@encoding}. Please " \
"either specify #{@encoding} as the encoding for your template " \
"in your text editor, or mark the template with its " \
"encoding by inserting the following as the first line " \
"of the template:\n\n# encoding: .\n\n" \
"The source of your template was:\n\n#{@string}"
end
end
class MissingTemplate < ActionViewError #:nodoc:
attr_reader :path
def initialize(paths, path, prefixes, partial, details, *)
@path = path
prefixes = Array.wrap(prefixes)
template_type = if partial
"partial"
elsif path =~ /layouts/i
'layout'
else
'template'
end
searched_paths = prefixes.map { |prefix| [prefix, path].join("/") }
out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n"
out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join
super out
end
end
class Template
# The Template::Error exception is raised when the compilation or rendering of the template
# fails. This exception then gathers a bunch of intimate details and uses it to report a
# precise exception message.
class Error < ActionViewError #:nodoc:
SOURCE_CODE_RADIUS = 3
attr_reader :original_exception, :backtrace
def initialize(template, assigns, original_exception)
super(original_exception.message)
@template, @assigns, @original_exception = template, assigns.dup, original_exception
@sub_templates = nil
@backtrace = original_exception.backtrace
end
def file_name
@template.identifier
end
def sub_template_message
if @sub_templates
"Trace of template inclusion: " +
@sub_templates.collect { |template| template.inspect }.join(", ")
else
""
end
end
def source_extract(indentation = 0)
return unless num = line_number
num = num.to_i
source_code = @template.source.split("\n")
start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
indent = ' ' * indentation
line_counter = start_on_line
return unless source_code = source_code[start_on_line..end_on_line]
source_code.sum do |line|
line_counter += 1
"#{indent}#{line_counter}: #{line}\n"
end
end
def sub_template_of(template_path)
@sub_templates ||= []
@sub_templates << template_path
end
def line_number
@line_number ||=
if file_name
regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
$1 if message =~ regexp || backtrace.find { |line| line =~ regexp }
end
end
def annoted_source_code
source_extract(4)
end
private
def source_location
if line_number
"on line ##{line_number} of "
else
'in '
end + file_name
end
end
end
TemplateError = Template::Error
end
actionpack-3.2.16/lib/action_view/template.rb 0000644 0001750 0001750 00000031541 12247655372 020501 0 ustar ondrej ondrej require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/kernel/singleton_class'
require 'thread'
module ActionView
# = Action View Template
class Template
extend ActiveSupport::Autoload
# === Encodings in ActionView::Template
#
# ActionView::Template is one of a few sources of potential
# encoding issues in Rails. This is because the source for
# templates are usually read from disk, and Ruby (like most
# encoding-aware programming languages) assumes that the
# String retrieved through File IO is encoded in the
# default_external encoding. In Rails, the default
# default_external encoding is UTF-8.
#
# As a result, if a user saves their template as ISO-8859-1
# (for instance, using a non-Unicode-aware text editor),
# and uses characters outside of the ASCII range, their
# users will see diamonds with question marks in them in
# the browser.
#
# For the rest of this documentation, when we say "UTF-8",
# we mean "UTF-8 or whatever the default_internal encoding
# is set to". By default, it will be UTF-8.
#
# To mitigate this problem, we use a few strategies:
# 1. If the source is not valid UTF-8, we raise an exception
# when the template is compiled to alert the user
# to the problem.
# 2. The user can specify the encoding using Ruby-style
# encoding comments in any template engine. If such
# a comment is supplied, Rails will apply that encoding
# to the resulting compiled source returned by the
# template handler.
# 3. In all cases, we transcode the resulting String to
# the UTF-8.
#
# This means that other parts of Rails can always assume
# that templates are encoded in UTF-8, even if the original
# source of the template was not UTF-8.
#
# From a user's perspective, the easiest thing to do is
# to save your templates as UTF-8. If you do this, you
# do not need to do anything else for things to "just work".
#
# === Instructions for template handlers
#
# The easiest thing for you to do is to simply ignore
# encodings. Rails will hand you the template source
# as the default_internal (generally UTF-8), raising
# an exception for the user before sending the template
# to you if it could not determine the original encoding.
#
# For the greatest simplicity, you can support only
# UTF-8 as the default_internal. This means
# that from the perspective of your handler, the
# entire pipeline is just UTF-8.
#
# === Advanced: Handlers with alternate metadata sources
#
# If you want to provide an alternate mechanism for
# specifying encodings (like ERB does via <%# encoding: ... %>),
# you may indicate that you will handle encodings yourself
# by implementing self.handles_encoding?
# on your handler.
#
# If you do, Rails will not try to encode the String
# into the default_internal, passing you the unaltered
# bytes tagged with the assumed encoding (from
# default_external).
#
# In this case, make sure you return a String from
# your handler encoded in the default_internal. Since
# you are handling out-of-band metadata, you are
# also responsible for alerting the user to any
# problems with converting the user's data to
# the default_internal.
#
# To do so, simply raise the raise +WrongEncodingError+
# as follows:
#
# raise WrongEncodingError.new(
# problematic_string,
# expected_encoding
# )
eager_autoload do
autoload :Error
autoload :Handlers
autoload :Text
end
extend Template::Handlers
attr_accessor :locals, :formats, :virtual_path
attr_reader :source, :identifier, :handler, :original_encoding, :updated_at
# This finalizer is needed (and exactly with a proc inside another proc)
# otherwise templates leak in development.
Finalizer = proc do |method_name, mod|
proc do
mod.module_eval do
remove_possible_method method_name
end
end
end
def initialize(source, identifier, handler, details)
format = details[:format] || (handler.default_format if handler.respond_to?(:default_format))
@source = source
@identifier = identifier
@handler = handler
@compiled = false
@original_encoding = nil
@locals = details[:locals] || []
@virtual_path = details[:virtual_path]
@updated_at = details[:updated_at] || Time.now
@formats = Array.wrap(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f }
@compile_mutex = Mutex.new
end
# Returns if the underlying handler supports streaming. If so,
# a streaming buffer *may* be passed when it start rendering.
def supports_streaming?
handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
end
# Render a template. If the template was not compiled yet, it is done
# exactly before rendering.
#
# This method is instrumented as "!render_template.action_view". Notice that
# we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
def render(view, locals, buffer=nil, &block)
ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do
compile!(view)
view.send(method_name, locals, buffer, &block)
end
rescue Exception => e
handle_render_error(view, e)
end
def mime_type
@mime_type ||= Mime::Type.lookup_by_extension(@formats.first.to_s) if @formats.first
end
# Receives a view object and return a template similar to self by using @virtual_path.
#
# This method is useful if you have a template object but it does not contain its source
# anymore since it was already compiled. In such cases, all you need to do is to call
# refresh passing in the view object.
#
# Notice this method raises an error if the template to be refreshed does not have a
# virtual path set (true just for inline templates).
def refresh(view)
raise "A template needs to have a virtual path in order to be refreshed" unless @virtual_path
lookup = view.lookup_context
pieces = @virtual_path.split("/")
name = pieces.pop
partial = !!name.sub!(/^_/, "")
lookup.disable_cache do
lookup.find_template(name, [ pieces.join('/') ], partial, @locals)
end
end
def inspect
@inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier
end
# This method is responsible for properly setting the encoding of the
# source. Until this point, we assume that the source is BINARY data.
# If no additional information is supplied, we assume the encoding is
# the same as Encoding.default_external.
#
# The user can also specify the encoding via a comment on the first
# line of the template (# encoding: NAME-OF-ENCODING). This will work
# with any template engine, as we process out the encoding comment
# before passing the source on to the template engine, leaving a
# blank line in its stead.
def encode!
return unless source.encoding_aware? && source.encoding == Encoding::BINARY
# Look for # encoding: *. If we find one, we'll encode the
# String in that encoding, otherwise, we'll use the
# default external encoding.
if source.sub!(/\A#{ENCODING_FLAG}/, '')
encoding = magic_encoding = $1
else
encoding = Encoding.default_external
end
# Tag the source with the default external encoding
# or the encoding specified in the file
source.force_encoding(encoding)
# If the user didn't specify an encoding, and the handler
# handles encodings, we simply pass the String as is to
# the handler (with the default_external tag)
if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
source
# Otherwise, if the String is valid in the encoding,
# encode immediately to default_internal. This means
# that if a handler doesn't handle encodings, it will
# always get Strings in the default_internal
elsif source.valid_encoding?
source.encode!
# Otherwise, since the String is invalid in the encoding
# specified, raise an exception
else
raise WrongEncodingError.new(source, encoding)
end
end
protected
# Compile a template. This method ensures a template is compiled
# just once and removes the source after it is compiled.
def compile!(view) #:nodoc:
return if @compiled
# Templates can be used concurrently in threaded environments
# so compilation and any instance variable modification must
# be synchronized
@compile_mutex.synchronize do
# Any thread holding this lock will be compiling the template needed
# by the threads waiting. So re-check the @compiled flag to avoid
# re-compilation
return if @compiled
if view.is_a?(ActionView::CompiledTemplates)
mod = ActionView::CompiledTemplates
else
mod = view.singleton_class
end
compile(view, mod)
# Just discard the source if we have a virtual path. This
# means we can get the template back.
@source = nil if @virtual_path
@compiled = true
end
end
# Among other things, this method is responsible for properly setting
# the encoding of the compiled template.
#
# If the template engine handles encodings, we send the encoded
# String to the engine without further processing. This allows
# the template engine to support additional mechanisms for
# specifying the encoding. For instance, ERB supports <%# encoding: %>
#
# Otherwise, after we figure out the correct encoding, we then
# encode the source into Encoding.default_internal.
# In general, this means that templates will be UTF-8 inside of Rails,
# regardless of the original source encoding.
def compile(view, mod) #:nodoc:
encode!
method_name = self.method_name
code = @handler.call(self)
# Make sure that the resulting String to be evalled is in the
# encoding of the code
source = <<-end_src
def #{method_name}(local_assigns, output_buffer)
_old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
ensure
@virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
end
end_src
if source.encoding_aware?
# Make sure the source is in the encoding of the returned code
source.force_encoding(code.encoding)
# In case we get back a String from a handler that is not in
# BINARY or the default_internal, encode it to the default_internal
source.encode!
# Now, validate that the source we got back from the template
# handler is valid in the default_internal. This is for handlers
# that handle encoding but screw up
unless source.valid_encoding?
raise WrongEncodingError.new(@source, Encoding.default_internal)
end
end
begin
mod.module_eval(source, identifier, 0)
ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
rescue Exception => e # errors from template code
if logger = (view && view.logger)
logger.debug "ERROR: compiling #{method_name} RAISED #{e}"
logger.debug "Function body: #{source}"
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
end
raise ActionView::Template::Error.new(self, {}, e)
end
end
def handle_render_error(view, e) #:nodoc:
if e.is_a?(Template::Error)
e.sub_template_of(self)
raise e
else
assigns = view.respond_to?(:assigns) ? view.assigns : {}
template = self
unless template.source
template = refresh(view)
template.encode!
end
raise Template::Error.new(template, assigns, e)
end
end
def locals_code #:nodoc:
@locals.map { |key| "#{key} = local_assigns[:#{key}];" }.join
end
def method_name #:nodoc:
@method_name ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".gsub('-', "_")
end
def identifier_method_name #:nodoc:
inspect.gsub(/[^a-z_]/, '_')
end
end
end
actionpack-3.2.16/lib/action_view/context.rb 0000644 0001750 0001750 00000002243 12247655372 020347 0 ustar ondrej ondrej module ActionView
module CompiledTemplates #:nodoc:
# holds compiled template code
end
# = Action View Context
#
# Action View contexts are supplied to Action Controller to render template.
# The default Action View context is ActionView::Base.
#
# In order to work with ActionController, a Context must just include this module.
# The initialization of the variables used by the context (@output_buffer, @view_flow,
# and @virtual_path) is responsibility of the object that includes this module
# (although you can call _prepare_context defined below).
module Context
include CompiledTemplates
attr_accessor :output_buffer, :view_flow
# Prepares the context by setting the appropriate instance variables.
# :api: plugin
def _prepare_context
@view_flow = OutputFlow.new
@output_buffer = nil
@virtual_path = nil
end
# Encapsulates the interaction with the view flow so it
# returns the correct buffer on yield. This is usually
# overwriten by helpers to add more behavior.
# :api: plugin
def _layout_for(name=nil)
name ||= :layout
view_flow.get(name).html_safe
end
end
end actionpack-3.2.16/lib/action_view/test_case.rb 0000644 0001750 0001750 00000014631 12247655372 020641 0 ustar ondrej ondrej require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/module/remove_method'
require 'action_controller'
require 'action_controller/test_case'
require 'action_view'
module ActionView
# = Action View Test Case
class TestCase < ActiveSupport::TestCase
class TestController < ActionController::Base
include ActionDispatch::TestProcess
attr_accessor :request, :response, :params
class << self
attr_writer :controller_path
end
def controller_path=(path)
self.class.controller_path=(path)
end
def initialize
super
self.class.controller_path = ""
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@request.env.delete('PATH_INFO')
@params = {}
end
end
module Behavior
extend ActiveSupport::Concern
include ActionDispatch::Assertions, ActionDispatch::TestProcess
include ActionController::TemplateAssertions
include ActionView::Context
include ActionDispatch::Routing::PolymorphicRoutes
include ActionController::RecordIdentifier
include AbstractController::Helpers
include ActionView::Helpers
delegate :lookup_context, :to => :controller
attr_accessor :controller, :output_buffer, :rendered
module ClassMethods
def tests(helper_class)
case helper_class
when String, Symbol
self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize
when Module
self.helper_class = helper_class
end
end
def determine_default_helper_class(name)
mod = name.sub(/Test$/, '').constantize
mod.is_a?(Class) ? nil : mod
rescue NameError
nil
end
def helper_method(*methods)
# Almost a duplicate from ActionController::Helpers
methods.flatten.each do |method|
_helpers.module_eval <<-end_eval
def #{method}(*args, &block) # def current_user(*args, &block)
_test_case.send(%(#{method}), *args, &block) # _test_case.send(%(current_user), *args, &block)
end # end
end_eval
end
end
attr_writer :helper_class
def helper_class
@helper_class ||= determine_default_helper_class(name)
end
def new(*)
include_helper_modules!
super
end
private
def include_helper_modules!
helper(helper_class) if helper_class
include _helpers
end
end
def setup_with_controller
@controller = ActionView::TestCase::TestController.new
@request = @controller.request
@output_buffer = ActiveSupport::SafeBuffer.new
@rendered = ''
make_test_case_available_to_view!
say_no_to_protect_against_forgery!
end
def config
@controller.config if @controller.respond_to?(:config)
end
def render(options = {}, local_assigns = {}, &block)
view.assign(view_assigns)
@rendered << output = view.render(options, local_assigns, &block)
output
end
def locals
@locals ||= {}
end
included do
setup :setup_with_controller
end
private
# Support the selector assertions
#
# Need to experiment if this priority is the best one: rendered => output_buffer
def response_from_page
HTML::Document.new(@rendered.blank? ? @output_buffer : @rendered).root
end
def say_no_to_protect_against_forgery!
_helpers.module_eval do
remove_possible_method :protect_against_forgery?
def protect_against_forgery?
false
end
end
end
def make_test_case_available_to_view!
test_case_instance = self
_helpers.module_eval do
unless private_method_defined?(:_test_case)
define_method(:_test_case) { test_case_instance }
private :_test_case
end
end
end
module Locals
attr_accessor :locals
def render(options = {}, local_assigns = {})
case options
when Hash
if block_given?
locals[options[:layout]] = options[:locals]
elsif options.key?(:partial)
locals[options[:partial]] = options[:locals]
end
else
locals[options] = local_assigns
end
super
end
end
# The instance of ActionView::Base that is used by +render+.
def view
@view ||= begin
view = @controller.view_context
view.singleton_class.send :include, _helpers
view.extend(Locals)
view.locals = self.locals
view.output_buffer = self.output_buffer
view
end
end
alias_method :_view, :view
INTERNAL_IVARS = %w{
@__name__
@__io__
@_assertion_wrapped
@_assertions
@_result
@_routes
@controller
@layouts
@locals
@method_name
@output_buffer
@partials
@passed
@rendered
@request
@routes
@templates
@options
@test_passed
@view
@view_context_class
}
def _user_defined_ivars
instance_variables.map(&:to_s) - INTERNAL_IVARS
end
# Returns a Hash of instance variables and their values, as defined by
# the user in the test case, which are then assigned to the view being
# rendered. This is generally intended for internal use and extension
# frameworks.
def view_assigns
Hash[_user_defined_ivars.map do |var|
[var[1, var.length].to_sym, instance_variable_get(var)]
end]
end
def _routes
@controller._routes if @controller.respond_to?(:_routes)
end
def method_missing(selector, *args)
if @controller.respond_to?(:_routes) &&
( @controller._routes.named_routes.helpers.include?(selector) ||
@controller._routes.mounted_helpers.method_defined?(selector) )
@controller.__send__(selector, *args)
else
super
end
end
end
include Behavior
end
end
actionpack-3.2.16/lib/action_view/flows.rb 0000644 0001750 0001750 00000003424 12247655372 020017 0 ustar ondrej ondrej require 'active_support/core_ext/string/output_safety'
module ActionView
class OutputFlow #:nodoc:
attr_reader :content
def initialize
@content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
end
# Called by _layout_for to read stored values.
def get(key)
@content[key]
end
# Called by each renderer object to set the layout contents.
def set(key, value)
@content[key] = value
end
# Called by content_for
def append(key, value)
@content[key] << value
end
# Called by provide
def append!(key, value)
@content[key] << value
end
end
class StreamingFlow < OutputFlow #:nodoc:
def initialize(view, fiber)
@view = view
@parent = nil
@child = view.output_buffer
@content = view.view_flow.content
@fiber = fiber
@root = Fiber.current.object_id
end
# Try to get an stored content. If the content
# is not available and we are inside the layout
# fiber, we set that we are waiting for the given
# key and yield.
def get(key)
return super if @content.key?(key)
if inside_fiber?
view = @view
begin
@waiting_for = key
view.output_buffer, @parent = @child, view.output_buffer
Fiber.yield
ensure
@waiting_for = nil
view.output_buffer, @child = @parent, view.output_buffer
end
end
super
end
# Appends the contents for the given key. This is called
# by provides and resumes back to the fiber if it is
# the key it is waiting for.
def append!(key, value)
super
@fiber.resume if @waiting_for == key
end
private
def inside_fiber?
Fiber.current.object_id != @root
end
end
end actionpack-3.2.16/lib/action_view/buffers.rb 0000644 0001750 0001750 00000001417 12247655372 020321 0 ustar ondrej ondrej require 'active_support/core_ext/string/output_safety'
module ActionView
class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
def initialize(*)
super
encode! if encoding_aware?
end
def <<(value)
super(value.to_s)
end
alias :append= :<<
alias :safe_append= :safe_concat
end
class StreamingBuffer #:nodoc:
def initialize(block)
@block = block
end
def <<(value)
value = value.to_s
value = ERB::Util.h(value) unless value.html_safe?
@block.call(value)
end
alias :concat :<<
alias :append= :<<
def safe_concat(value)
@block.call(value.to_s)
end
alias :safe_append= :safe_concat
def html_safe?
true
end
def html_safe
self
end
end
end
actionpack-3.2.16/lib/action_view/asset_paths.rb 0000644 0001750 0001750 00000010567 12247655372 021211 0 ustar ondrej ondrej require 'zlib'
require 'active_support/core_ext/file'
require 'action_controller/metal/exceptions'
module ActionView
class AssetPaths #:nodoc:
attr_reader :config, :controller
def initialize(config, controller = nil)
@config = config
@controller = controller
end
# Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched.
# Prefix with /dir/ if lacking a leading +/+. Account for relative URL
# roots. Rewrite the asset path for cache-busting asset ids. Include
# asset host, if configured, with the correct request protocol.
#
# When :relative (default), the protocol will be determined by the client using current protocol
# When :request, the protocol will be the request protocol
# Otherwise, the protocol is used (E.g. :http, :https, etc)
def compute_public_path(source, dir, options = {})
source = source.to_s
return source if is_uri?(source)
source = rewrite_extension(source, dir, options[:ext]) if options[:ext]
source = rewrite_asset_path(source, dir, options)
source = rewrite_relative_url_root(source, relative_url_root)
source = rewrite_host_and_protocol(source, options[:protocol])
source
end
# Return the filesystem path for the source
def compute_source_path(source, dir, ext)
source = rewrite_extension(source, dir, ext) if ext
sources = []
sources << config.assets_dir
sources << dir unless source[0] == ?/
sources << source
File.join(sources)
end
def is_uri?(path)
path =~ %r{^[-a-z]+://|^(?:cid|data):|^//}i
end
private
def rewrite_extension(source, dir, ext)
raise NotImplementedError
end
def rewrite_asset_path(source, path = nil)
raise NotImplementedError
end
def rewrite_relative_url_root(source, relative_url_root)
relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source
end
def has_request?
controller.respond_to?(:request)
end
def rewrite_host_and_protocol(source, protocol = nil)
host = compute_asset_host(source)
if host && !is_uri?(host)
if (protocol || default_protocol) == :request && !has_request?
host = nil
else
host = "#{compute_protocol(protocol)}#{host}"
end
end
host ? "#{host}#{source}" : source
end
def compute_protocol(protocol)
protocol ||= default_protocol
case protocol
when :relative
"//"
when :request
unless @controller
invalid_asset_host!("The protocol requested was :request. Consider using :relative instead.")
end
@controller.request.protocol
else
"#{protocol}://"
end
end
def default_protocol
@config.default_asset_host_protocol || (has_request? ? :request : :relative)
end
def invalid_asset_host!(help_message)
raise ActionController::RoutingError, "This asset host cannot be computed without a request in scope. #{help_message}"
end
# Pick an asset host for this source. Returns +nil+ if no host is set,
# the host if no wildcard is set, the host interpolated with the
# numbers 0-3 if it contains %d (the number is the source hash mod 4),
# or the value returned from invoking call on an object responding to call
# (proc or otherwise).
def compute_asset_host(source)
if host = asset_host_config
if host.respond_to?(:call)
args = [source]
arity = arity_of(host)
if (arity > 1 || arity < -2) && !has_request?
invalid_asset_host!("Remove the second argument to your asset_host Proc if you do not need the request, or make it optional.")
end
args << current_request if (arity > 1 || arity < 0) && has_request?
host.call(*args)
else
(host =~ /%d/) ? host % (Zlib.crc32(source) % 4) : host
end
end
end
def relative_url_root
config.relative_url_root
end
def asset_host_config
config.asset_host
end
# Returns the current request if one exists.
def current_request
controller.request if has_request?
end
# Returns the arity of a callable
def arity_of(callable)
callable.respond_to?(:arity) ? callable.arity : callable.method(:call).arity
end
end
end
actionpack-3.2.16/lib/action_view/renderer/ 0000755 0001750 0001750 00000000000 12247655372 020143 5 ustar ondrej ondrej actionpack-3.2.16/lib/action_view/renderer/renderer.rb 0000644 0001750 0001750 00000003126 12247655372 022300 0 ustar ondrej ondrej module ActionView
# This is the main entry point for rendering. It basically delegates
# to other objects like TemplateRenderer and PartialRenderer which
# actually renders the template.
class Renderer
attr_accessor :lookup_context
def initialize(lookup_context)
@lookup_context = lookup_context
end
# Main render entry point shared by AV and AC.
def render(context, options)
if options.key?(:partial)
render_partial(context, options)
else
render_template(context, options)
end
end
# Render but returns a valid Rack body. If fibers are defined, we return
# a streaming body that renders the template piece by piece.
#
# Note that partials are not supported to be rendered with streaming,
# so in such cases, we just wrap them in an array.
def render_body(context, options)
if options.key?(:partial)
[render_partial(context, options)]
else
StreamingTemplateRenderer.new(@lookup_context).render(context, options)
end
end
# Direct accessor to template rendering.
def render_template(context, options) #:nodoc:
_template_renderer.render(context, options)
end
# Direct access to partial rendering.
def render_partial(context, options, &block) #:nodoc:
_partial_renderer.render(context, options, block)
end
private
def _template_renderer #:nodoc:
@_template_renderer ||= TemplateRenderer.new(@lookup_context)
end
def _partial_renderer #:nodoc:
@_partial_renderer ||= PartialRenderer.new(@lookup_context)
end
end
end
actionpack-3.2.16/lib/action_view/renderer/template_renderer.rb 0000644 0001750 0001750 00000006503 12247655372 024175 0 ustar ondrej ondrej require 'active_support/core_ext/object/try'
require 'active_support/core_ext/array/wrap'
module ActionView
class TemplateRenderer < AbstractRenderer #:nodoc:
def render(context, options)
@view = context
@details = extract_details(options)
extract_format(options[:file] || options[:template], @details)
template = determine_template(options)
context = @lookup_context
unless context.rendered_format
context.formats = template.formats unless template.formats.empty?
context.rendered_format = context.formats.first
end
render_template(template, options[:layout], options[:locals])
end
# Determine the template to be rendered using the given options.
def determine_template(options) #:nodoc:
keys = options[:locals].try(:keys) || []
if options.key?(:text)
Template::Text.new(options[:text], formats.try(:first))
elsif options.key?(:file)
with_fallbacks { find_template(options[:file], nil, false, keys, @details) }
elsif options.key?(:inline)
handler = Template.handler_for_extension(options[:type] || "erb")
Template.new(options[:inline], "inline template", handler, :locals => keys)
elsif options.key?(:template)
options[:template].respond_to?(:render) ?
options[:template] : find_template(options[:template], options[:prefixes], false, keys, @details)
else
raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file or :text option."
end
end
# Renders the given template. An string representing the layout can be
# supplied as well.
def render_template(template, layout_name = nil, locals = {}) #:nodoc:
view, locals = @view, locals || {}
render_with_layout(layout_name, locals) do |layout|
instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
template.render(view, locals) { |*name| view._layout_for(*name) }
end
end
end
def render_with_layout(path, locals) #:nodoc:
layout = path && find_layout(path, locals.keys)
content = yield(layout)
if layout
view = @view
view.view_flow.set(:layout, content)
layout.render(view, locals){ |*name| view._layout_for(*name) }
else
content
end
end
# This is the method which actually finds the layout using details in the lookup
# context object. If no layout is found, it checks if at least a layout with
# the given name exists across all details before raising the error.
def find_layout(layout, keys)
with_layout_format { resolve_layout(layout, keys) }
end
def resolve_layout(layout, keys)
case layout
when String
begin
if layout =~ /^\//
with_fallbacks { find_template(layout, nil, false, keys, @details) }
else
find_template(layout, nil, false, keys, @details)
end
rescue ActionView::MissingTemplate
all_details = @details.merge(:formats => @lookup_context.default_formats)
raise unless template_exists?(layout, nil, false, keys, all_details)
end
when Proc
resolve_layout(layout.call, keys)
when FalseClass
nil
else
layout
end
end
end
end
actionpack-3.2.16/lib/action_view/renderer/abstract_renderer.rb 0000644 0001750 0001750 00000002213 12247655372 024157 0 ustar ondrej ondrej module ActionView
class AbstractRenderer #:nodoc:
delegate :find_template, :template_exists?, :with_fallbacks, :update_details,
:with_layout_format, :formats, :to => :@lookup_context
def initialize(lookup_context)
@lookup_context = lookup_context
end
def render
raise NotImplementedError
end
protected
def extract_details(options)
details = {}
@lookup_context.registered_details.each do |key|
next unless value = options[key]
details[key] = Array.wrap(value)
end
details
end
def extract_format(value, details)
if value.is_a?(String) && value.sub!(formats_regexp, "")
ActiveSupport::Deprecation.warn "Passing the format in the template name is deprecated. " \
"Please pass render with :formats => [:#{$1}] instead.", caller
details[:formats] ||= [$1.to_sym]
end
end
def formats_regexp
@@formats_regexp ||= /\.(#{Mime::SET.symbols.join('|')})$/
end
def instrument(name, options={})
ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield }
end
end
end
actionpack-3.2.16/lib/action_view/renderer/partial_renderer.rb 0000644 0001750 0001750 00000034577 12247655372 024032 0 ustar ondrej ondrej require 'active_support/core_ext/object/blank'
module ActionView
# = Action View Partials
#
# 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.html.erb".
#
# In another template for Advertiser#buy, we could have:
#
# <%= render :partial => "account", :locals => { :account => @buyer } %>
#
# <% @advertisements.each do |ad| %>
# <%= render :partial => "ad", :locals => { :ad => ad } %>
# <% end %>
#
# This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then
# render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display.
#
# == The :as and :object options
#
# By default ActionView::PartialRenderer doesn't have any local variables.
# The :object option can be used to pass an object to the partial. For instance:
#
# <%= render :partial => "account", :object => @buyer %>
#
# would provide the +@buyer+ object to the partial, available under the local variable +account+ and is
# equivalent to:
#
# <%= render :partial => "account", :locals => { :account => @buyer } %>
#
# With the :as option we can specify a different name for said local variable. For example, if we
# wanted it to be +user+ instead of +account+ we'd do:
#
# <%= render :partial => "account", :object => @buyer, :as => 'user' %>
#
# This is equivalent to
#
# <%= render :partial => "account", :locals => { :user => @buyer } %>
#
# == 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:
#
# <%= render :partial => "ad", :collection => @advertisements %>
#
# This will render "advertiser/_ad.html.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+.
#
# The :as option may be used when rendering partials.
#
# You can specify a partial to be rendered between elements via the :spacer_template option.
# The following example will render advertiser/_ad_divider.html.erb between each ad partial:
#
# <%= render :partial => "ad", :collection => @advertisements, :spacer_template => "ad_divider" %>
#
# If the given :collection is nil or empty, render will return nil. This will allow you
# to specify a text which will displayed instead by using this form:
#
# <%= render(:partial => "ad", :collection => @advertisements) || "There's no ad to be displayed" %>
#
# 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
#
# Two controllers can share a set of partials and render them like this:
#
# <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %>
#
# This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
#
# == Rendering objects that respond to `to_partial_path`
#
# Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
# and pick the proper path by checking `to_proper_path` method. If the object passed to render is a collection,
# all objects must return the same path.
#
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
# # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
# <%= render :partial => @account %>
#
# # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
# # that's why we can replace:
# # <%= render :partial => "posts/post", :collection => @posts %>
# <%= render :partial => @posts %>
#
# == Rendering the default case
#
# If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
# defaults of render to render partials. Examples:
#
# # Instead of <%= render :partial => "account" %>
# <%= render "account" %>
#
# # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %>
# <%= render "account", :account => @buyer %>
#
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
# # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
# <%= render @account %>
#
# # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
# # that's why we can replace:
# # <%= render :partial => "posts/post", :collection => @posts %>
# <%= render @posts %>
#
# == 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:
#
# <%# app/views/users/index.html.erb &>
# Here's the administrator:
# <%= render :partial => "user", :layout => "administrator", :locals => { :user => administrator } %>
#
# Here's the editor:
# <%= render :partial => "user", :layout => "editor", :locals => { :user => editor } %>
#
# <%# app/views/users/_user.html.erb &>
# Name: <%= user.name %>
#
# <%# app/views/users/_administrator.html.erb &>
#
# Budget: $<%= user.budget %>
# <%= yield %>
#
#
# <%# app/views/users/_editor.html.erb &>
#
# Deadline: <%= user.deadline %>
# <%= yield %>
#
#
# ...this will return:
#
# Here's the administrator:
#
#
# You can also apply a layout to a block within any template:
#
# <%# app/views/users/_chief.html.erb &>
# <%= render(:layout => "administrator", :locals => { :user => chief }) do %>
# Title: <%= chief.title %>
# <% end %>
#
# ...this will return:
#
#
#
# As you can see, the :locals 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 &>
#
#
# <%# 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 &>
#
#
# <%# 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 %>
class PartialRenderer < AbstractRenderer
PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} }
def initialize(*)
super
@context_prefix = @lookup_context.prefixes.first
@partial_names = PARTIAL_NAMES[@context_prefix]
end
def render(context, options, block)
setup(context, options, block)
identifier = (@template = find_partial) ? @template.identifier : @path
@lookup_context.rendered_format ||= begin
if @template && @template.formats.present?
@template.formats.first
else
formats.first
end
end
if @collection
instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
render_collection
end
else
instrument(:partial, :identifier => identifier) do
render_partial
end
end
end
def render_collection
return nil if @collection.blank?
if @options.key?(:spacer_template)
spacer = find_template(@options[:spacer_template]).render(@view, @locals)
end
result = @template ? collection_with_template : collection_without_template
result.join(spacer).html_safe
end
def render_partial
locals, view, block = @locals, @view, @block
object, as = @object, @variable
if !block && (layout = @options[:layout])
layout = find_template(layout.to_s)
end
object ||= locals[as]
locals[as] = object
content = @template.render(view, locals) do |*name|
view._layout_for(*name, &block)
end
content = layout.render(view, locals){ content } if layout
content
end
private
def setup(context, options, block)
@view = context
partial = options[:partial]
@options = options
@locals = options[:locals] || {}
@block = block
@details = extract_details(options)
if String === partial
@object = options[:object]
@path = partial
@collection = collection
else
@object = partial
if @collection = collection_from_object || collection
paths = @collection_data = @collection.map { |o| partial_path(o) }
@path = paths.uniq.size == 1 ? paths.first : nil
else
@path = partial_path
end
end
if @path
@variable, @variable_counter = retrieve_variable(@path)
else
paths.map! { |path| retrieve_variable(path).unshift(path) }
end
if String === partial && @variable.to_s !~ /^[a-z_][a-zA-Z_0-9]*$/
raise ArgumentError.new("The partial name (#{partial}) is not a valid Ruby identifier; " +
"make sure your partial name starts with a letter or underscore, " +
"and is followed by any combinations of letters, numbers, or underscores.")
end
extract_format(@path, @details)
self
end
def collection
if @options.key?(:collection)
collection = @options[:collection]
collection.respond_to?(:to_ary) ? collection.to_ary : []
end
end
def collection_from_object
if @object.respond_to?(:to_ary)
@object.to_ary
end
end
def find_partial
if path = @path
locals = @locals.keys
locals << @variable
locals << @variable_counter if @collection
find_template(path, locals)
end
end
def find_template(path=@path, locals=@locals.keys)
prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
@lookup_context.find_template(path, prefixes, true, locals, @details)
end
def collection_with_template
segments, locals, template = [], @locals, @template
as, counter = @variable, @variable_counter
locals[counter] = -1
@collection.each do |object|
locals[counter] += 1
locals[as] = object
segments << template.render(@view, locals)
end
segments
end
def collection_without_template
segments, locals, collection_data = [], @locals, @collection_data
index, template, cache = -1, nil, {}
keys = @locals.keys
@collection.each_with_index do |object, i|
path, *data = collection_data[i]
template = (cache[path] ||= find_template(path, keys + data))
locals[data[0]] = object
locals[data[1]] = (index += 1)
segments << template.render(@view, locals)
end
@template = template
segments
end
def partial_path(object = @object)
object = object.to_model if object.respond_to?(:to_model)
path = if object.respond_to?(:to_partial_path)
object.to_partial_path
else
klass = object.class
if klass.respond_to?(:model_name)
ActiveSupport::Deprecation.warn "ActiveModel-compatible objects whose classes return a #model_name that responds to #partial_path are deprecated. Please respond to #to_partial_path directly instead."
klass.model_name.partial_path
else
raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object that returns a valid partial path.")
end
end
@partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
end
def merge_prefix_into_object_path(prefix, object_path)
if prefix.include?(?/) && object_path.include?(?/)
prefixes = []
prefix_array = File.dirname(prefix).split('/')
object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
prefix_array.each_with_index do |dir, index|
break if dir == object_path_array[index]
prefixes << dir
end
(prefixes << object_path).join("/")
else
object_path
end
end
def retrieve_variable(path)
variable = @options.fetch(:as) { path[%r'_?(\w+)(\.\w+)*$', 1] }.try(:to_sym)
variable_counter = :"#{variable}_counter" if @collection
[variable, variable_counter]
end
end
end
actionpack-3.2.16/lib/action_view/renderer/streaming_template_renderer.rb 0000644 0001750 0001750 00000007475 12247655372 026257 0 ustar ondrej ondrej # 1.9 ships with Fibers but we need to require the extra
# methods explicitly. We only load those extra methods if
# Fiber is available in the first place.
require 'fiber' if defined?(Fiber)
module ActionView
# == TODO
#
# * Support streaming from child templates, partials and so on.
# * Integrate exceptions with exceptron
# * Rack::Cache needs to support streaming bodies
class StreamingTemplateRenderer < TemplateRenderer #:nodoc:
# A valid Rack::Body (i.e. it responds to each).
# It is initialized with a block that, when called, starts
# rendering the template.
class Body #:nodoc:
def initialize(&start)
@start = start
end
def each(&block)
begin
@start.call(block)
rescue Exception => exception
log_error(exception)
block.call ActionView::Base.streaming_completion_on_exception
end
self
end
private
# This is the same logging logic as in ShowExceptions middleware.
# TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron.
def log_error(exception) #:nodoc:
logger = ActionController::Base.logger
return unless logger
message = "\n#{exception.class} (#{exception.message}):\n"
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
message << " " << exception.backtrace.join("\n ")
logger.fatal("#{message}\n\n")
end
end
# For streaming, instead of rendering a given a template, we return a Body
# object that responds to each. This object is initialized with a block
# that knows how to render the template.
def render_template(template, layout_name = nil, locals = {}) #:nodoc:
return [super] unless layout_name && template.supports_streaming?
locals ||= {}
layout = layout_name && find_layout(layout_name, locals.keys)
Body.new do |buffer|
delayed_render(buffer, template, layout, @view, locals)
end
end
private
def delayed_render(buffer, template, layout, view, locals)
# Wrap the given buffer in the StreamingBuffer and pass it to the
# underlying template handler. Now, everytime something is concatenated
# to the buffer, it is not appended to an array, but streamed straight
# to the client.
output = ActionView::StreamingBuffer.new(buffer)
yielder = lambda { |*name| view._layout_for(*name) }
instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
fiber = Fiber.new do
if layout
layout.render(view, locals, output, &yielder)
else
# If you don't have a layout, just render the thing
# and concatenate the final result. This is the same
# as a layout with just <%= yield %>
output.safe_concat view._layout_for
end
end
# Set the view flow to support streaming. It will be aware
# when to stop rendering the layout because it needs to search
# something in the template and vice-versa.
view.view_flow = StreamingFlow.new(view, fiber)
# Yo! Start the fiber!
fiber.resume
# If the fiber is still alive, it means we need something
# from the template, so start rendering it. If not, it means
# the layout exited without requiring anything from the template.
if fiber.alive?
content = template.render(view, locals, &yielder)
# Once rendering the template is done, sets its content in the :layout key.
view.view_flow.set(:layout, content)
# In case the layout continues yielding, we need to resume
# the fiber until all yields are handled.
fiber.resume while fiber.alive?
end
end
end
end
end
actionpack-3.2.16/lib/action_view/base.rb 0000644 0001750 0001750 00000020724 12247655372 017601 0 ustar ondrej ondrej require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/array/wrap'
require 'active_support/ordered_options'
require 'action_view/log_subscriber'
require 'active_support/core_ext/module/deprecation'
module ActionView #:nodoc:
# = Action View Base
#
# Action View templates can be written in several ways. If the template file has a .erb extension then it uses a mixture of ERb
# (included in Ruby) and HTML. If the template file has a .builder extension then Jim Weirich's Builder::XmlMarkup library is used.
#
# == ERB
#
# You trigger ERB by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
# following loop for names:
#
# Names of all the people
# <% @people.each do |person| %>
# Name: <%= person.name %>
# <% end %>
#
# The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this
# is not just a usage suggestion. Regular output functions like print or puts won't work with ERB templates. So this would be wrong:
#
# <%# WRONG %>
# Hi, Mr. <% puts "Frodo" %>
#
# If you absolutely must write from within a function use +concat+.
#
# <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>.
#
# === Using sub templates
#
# Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The
# classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts):
#
# <%= render "shared/header" %>
# Something really specific and terrific
# <%= render "shared/footer" %>
#
# As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the
# result of the rendering. The output embedding writes it to the current template.
#
# But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance
# variables defined using the regular embedding tags. Like this:
#
# <% @page_title = "A Wonderful Hello" %>
# <%= render "shared/header" %>
#
# Now the header can pick up on the @page_title variable and use it for outputting a title tag:
#
# <%= @page_title %>
#
# === Passing local variables to sub templates
#
# You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values:
#
# <%= render "shared/header", { :headline => "Welcome", :person => person } %>
#
# These can now be accessed in shared/header with:
#
# Headline: <%= headline %>
# First name: <%= person.first_name %>
#
# If you need to find out whether a certain local variable has been assigned a value in a particular render call,
# you need to use the following pattern:
#
# <% if local_assigns.has_key? :headline %>
# Headline: <%= headline %>
# <% end %>
#
# Testing using defined? headline will not work. This is an implementation restriction.
#
# === Template caching
#
# By default, Rails will compile each template to a method in order to render it. When you alter a template,
# Rails will check the file's modification time and recompile it in development mode.
#
# == Builder
#
# Builder templates are a more programmatic alternative to ERB. They are especially useful for generating XML content. An XmlMarkup object
# named +xml+ is automatically made available to templates with a .builder extension.
#
# Here are some basic examples:
#
# xml.em("emphasized") # => emphasized
# xml.em { xml.b("emph & bold") } # => emph & bold
# xml.a("A Link", "href" => "http://onestepback.org") # => A Link
# xml.target("name" => "compile", "option" => "fast") # =>
# # NOTE: order of attributes is not specified.
#
# Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following:
#
# xml.div do
# xml.h1(@person.name)
# xml.p(@person.bio)
# end
#
# would produce something like:
#
#
#
David Heinemeier Hansson
#
A product of Danish Design during the Winter of '79...
#
#
# A full-length RSS example actually used on Basecamp:
#
# xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
# xml.channel do
# xml.title(@feed_title)
# xml.link(@url)
# xml.description "Basecamp: Recent items"
# xml.language "en-us"
# xml.ttl "40"
#
# @recent_items.each do |item|
# xml.item do
# xml.title(item_title(item))
# xml.description(item_description(item)) if item_description(item)
# xml.pubDate(item_pubDate(item))
# xml.guid(@person.firm.account.url + @recent_items.url(item))
# xml.link(@person.firm.account.url + @recent_items.url(item))
#
# xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
# end
# end
# end
# end
#
# More builder documentation can be found at http://builder.rubyforge.org.
class Base
include Helpers, ::ERB::Util, Context
# Specify the proc used to decorate input tags that refer to attributes with errors.
cattr_accessor :field_error_proc
@@field_error_proc = Proc.new{ |html_tag, instance| "
#{html_tag}
".html_safe }
# How to complete the streaming when an exception occurs.
# This is our best guess: first try to close the attribute, then the tag.
cattr_accessor :streaming_completion_on_exception
@@streaming_completion_on_exception = %(">