Upgrade to Rails 2.2.0
As a side benefit, fix an (non-user-visible) bug in display_s5(). Also fixed a bug where removing orphaned pages did not expire cached summary pages.
This commit is contained in:
parent
39348c65c2
commit
7600aef48b
|
@ -11,4 +11,8 @@ class WebSweeper < ActionController::Caching::Sweeper
|
|||
web.pages.each { |page| expire_cached_page(web, page.name) }
|
||||
expire_cached_summary_pages(web)
|
||||
end
|
||||
|
||||
def after_remove_orphaned_pages(web)
|
||||
expire_cached_summary_pages(web)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -410,7 +410,7 @@ class WikiController < ApplicationController
|
|||
def remote_ip
|
||||
ip = request.remote_ip
|
||||
logger.info(ip)
|
||||
ip.gsub!(Regexp.union(Resolv::IPv4::Regex, Resolv::IPv6::Regex), '\0') || 'bogus address'
|
||||
ip.dup.gsub!(Regexp.union(Resolv::IPv4::Regex, Resolv::IPv6::Regex), '\0') || 'bogus address'
|
||||
end
|
||||
|
||||
def render_atom(hide_description = false, limit = 15)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<ul>
|
||||
<% @pages_in_category.each do |page| %>
|
||||
<li>
|
||||
<%= link_to_existing_page page, truncate(page.plain_name, 35) %>
|
||||
<%= link_to_existing_page page, truncate(page.plain_name, :length => 35) %>
|
||||
</li>
|
||||
<% end %></ul>
|
||||
|
||||
|
@ -36,7 +36,7 @@
|
|||
<ul style="margin-bottom: 10px">
|
||||
<% @page_names_that_are_wanted.each do |wanted_page_name| %>
|
||||
<li>
|
||||
<%= link_to_page(wanted_page_name, @web, truncate(WikiWords.separate(wanted_page_name), 35)) %>
|
||||
<%= link_to_page(wanted_page_name, @web, truncate(WikiWords.separate(wanted_page_name), :length => 35)) %>
|
||||
wanted by
|
||||
<%= @web.select.pages_that_reference(wanted_page_name).collect { |referring_page|
|
||||
link_to_existing_page referring_page
|
||||
|
@ -56,7 +56,7 @@
|
|||
<ul style="margin-bottom: 35px">
|
||||
<% @pages_that_are_orphaned.each do |orphan_page| %>
|
||||
<li>
|
||||
<%= link_to_existing_page orphan_page, truncate(orphan_page.plain_name, 35) %>
|
||||
<%= link_to_existing_page orphan_page, truncate(orphan_page.plain_name, :length => 35) %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
|
|
@ -71,8 +71,8 @@ class PageRenderer
|
|||
# Renders an S5 slideshow
|
||||
def display_s5
|
||||
@display_s5 ||= render(:mode => :s5,
|
||||
:engine_opts => { :author => @revision.page.author,
|
||||
:title => @revision.page.plain_name}, :renderer => self)
|
||||
:engine_opts => {:author => @author, :title => @plain_name},
|
||||
:renderer => self)
|
||||
end
|
||||
|
||||
# Returns an array of all the WikiIncludes present in the content of this revision.
|
||||
|
|
304
public/javascripts/prototype.js
vendored
304
public/javascripts/prototype.js
vendored
|
@ -1,5 +1,5 @@
|
|||
/* Prototype JavaScript framework, version 1.6.0.1
|
||||
* (c) 2005-2007 Sam Stephenson
|
||||
/* Prototype JavaScript framework, version 1.6.0.2
|
||||
* (c) 2005-2008 Sam Stephenson
|
||||
*
|
||||
* Prototype is freely distributable under the terms of an MIT-style license.
|
||||
* For details, see the Prototype web site: http://www.prototypejs.org/
|
||||
|
@ -7,7 +7,7 @@
|
|||
*--------------------------------------------------------------------------*/
|
||||
|
||||
var Prototype = {
|
||||
Version: '1.6.0.1',
|
||||
Version: '1.6.0.2',
|
||||
|
||||
Browser: {
|
||||
IE: !!(window.attachEvent && !window.opera),
|
||||
|
@ -110,7 +110,7 @@ Object.extend(Object, {
|
|||
try {
|
||||
if (Object.isUndefined(object)) return 'undefined';
|
||||
if (object === null) return 'null';
|
||||
return object.inspect ? object.inspect() : object.toString();
|
||||
return object.inspect ? object.inspect() : String(object);
|
||||
} catch (e) {
|
||||
if (e instanceof RangeError) return '...';
|
||||
throw e;
|
||||
|
@ -171,7 +171,8 @@ Object.extend(Object, {
|
|||
},
|
||||
|
||||
isArray: function(object) {
|
||||
return object && object.constructor === Array;
|
||||
return object != null && typeof object == "object" &&
|
||||
'splice' in object && 'join' in object;
|
||||
},
|
||||
|
||||
isHash: function(object) {
|
||||
|
@ -578,7 +579,7 @@ var Template = Class.create({
|
|||
}
|
||||
|
||||
return before + String.interpret(ctx);
|
||||
}.bind(this));
|
||||
});
|
||||
}
|
||||
});
|
||||
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
|
||||
|
@ -806,20 +807,20 @@ Object.extend(Enumerable, {
|
|||
function $A(iterable) {
|
||||
if (!iterable) return [];
|
||||
if (iterable.toArray) return iterable.toArray();
|
||||
var length = iterable.length, results = new Array(length);
|
||||
var length = iterable.length || 0, results = new Array(length);
|
||||
while (length--) results[length] = iterable[length];
|
||||
return results;
|
||||
}
|
||||
|
||||
if (Prototype.Browser.WebKit) {
|
||||
function $A(iterable) {
|
||||
$A = function(iterable) {
|
||||
if (!iterable) return [];
|
||||
if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
|
||||
iterable.toArray) return iterable.toArray();
|
||||
var length = iterable.length, results = new Array(length);
|
||||
var length = iterable.length || 0, results = new Array(length);
|
||||
while (length--) results[length] = iterable[length];
|
||||
return results;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Array.from = $A;
|
||||
|
@ -1298,7 +1299,7 @@ Ajax.Request = Class.create(Ajax.Base, {
|
|||
|
||||
var contentType = response.getHeader('Content-type');
|
||||
if (this.options.evalJS == 'force'
|
||||
|| (this.options.evalJS && contentType
|
||||
|| (this.options.evalJS && this.isSameOrigin() && contentType
|
||||
&& contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
|
||||
this.evalResponse();
|
||||
}
|
||||
|
@ -1316,9 +1317,18 @@ Ajax.Request = Class.create(Ajax.Base, {
|
|||
}
|
||||
},
|
||||
|
||||
isSameOrigin: function() {
|
||||
var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
|
||||
return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
|
||||
protocol: location.protocol,
|
||||
domain: document.domain,
|
||||
port: location.port ? ':' + location.port : ''
|
||||
}));
|
||||
},
|
||||
|
||||
getHeader: function(name) {
|
||||
try {
|
||||
return this.transport.getResponseHeader(name);
|
||||
return this.transport.getResponseHeader(name) || null;
|
||||
} catch (e) { return null }
|
||||
},
|
||||
|
||||
|
@ -1391,7 +1401,8 @@ Ajax.Response = Class.create({
|
|||
if (!json) return null;
|
||||
json = decodeURIComponent(escape(json));
|
||||
try {
|
||||
return json.evalJSON(this.request.options.sanitizeJSON);
|
||||
return json.evalJSON(this.request.options.sanitizeJSON ||
|
||||
!this.request.isSameOrigin());
|
||||
} catch (e) {
|
||||
this.request.dispatchException(e);
|
||||
}
|
||||
|
@ -1404,7 +1415,8 @@ Ajax.Response = Class.create({
|
|||
this.responseText.blank())
|
||||
return null;
|
||||
try {
|
||||
return this.responseText.evalJSON(options.sanitizeJSON);
|
||||
return this.responseText.evalJSON(options.sanitizeJSON ||
|
||||
!this.request.isSameOrigin());
|
||||
} catch (e) {
|
||||
this.request.dispatchException(e);
|
||||
}
|
||||
|
@ -1608,24 +1620,28 @@ Element.Methods = {
|
|||
Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
|
||||
insertions = {bottom:insertions};
|
||||
|
||||
var content, t, range;
|
||||
var content, insert, tagName, childNodes;
|
||||
|
||||
for (position in insertions) {
|
||||
for (var position in insertions) {
|
||||
content = insertions[position];
|
||||
position = position.toLowerCase();
|
||||
t = Element._insertionTranslations[position];
|
||||
insert = Element._insertionTranslations[position];
|
||||
|
||||
if (content && content.toElement) content = content.toElement();
|
||||
if (Object.isElement(content)) {
|
||||
t.insert(element, content);
|
||||
insert(element, content);
|
||||
continue;
|
||||
}
|
||||
|
||||
content = Object.toHTML(content);
|
||||
|
||||
range = element.ownerDocument.createRange();
|
||||
t.initializeRange(element, range);
|
||||
t.insert(element, range.createContextualFragment(content.stripScripts()));
|
||||
tagName = ((position == 'before' || position == 'after')
|
||||
? element.parentNode : element).tagName.toUpperCase();
|
||||
|
||||
childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
|
||||
|
||||
if (position == 'top' || position == 'after') childNodes.reverse();
|
||||
childNodes.each(insert.curry(element));
|
||||
|
||||
content.evalScripts.bind(content).defer();
|
||||
}
|
||||
|
@ -1670,7 +1686,7 @@ Element.Methods = {
|
|||
},
|
||||
|
||||
descendants: function(element) {
|
||||
return $(element).getElementsBySelector("*");
|
||||
return $(element).select("*");
|
||||
},
|
||||
|
||||
firstDescendant: function(element) {
|
||||
|
@ -1709,32 +1725,31 @@ Element.Methods = {
|
|||
element = $(element);
|
||||
if (arguments.length == 1) return $(element.parentNode);
|
||||
var ancestors = element.ancestors();
|
||||
return expression ? Selector.findElement(ancestors, expression, index) :
|
||||
ancestors[index || 0];
|
||||
return Object.isNumber(expression) ? ancestors[expression] :
|
||||
Selector.findElement(ancestors, expression, index);
|
||||
},
|
||||
|
||||
down: function(element, expression, index) {
|
||||
element = $(element);
|
||||
if (arguments.length == 1) return element.firstDescendant();
|
||||
var descendants = element.descendants();
|
||||
return expression ? Selector.findElement(descendants, expression, index) :
|
||||
descendants[index || 0];
|
||||
return Object.isNumber(expression) ? element.descendants()[expression] :
|
||||
element.select(expression)[index || 0];
|
||||
},
|
||||
|
||||
previous: function(element, expression, index) {
|
||||
element = $(element);
|
||||
if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
|
||||
var previousSiblings = element.previousSiblings();
|
||||
return expression ? Selector.findElement(previousSiblings, expression, index) :
|
||||
previousSiblings[index || 0];
|
||||
return Object.isNumber(expression) ? previousSiblings[expression] :
|
||||
Selector.findElement(previousSiblings, expression, index);
|
||||
},
|
||||
|
||||
next: function(element, expression, index) {
|
||||
element = $(element);
|
||||
if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
|
||||
var nextSiblings = element.nextSiblings();
|
||||
return expression ? Selector.findElement(nextSiblings, expression, index) :
|
||||
nextSiblings[index || 0];
|
||||
return Object.isNumber(expression) ? nextSiblings[expression] :
|
||||
Selector.findElement(nextSiblings, expression, index);
|
||||
},
|
||||
|
||||
select: function() {
|
||||
|
@ -1860,7 +1875,8 @@ Element.Methods = {
|
|||
do { ancestor = ancestor.parentNode; }
|
||||
while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
|
||||
}
|
||||
if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
|
||||
if (nextAncestor && nextAncestor.sourceIndex)
|
||||
return (e > a && e < nextAncestor.sourceIndex);
|
||||
}
|
||||
|
||||
while (element = element.parentNode)
|
||||
|
@ -2004,7 +2020,7 @@ Element.Methods = {
|
|||
if (element) {
|
||||
if (element.tagName == 'BODY') break;
|
||||
var p = Element.getStyle(element, 'position');
|
||||
if (p == 'relative' || p == 'absolute') break;
|
||||
if (p !== 'static') break;
|
||||
}
|
||||
} while (element);
|
||||
return Element._returnOffset(valueL, valueT);
|
||||
|
@ -2153,46 +2169,6 @@ Element._attributeTranslations = {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
if (!document.createRange || Prototype.Browser.Opera) {
|
||||
Element.Methods.insert = function(element, insertions) {
|
||||
element = $(element);
|
||||
|
||||
if (Object.isString(insertions) || Object.isNumber(insertions) ||
|
||||
Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
|
||||
insertions = { bottom: insertions };
|
||||
|
||||
var t = Element._insertionTranslations, content, position, pos, tagName;
|
||||
|
||||
for (position in insertions) {
|
||||
content = insertions[position];
|
||||
position = position.toLowerCase();
|
||||
pos = t[position];
|
||||
|
||||
if (content && content.toElement) content = content.toElement();
|
||||
if (Object.isElement(content)) {
|
||||
pos.insert(element, content);
|
||||
continue;
|
||||
}
|
||||
|
||||
content = Object.toHTML(content);
|
||||
tagName = ((position == 'before' || position == 'after')
|
||||
? element.parentNode : element).tagName.toUpperCase();
|
||||
|
||||
if (t.tags[tagName]) {
|
||||
var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
|
||||
if (position == 'top' || position == 'after') fragments.reverse();
|
||||
fragments.each(pos.insert.curry(element));
|
||||
}
|
||||
else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());
|
||||
|
||||
content.evalScripts.bind(content).defer();
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
||||
}
|
||||
|
||||
if (Prototype.Browser.Opera) {
|
||||
Element.Methods.getStyle = Element.Methods.getStyle.wrap(
|
||||
function(proceed, element, style) {
|
||||
|
@ -2237,12 +2213,31 @@ if (Prototype.Browser.Opera) {
|
|||
}
|
||||
|
||||
else if (Prototype.Browser.IE) {
|
||||
$w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
|
||||
// IE doesn't report offsets correctly for static elements, so we change them
|
||||
// to "relative" to get the values, then change them back.
|
||||
Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
|
||||
function(proceed, element) {
|
||||
element = $(element);
|
||||
var position = element.getStyle('position');
|
||||
if (position !== 'static') return proceed(element);
|
||||
element.setStyle({ position: 'relative' });
|
||||
var value = proceed(element);
|
||||
element.setStyle({ position: position });
|
||||
return value;
|
||||
}
|
||||
);
|
||||
|
||||
$w('positionedOffset viewportOffset').each(function(method) {
|
||||
Element.Methods[method] = Element.Methods[method].wrap(
|
||||
function(proceed, element) {
|
||||
element = $(element);
|
||||
var position = element.getStyle('position');
|
||||
if (position != 'static') return proceed(element);
|
||||
if (position !== 'static') return proceed(element);
|
||||
// Trigger hasLayout on the offset parent so that IE6 reports
|
||||
// accurate offsetTop and offsetLeft values for position: fixed.
|
||||
var offsetParent = element.getOffsetParent();
|
||||
if (offsetParent && offsetParent.getStyle('position') === 'fixed')
|
||||
offsetParent.setStyle({ zoom: 1 });
|
||||
element.setStyle({ position: 'relative' });
|
||||
var value = proceed(element);
|
||||
element.setStyle({ position: position });
|
||||
|
@ -2324,7 +2319,10 @@ else if (Prototype.Browser.IE) {
|
|||
};
|
||||
|
||||
Element._attributeTranslations.write = {
|
||||
names: Object.clone(Element._attributeTranslations.read.names),
|
||||
names: Object.extend({
|
||||
cellpadding: 'cellPadding',
|
||||
cellspacing: 'cellSpacing'
|
||||
}, Element._attributeTranslations.read.names),
|
||||
values: {
|
||||
checked: function(element, value) {
|
||||
element.checked = !!value;
|
||||
|
@ -2444,7 +2442,7 @@ if (Prototype.Browser.IE || Prototype.Browser.Opera) {
|
|||
};
|
||||
}
|
||||
|
||||
if (document.createElement('div').outerHTML) {
|
||||
if ('outerHTML' in document.createElement('div')) {
|
||||
Element.Methods.replace = function(element, content) {
|
||||
element = $(element);
|
||||
|
||||
|
@ -2482,45 +2480,25 @@ Element._returnOffset = function(l, t) {
|
|||
|
||||
Element._getContentFromAnonymousElement = function(tagName, html) {
|
||||
var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
|
||||
div.innerHTML = t[0] + html + t[1];
|
||||
t[2].times(function() { div = div.firstChild });
|
||||
if (t) {
|
||||
div.innerHTML = t[0] + html + t[1];
|
||||
t[2].times(function() { div = div.firstChild });
|
||||
} else div.innerHTML = html;
|
||||
return $A(div.childNodes);
|
||||
};
|
||||
|
||||
Element._insertionTranslations = {
|
||||
before: {
|
||||
adjacency: 'beforeBegin',
|
||||
insert: function(element, node) {
|
||||
element.parentNode.insertBefore(node, element);
|
||||
},
|
||||
initializeRange: function(element, range) {
|
||||
range.setStartBefore(element);
|
||||
}
|
||||
before: function(element, node) {
|
||||
element.parentNode.insertBefore(node, element);
|
||||
},
|
||||
top: {
|
||||
adjacency: 'afterBegin',
|
||||
insert: function(element, node) {
|
||||
element.insertBefore(node, element.firstChild);
|
||||
},
|
||||
initializeRange: function(element, range) {
|
||||
range.selectNodeContents(element);
|
||||
range.collapse(true);
|
||||
}
|
||||
top: function(element, node) {
|
||||
element.insertBefore(node, element.firstChild);
|
||||
},
|
||||
bottom: {
|
||||
adjacency: 'beforeEnd',
|
||||
insert: function(element, node) {
|
||||
element.appendChild(node);
|
||||
}
|
||||
bottom: function(element, node) {
|
||||
element.appendChild(node);
|
||||
},
|
||||
after: {
|
||||
adjacency: 'afterEnd',
|
||||
insert: function(element, node) {
|
||||
element.parentNode.insertBefore(node, element.nextSibling);
|
||||
},
|
||||
initializeRange: function(element, range) {
|
||||
range.setStartAfter(element);
|
||||
}
|
||||
after: function(element, node) {
|
||||
element.parentNode.insertBefore(node, element.nextSibling);
|
||||
},
|
||||
tags: {
|
||||
TABLE: ['<table>', '</table>', 1],
|
||||
|
@ -2532,7 +2510,6 @@ Element._insertionTranslations = {
|
|||
};
|
||||
|
||||
(function() {
|
||||
this.bottom.initializeRange = this.top.initializeRange;
|
||||
Object.extend(this.tags, {
|
||||
THEAD: this.tags.TBODY,
|
||||
TFOOT: this.tags.TBODY,
|
||||
|
@ -2716,7 +2693,7 @@ document.viewport = {
|
|||
window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
|
||||
}
|
||||
};
|
||||
/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
|
||||
/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
|
||||
* part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
|
||||
* license. Please see http://www.yui-ext.com/ for more information. */
|
||||
|
||||
|
@ -2959,13 +2936,13 @@ Object.extend(Selector, {
|
|||
},
|
||||
|
||||
criteria: {
|
||||
tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
|
||||
className: 'n = h.className(n, r, "#{1}", c); c = false;',
|
||||
id: 'n = h.id(n, r, "#{1}", c); c = false;',
|
||||
attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
|
||||
tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
|
||||
className: 'n = h.className(n, r, "#{1}", c); c = false;',
|
||||
id: 'n = h.id(n, r, "#{1}", c); c = false;',
|
||||
attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
|
||||
attr: function(m) {
|
||||
m[3] = (m[5] || m[6]);
|
||||
return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
|
||||
return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
|
||||
},
|
||||
pseudo: function(m) {
|
||||
if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
|
||||
|
@ -2989,7 +2966,8 @@ Object.extend(Selector, {
|
|||
tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
|
||||
id: /^#([\w\-\*]+)(\b|$)/,
|
||||
className: /^\.([\w\-\*]+)(\b|$)/,
|
||||
pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
|
||||
pseudo:
|
||||
/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
|
||||
attrPresence: /^\[([\w]+)\]/,
|
||||
attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
|
||||
},
|
||||
|
@ -3014,7 +2992,7 @@ Object.extend(Selector, {
|
|||
|
||||
attr: function(element, matches) {
|
||||
var nodeValue = Element.readAttribute(element, matches[1]);
|
||||
return Selector.operators[matches[2]](nodeValue, matches[3]);
|
||||
return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -3029,14 +3007,15 @@ Object.extend(Selector, {
|
|||
|
||||
// marks an array of nodes for counting
|
||||
mark: function(nodes) {
|
||||
var _true = Prototype.emptyFunction;
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
node._counted = true;
|
||||
node._countedByPrototype = _true;
|
||||
return nodes;
|
||||
},
|
||||
|
||||
unmark: function(nodes) {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
node._counted = undefined;
|
||||
node._countedByPrototype = undefined;
|
||||
return nodes;
|
||||
},
|
||||
|
||||
|
@ -3044,15 +3023,15 @@ Object.extend(Selector, {
|
|||
// "ofType" flag indicates whether we're indexing for nth-of-type
|
||||
// rather than nth-child
|
||||
index: function(parentNode, reverse, ofType) {
|
||||
parentNode._counted = true;
|
||||
parentNode._countedByPrototype = Prototype.emptyFunction;
|
||||
if (reverse) {
|
||||
for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
|
||||
var node = nodes[i];
|
||||
if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
|
||||
if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
|
||||
if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
|
||||
if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -3061,8 +3040,8 @@ Object.extend(Selector, {
|
|||
if (nodes.length == 0) return nodes;
|
||||
var results = [], n;
|
||||
for (var i = 0, l = nodes.length; i < l; i++)
|
||||
if (!(n = nodes[i])._counted) {
|
||||
n._counted = true;
|
||||
if (!(n = nodes[i])._countedByPrototype) {
|
||||
n._countedByPrototype = Prototype.emptyFunction;
|
||||
results.push(Element.extend(n));
|
||||
}
|
||||
return Selector.handlers.unmark(results);
|
||||
|
@ -3114,7 +3093,7 @@ Object.extend(Selector, {
|
|||
|
||||
// TOKEN FUNCTIONS
|
||||
tagName: function(nodes, root, tagName, combinator) {
|
||||
tagName = tagName.toUpperCase();
|
||||
var uTagName = tagName.toUpperCase();
|
||||
var results = [], h = Selector.handlers;
|
||||
if (nodes) {
|
||||
if (combinator) {
|
||||
|
@ -3127,7 +3106,7 @@ Object.extend(Selector, {
|
|||
if (tagName == "*") return nodes;
|
||||
}
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (node.tagName.toUpperCase() == tagName) results.push(node);
|
||||
if (node.tagName.toUpperCase() === uTagName) results.push(node);
|
||||
return results;
|
||||
} else return root.getElementsByTagName(tagName);
|
||||
},
|
||||
|
@ -3174,16 +3153,18 @@ Object.extend(Selector, {
|
|||
return results;
|
||||
},
|
||||
|
||||
attrPresence: function(nodes, root, attr) {
|
||||
attrPresence: function(nodes, root, attr, combinator) {
|
||||
if (!nodes) nodes = root.getElementsByTagName("*");
|
||||
if (nodes && combinator) nodes = this[combinator](nodes);
|
||||
var results = [];
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (Element.hasAttribute(node, attr)) results.push(node);
|
||||
return results;
|
||||
},
|
||||
|
||||
attr: function(nodes, root, attr, value, operator) {
|
||||
attr: function(nodes, root, attr, value, operator, combinator) {
|
||||
if (!nodes) nodes = root.getElementsByTagName("*");
|
||||
if (nodes && combinator) nodes = this[combinator](nodes);
|
||||
var handler = Selector.operators[operator], results = [];
|
||||
for (var i = 0, node; node = nodes[i]; i++) {
|
||||
var nodeValue = Element.readAttribute(node, attr);
|
||||
|
@ -3262,7 +3243,7 @@ Object.extend(Selector, {
|
|||
var h = Selector.handlers, results = [], indexed = [], m;
|
||||
h.mark(nodes);
|
||||
for (var i = 0, node; node = nodes[i]; i++) {
|
||||
if (!node.parentNode._counted) {
|
||||
if (!node.parentNode._countedByPrototype) {
|
||||
h.index(node.parentNode, reverse, ofType);
|
||||
indexed.push(node.parentNode);
|
||||
}
|
||||
|
@ -3300,7 +3281,7 @@ Object.extend(Selector, {
|
|||
var exclusions = new Selector(selector).findElements(root);
|
||||
h.mark(exclusions);
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
if (!node._counted) results.push(node);
|
||||
if (!node._countedByPrototype) results.push(node);
|
||||
h.unmark(exclusions);
|
||||
return results;
|
||||
},
|
||||
|
@ -3334,11 +3315,19 @@ Object.extend(Selector, {
|
|||
'|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
|
||||
},
|
||||
|
||||
split: function(expression) {
|
||||
var expressions = [];
|
||||
expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
|
||||
expressions.push(m[1].strip());
|
||||
});
|
||||
return expressions;
|
||||
},
|
||||
|
||||
matchElements: function(elements, expression) {
|
||||
var matches = new Selector(expression).findElements(), h = Selector.handlers;
|
||||
var matches = $$(expression), h = Selector.handlers;
|
||||
h.mark(matches);
|
||||
for (var i = 0, results = [], element; element = elements[i]; i++)
|
||||
if (element._counted) results.push(element);
|
||||
if (element._countedByPrototype) results.push(element);
|
||||
h.unmark(matches);
|
||||
return results;
|
||||
},
|
||||
|
@ -3351,11 +3340,7 @@ Object.extend(Selector, {
|
|||
},
|
||||
|
||||
findChildElements: function(element, expressions) {
|
||||
var exprs = expressions.join(',');
|
||||
expressions = [];
|
||||
exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
|
||||
expressions.push(m[1].strip());
|
||||
});
|
||||
expressions = Selector.split(expressions.join(','));
|
||||
var results = [], h = Selector.handlers;
|
||||
for (var i = 0, l = expressions.length, selector; i < l; i++) {
|
||||
selector = new Selector(expressions[i].strip());
|
||||
|
@ -3366,13 +3351,22 @@ Object.extend(Selector, {
|
|||
});
|
||||
|
||||
if (Prototype.Browser.IE) {
|
||||
// IE returns comment nodes on getElementsByTagName("*").
|
||||
// Filter them out.
|
||||
Selector.handlers.concat = function(a, b) {
|
||||
for (var i = 0, node; node = b[i]; i++)
|
||||
if (node.tagName !== "!") a.push(node);
|
||||
return a;
|
||||
};
|
||||
Object.extend(Selector.handlers, {
|
||||
// IE returns comment nodes on getElementsByTagName("*").
|
||||
// Filter them out.
|
||||
concat: function(a, b) {
|
||||
for (var i = 0, node; node = b[i]; i++)
|
||||
if (node.tagName !== "!") a.push(node);
|
||||
return a;
|
||||
},
|
||||
|
||||
// IE improperly serializes _countedByPrototype in (inner|outer)HTML.
|
||||
unmark: function(nodes) {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
node.removeAttribute('_countedByPrototype');
|
||||
return nodes;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function $$() {
|
||||
|
@ -3850,9 +3844,9 @@ Object.extend(Event, (function() {
|
|||
var cache = Event.cache;
|
||||
|
||||
function getEventID(element) {
|
||||
if (element._eventID) return element._eventID;
|
||||
if (element._prototypeEventID) return element._prototypeEventID[0];
|
||||
arguments.callee.id = arguments.callee.id || 1;
|
||||
return element._eventID = ++arguments.callee.id;
|
||||
return element._prototypeEventID = [++arguments.callee.id];
|
||||
}
|
||||
|
||||
function getDOMEventName(eventName) {
|
||||
|
@ -3880,7 +3874,7 @@ Object.extend(Event, (function() {
|
|||
return false;
|
||||
|
||||
Event.extend(event);
|
||||
handler.call(element, event)
|
||||
handler.call(element, event);
|
||||
};
|
||||
|
||||
wrapper.handler = handler;
|
||||
|
@ -3962,11 +3956,12 @@ Object.extend(Event, (function() {
|
|||
if (element == document && document.createEvent && !element.dispatchEvent)
|
||||
element = document.documentElement;
|
||||
|
||||
var event;
|
||||
if (document.createEvent) {
|
||||
var event = document.createEvent("HTMLEvents");
|
||||
event = document.createEvent("HTMLEvents");
|
||||
event.initEvent("dataavailable", true, true);
|
||||
} else {
|
||||
var event = document.createEventObject();
|
||||
event = document.createEventObject();
|
||||
event.eventType = "ondataavailable";
|
||||
}
|
||||
|
||||
|
@ -3995,20 +3990,21 @@ Element.addMethods({
|
|||
Object.extend(document, {
|
||||
fire: Element.Methods.fire.methodize(),
|
||||
observe: Element.Methods.observe.methodize(),
|
||||
stopObserving: Element.Methods.stopObserving.methodize()
|
||||
stopObserving: Element.Methods.stopObserving.methodize(),
|
||||
loaded: false
|
||||
});
|
||||
|
||||
(function() {
|
||||
/* Support for the DOMContentLoaded event is based on work by Dan Webb,
|
||||
Matthias Miller, Dean Edwards and John Resig. */
|
||||
|
||||
var timer, fired = false;
|
||||
var timer;
|
||||
|
||||
function fireContentLoadedEvent() {
|
||||
if (fired) return;
|
||||
if (document.loaded) return;
|
||||
if (timer) window.clearInterval(timer);
|
||||
document.fire("dom:loaded");
|
||||
fired = true;
|
||||
document.loaded = true;
|
||||
}
|
||||
|
||||
if (document.addEventListener) {
|
||||
|
|
7
vendor/rails/actionmailer/CHANGELOG
vendored
7
vendor/rails/actionmailer/CHANGELOG
vendored
|
@ -1,6 +1,9 @@
|
|||
*2.1.1 (September 4th, 2008)*
|
||||
*2.2.0 [RC1] (October 24th, 2008)*
|
||||
|
||||
* Included in Rails 2.1.1
|
||||
* Add layout functionality to mailers [Pratik]
|
||||
|
||||
Mailer layouts behaves just like controller layouts, except layout names need to
|
||||
have '_mailer' postfix for them to be automatically picked up.
|
||||
|
||||
|
||||
*2.1.0 (May 31st, 2008)*
|
||||
|
|
8
vendor/rails/actionmailer/Rakefile
vendored
8
vendor/rails/actionmailer/Rakefile
vendored
|
@ -5,8 +5,6 @@ require 'rake/rdoctask'
|
|||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rake/contrib/sshpublisher'
|
||||
require 'rake/contrib/rubyforgepublisher'
|
||||
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version')
|
||||
|
||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
||||
|
@ -57,7 +55,7 @@ spec = Gem::Specification.new do |s|
|
|||
s.rubyforge_project = "actionmailer"
|
||||
s.homepage = "http://www.rubyonrails.org"
|
||||
|
||||
s.add_dependency('actionpack', '= 2.1.1' + PKG_BUILD)
|
||||
s.add_dependency('actionpack', '= 2.2.0' + PKG_BUILD)
|
||||
|
||||
s.has_rdoc = true
|
||||
s.requirements << 'none'
|
||||
|
@ -78,8 +76,8 @@ end
|
|||
|
||||
desc "Publish the API documentation"
|
||||
task :pgem => [:package] do
|
||||
Rake::SshFilePublisher.new("david@greed.loudthinking.com", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||
`ssh david@greed.loudthinking.com '/u/sites/gems/gemupdate.sh'`
|
||||
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
|
||||
end
|
||||
|
||||
desc "Publish the API documentation"
|
||||
|
|
12
vendor/rails/actionmailer/lib/action_mailer.rb
vendored
12
vendor/rails/actionmailer/lib/action_mailer.rb
vendored
|
@ -21,13 +21,13 @@
|
|||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#++
|
||||
|
||||
unless defined?(ActionController)
|
||||
begin
|
||||
$:.unshift "#{File.dirname(__FILE__)}/../../actionpack/lib"
|
||||
begin
|
||||
require 'action_controller'
|
||||
rescue LoadError
|
||||
actionpack_path = "#{File.dirname(__FILE__)}/../../actionpack/lib"
|
||||
if File.directory?(actionpack_path)
|
||||
$:.unshift actionpack_path
|
||||
require 'action_controller'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
gem 'actionpack', '>= 1.12.5'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
116
vendor/rails/actionmailer/lib/action_mailer/base.rb
vendored
116
vendor/rails/actionmailer/lib/action_mailer/base.rb
vendored
|
@ -216,7 +216,7 @@ module ActionMailer #:nodoc:
|
|||
# * <tt>:domain</tt> - If you need to specify a HELO domain, you can do it here.
|
||||
# * <tt>:user_name</tt> - If your mail server requires authentication, set the username in this setting.
|
||||
# * <tt>:password</tt> - If your mail server requires authentication, set the password in this setting.
|
||||
# * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the authentication type here.
|
||||
# * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the authentication type here.
|
||||
# This is a symbol and one of <tt>:plain</tt>, <tt>:login</tt>, <tt>:cram_md5</tt>.
|
||||
#
|
||||
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
|
||||
|
@ -233,10 +233,10 @@ module ActionMailer #:nodoc:
|
|||
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with <tt>delivery_method :test</tt>. Most useful
|
||||
# for unit and functional testing.
|
||||
#
|
||||
# * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
|
||||
# * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
|
||||
# pick a different charset from inside a method with +charset+.
|
||||
# * <tt>default_content_type</tt> - The default content type used for the main part of the message. Defaults to "text/plain". You
|
||||
# can also pick a different content type from inside a method with +content_type+.
|
||||
# can also pick a different content type from inside a method with +content_type+.
|
||||
# * <tt>default_mime_version</tt> - The default mime version used for the message. Defaults to <tt>1.0</tt>. You
|
||||
# can also pick a different value from inside a method with +mime_version+.
|
||||
# * <tt>default_implicit_parts_order</tt> - When a message is built implicitly (i.e. multiple parts are assembled from templates
|
||||
|
@ -246,16 +246,16 @@ module ActionMailer #:nodoc:
|
|||
# +implicit_parts_order+.
|
||||
class Base
|
||||
include AdvAttrAccessor, PartContainer
|
||||
include ActionController::UrlWriter if Object.const_defined?(:ActionController)
|
||||
if Object.const_defined?(:ActionController)
|
||||
include ActionController::UrlWriter
|
||||
include ActionController::Layout
|
||||
end
|
||||
|
||||
private_class_method :new #:nodoc:
|
||||
|
||||
class_inheritable_accessor :template_root
|
||||
class_inheritable_accessor :view_paths
|
||||
cattr_accessor :logger
|
||||
|
||||
cattr_accessor :template_extensions
|
||||
@@template_extensions = ['erb', 'builder', 'rhtml', 'rxml']
|
||||
|
||||
@@smtp_settings = {
|
||||
:address => "localhost",
|
||||
:port => 25,
|
||||
|
@ -296,6 +296,9 @@ module ActionMailer #:nodoc:
|
|||
@@default_implicit_parts_order = [ "text/html", "text/enriched", "text/plain" ]
|
||||
cattr_accessor :default_implicit_parts_order
|
||||
|
||||
cattr_reader :protected_instance_variables
|
||||
@@protected_instance_variables = %w(@body)
|
||||
|
||||
# Specify the BCC addresses for the message
|
||||
adv_attr_accessor :bcc
|
||||
|
||||
|
@ -365,6 +368,7 @@ module ActionMailer #:nodoc:
|
|||
|
||||
# The mail object instance referenced by this mailer.
|
||||
attr_reader :mail
|
||||
attr_reader :template_name, :default_template_name, :action_name
|
||||
|
||||
class << self
|
||||
attr_writer :mailer_name
|
||||
|
@ -377,11 +381,16 @@ module ActionMailer #:nodoc:
|
|||
alias_method :controller_name, :mailer_name
|
||||
alias_method :controller_path, :mailer_name
|
||||
|
||||
def method_missing(method_symbol, *parameters)#:nodoc:
|
||||
case method_symbol.id2name
|
||||
when /^create_([_a-z]\w*)/ then new($1, *parameters).mail
|
||||
when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver!
|
||||
when "new" then nil
|
||||
def respond_to?(method_symbol, include_private = false) #:nodoc:
|
||||
matches_dynamic_method?(method_symbol) || super
|
||||
end
|
||||
|
||||
def method_missing(method_symbol, *parameters) #:nodoc:
|
||||
match = matches_dynamic_method?(method_symbol)
|
||||
case match[1]
|
||||
when 'create' then new(match[2], *parameters).mail
|
||||
when 'deliver' then new(match[2], *parameters).deliver!
|
||||
when 'new' then nil
|
||||
else super
|
||||
end
|
||||
end
|
||||
|
@ -414,21 +423,25 @@ module ActionMailer #:nodoc:
|
|||
new.deliver!(mail)
|
||||
end
|
||||
|
||||
# Register a template extension so mailer templates written in a
|
||||
# templating language other than rhtml or rxml are supported.
|
||||
# To use this, include in your template-language plugin's init
|
||||
# code or on a per-application basis, this can be invoked from
|
||||
# <tt>config/environment.rb</tt>:
|
||||
#
|
||||
# ActionMailer::Base.register_template_extension('haml')
|
||||
def register_template_extension(extension)
|
||||
template_extensions << extension
|
||||
ActiveSupport::Deprecation.warn(
|
||||
"ActionMailer::Base.register_template_extension has been deprecated." +
|
||||
"Use ActionView::Base.register_template_extension instead", caller)
|
||||
end
|
||||
|
||||
def template_root
|
||||
self.view_paths && self.view_paths.first
|
||||
end
|
||||
|
||||
def template_root=(root)
|
||||
write_inheritable_attribute(:template_root, root)
|
||||
ActionView::TemplateFinder.process_view_paths(root)
|
||||
self.view_paths = ActionView::Base.process_view_paths(root)
|
||||
end
|
||||
|
||||
private
|
||||
def matches_dynamic_method?(method_name) #:nodoc:
|
||||
method_name = method_name.to_s
|
||||
/(create|deliver)_([_a-z]\w*)/.match(method_name) || /^(new)$/.match(method_name)
|
||||
end
|
||||
end
|
||||
|
||||
# Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
|
||||
|
@ -452,16 +465,18 @@ module ActionMailer #:nodoc:
|
|||
# "the_template_file.text.html.erb", etc.). Only do this if parts
|
||||
# have not already been specified manually.
|
||||
if @parts.empty?
|
||||
templates = Dir.glob("#{template_path}/#{@template}.*")
|
||||
templates.each do |path|
|
||||
basename = File.basename(path)
|
||||
template_regex = Regexp.new("^([^\\\.]+)\\\.([^\\\.]+\\\.[^\\\.]+)\\\.(" + template_extensions.join('|') + ")$")
|
||||
next unless md = template_regex.match(basename)
|
||||
template_name = basename
|
||||
content_type = md.captures[1].gsub('.', '/')
|
||||
@parts << Part.new(:content_type => content_type,
|
||||
:disposition => "inline", :charset => charset,
|
||||
:body => render_message(template_name, @body))
|
||||
Dir.glob("#{template_path}/#{@template}.*").each do |path|
|
||||
template = template_root["#{mailer_name}/#{File.basename(path)}"]
|
||||
|
||||
# Skip unless template has a multipart format
|
||||
next unless template && template.multipart?
|
||||
|
||||
@parts << Part.new(
|
||||
:content_type => template.content_type,
|
||||
:disposition => "inline",
|
||||
:charset => charset,
|
||||
:body => render_message(template, @body)
|
||||
)
|
||||
end
|
||||
unless @parts.empty?
|
||||
@content_type = "multipart/alternative"
|
||||
|
@ -474,7 +489,7 @@ module ActionMailer #:nodoc:
|
|||
# normal template exists (or if there were no implicit parts) we render
|
||||
# it.
|
||||
template_exists = @parts.empty?
|
||||
template_exists ||= Dir.glob("#{template_path}/#{@template}.*").any? { |i| File.basename(i).split(".").length == 2 }
|
||||
template_exists ||= template_root["#{mailer_name}/#{@template}"]
|
||||
@body = render_message(@template, @body) if template_exists
|
||||
|
||||
# Finally, if there are other message parts and a textual body exists,
|
||||
|
@ -522,6 +537,7 @@ module ActionMailer #:nodoc:
|
|||
@content_type ||= @@default_content_type.dup
|
||||
@implicit_parts_order ||= @@default_implicit_parts_order.dup
|
||||
@template ||= method_name
|
||||
@default_template_name = @action_name = @template
|
||||
@mailer_name ||= self.class.name.underscore
|
||||
@parts ||= []
|
||||
@headers ||= {}
|
||||
|
@ -530,16 +546,38 @@ module ActionMailer #:nodoc:
|
|||
end
|
||||
|
||||
def render_message(method_name, body)
|
||||
render :file => method_name, :body => body, :use_full_path => true
|
||||
render :file => method_name, :body => body
|
||||
end
|
||||
|
||||
def render(opts)
|
||||
body = opts.delete(:body)
|
||||
if opts[:file] && opts[:file] !~ /\//
|
||||
if opts[:file] && (opts[:file] !~ /\// && !opts[:file].respond_to?(:render))
|
||||
opts[:file] = "#{mailer_name}/#{opts[:file]}"
|
||||
end
|
||||
opts[:use_full_path] = true
|
||||
initialize_template_class(body).render(opts)
|
||||
|
||||
begin
|
||||
old_template, @template = @template, initialize_template_class(body)
|
||||
layout = respond_to?(:pick_layout, true) ? pick_layout(opts) : false
|
||||
@template.render(opts.merge(:layout => layout))
|
||||
ensure
|
||||
@template = old_template
|
||||
end
|
||||
end
|
||||
|
||||
def default_template_format
|
||||
:html
|
||||
end
|
||||
|
||||
def candidate_for_layout?(options)
|
||||
!@template.send(:_exempt_from_layout?, default_template_name)
|
||||
end
|
||||
|
||||
def template_root
|
||||
self.class.template_root
|
||||
end
|
||||
|
||||
def template_root=(root)
|
||||
self.class.template_root = root
|
||||
end
|
||||
|
||||
def template_path
|
||||
|
@ -547,7 +585,7 @@ module ActionMailer #:nodoc:
|
|||
end
|
||||
|
||||
def initialize_template_class(assigns)
|
||||
ActionView::Base.new([template_root], assigns, self)
|
||||
ActionView::Base.new(view_paths, assigns, self)
|
||||
end
|
||||
|
||||
def sort_parts(parts, order = [])
|
||||
|
|
|
@ -72,7 +72,7 @@ module ActionMailer
|
|||
methods.flatten.each do |method|
|
||||
master_helper_module.module_eval <<-end_eval
|
||||
def #{method}(*args, &block)
|
||||
controller.send!(%(#{method}), *args, &block)
|
||||
controller.__send__(%(#{method}), *args, &block)
|
||||
end
|
||||
end_eval
|
||||
end
|
||||
|
@ -92,7 +92,7 @@ module ActionMailer
|
|||
inherited_without_helper(child)
|
||||
begin
|
||||
child.master_helper_module = Module.new
|
||||
child.master_helper_module.send! :include, master_helper_module
|
||||
child.master_helper_module.__send__(:include, master_helper_module)
|
||||
child.helper child.name.to_s.underscore
|
||||
rescue MissingSourceFile => e
|
||||
raise unless e.is_missing?("helpers/#{child.name.to_s.underscore}_helper")
|
||||
|
|
|
@ -38,7 +38,7 @@ module TMail
|
|||
# = Class Address
|
||||
#
|
||||
# Provides a complete handling library for email addresses. Can parse a string of an
|
||||
# address directly or take in preformatted addresses themseleves. Allows you to add
|
||||
# address directly or take in preformatted addresses themselves. Allows you to add
|
||||
# and remove phrases from the front of the address and provides a compare function for
|
||||
# email addresses.
|
||||
#
|
||||
|
@ -143,7 +143,7 @@ module TMail
|
|||
|
||||
# This is to catch an unquoted "@" symbol in the local part of the
|
||||
# address. Handles addresses like <"@"@me.com> and makes sure they
|
||||
# stay like <"@"@me.com> (previously were becomming <@@me.com>)
|
||||
# stay like <"@"@me.com> (previously were becoming <@@me.com>)
|
||||
if local && (local.join == '@' || local.join =~ /\A[^"].*?@.*?[^"]\Z/)
|
||||
@local = "\"#{local.join}\""
|
||||
else
|
||||
|
|
|
@ -59,7 +59,7 @@ module TMail
|
|||
#
|
||||
# This is because a mailbox doesn't have the : after the From that designates the
|
||||
# beginning of the envelope sender (which can be different to the from address of
|
||||
# the emial)
|
||||
# the email)
|
||||
#
|
||||
# Other fields can be passed as normal, "Reply-To", "Received" etc.
|
||||
#
|
||||
|
|
|
@ -42,7 +42,7 @@ module TMail
|
|||
# Allows you to query the mail object with a string to get the contents
|
||||
# of the field you want.
|
||||
#
|
||||
# Returns a string of the exact contnts of the field
|
||||
# Returns a string of the exact contents of the field
|
||||
#
|
||||
# mail.from = "mikel <mikel@lindsaar.net>"
|
||||
# mail.header_string("From") #=> "mikel <mikel@lindsaar.net>"
|
||||
|
|
|
@ -255,7 +255,7 @@ module TMail
|
|||
alias fetch []
|
||||
|
||||
# Allows you to set or delete TMail header objects at will.
|
||||
# Eamples:
|
||||
# Examples:
|
||||
# @mail = TMail::Mail.new
|
||||
# @mail['to'].to_s # => 'mikel@test.com.au'
|
||||
# @mail['to'] = 'mikel@elsewhere.org'
|
||||
|
@ -265,7 +265,7 @@ module TMail
|
|||
# @mail['to'].to_s # => nil
|
||||
# @mail.encoded # => "\r\n"
|
||||
#
|
||||
# Note: setting mail[] = nil actualy deletes the header field in question from the object,
|
||||
# Note: setting mail[] = nil actually deletes the header field in question from the object,
|
||||
# it does not just set the value of the hash to nil
|
||||
def []=( key, val )
|
||||
dkey = key.downcase
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
module ActionMailer
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 1
|
||||
TINY = 1
|
||||
MINOR = 2
|
||||
TINY = 0
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
require 'test/unit'
|
||||
|
||||
$:.unshift "#{File.dirname(__FILE__)}/../lib"
|
||||
$:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib"
|
||||
$:.unshift "#{File.dirname(__FILE__)}/../../actionpack/lib"
|
||||
require 'action_mailer'
|
||||
require 'action_mailer/test_case'
|
||||
|
||||
|
|
1
vendor/rails/actionmailer/test/fixtures/auto_layout_mailer/hello.html.erb
vendored
Normal file
1
vendor/rails/actionmailer/test/fixtures/auto_layout_mailer/hello.html.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Inside
|
1
vendor/rails/actionmailer/test/fixtures/explicit_layout_mailer/logout.html.erb
vendored
Normal file
1
vendor/rails/actionmailer/test/fixtures/explicit_layout_mailer/logout.html.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
You logged out
|
1
vendor/rails/actionmailer/test/fixtures/explicit_layout_mailer/signup.html.erb
vendored
Normal file
1
vendor/rails/actionmailer/test/fixtures/explicit_layout_mailer/signup.html.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
We do not spam
|
1
vendor/rails/actionmailer/test/fixtures/layouts/auto_layout_mailer.html.erb
vendored
Normal file
1
vendor/rails/actionmailer/test/fixtures/layouts/auto_layout_mailer.html.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Hello from layout <%= yield %>
|
1
vendor/rails/actionmailer/test/fixtures/layouts/spam.html.erb
vendored
Normal file
1
vendor/rails/actionmailer/test/fixtures/layouts/spam.html.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Spammer layout <%= yield %>
|
2
vendor/rails/actionmailer/test/fixtures/test_mailer/body_ivar.erb
vendored
Normal file
2
vendor/rails/actionmailer/test/fixtures/test_mailer/body_ivar.erb
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
body: <%= @body %>
|
||||
bar: <%= @bar %>
|
3
vendor/rails/actionmailer/test/fixtures/test_mailer/signed_up.html.erb
vendored
Normal file
3
vendor/rails/actionmailer/test/fixtures/test_mailer/signed_up.html.erb
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
Hello there,
|
||||
|
||||
Mr. <%= @recipient %>
|
78
vendor/rails/actionmailer/test/mail_layout_test.rb
vendored
Normal file
78
vendor/rails/actionmailer/test/mail_layout_test.rb
vendored
Normal file
|
@ -0,0 +1,78 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class AutoLayoutMailer < ActionMailer::Base
|
||||
def hello(recipient)
|
||||
recipients recipient
|
||||
subject "You have a mail"
|
||||
from "tester@example.com"
|
||||
end
|
||||
|
||||
def spam(recipient)
|
||||
recipients recipient
|
||||
subject "You have a mail"
|
||||
from "tester@example.com"
|
||||
body render(:inline => "Hello, <%= @world %>", :layout => 'spam', :body => { :world => "Earth" })
|
||||
end
|
||||
|
||||
def nolayout(recipient)
|
||||
recipients recipient
|
||||
subject "You have a mail"
|
||||
from "tester@example.com"
|
||||
body render(:inline => "Hello, <%= @world %>", :layout => false, :body => { :world => "Earth" })
|
||||
end
|
||||
end
|
||||
|
||||
class ExplicitLayoutMailer < ActionMailer::Base
|
||||
layout 'spam', :except => [:logout]
|
||||
|
||||
def signup(recipient)
|
||||
recipients recipient
|
||||
subject "You have a mail"
|
||||
from "tester@example.com"
|
||||
end
|
||||
|
||||
def logout(recipient)
|
||||
recipients recipient
|
||||
subject "You have a mail"
|
||||
from "tester@example.com"
|
||||
end
|
||||
end
|
||||
|
||||
class LayoutMailerTest < Test::Unit::TestCase
|
||||
def setup
|
||||
set_delivery_method :test
|
||||
ActionMailer::Base.perform_deliveries = true
|
||||
ActionMailer::Base.deliveries = []
|
||||
|
||||
@recipient = 'test@localhost'
|
||||
end
|
||||
|
||||
def teardown
|
||||
restore_delivery_method
|
||||
end
|
||||
|
||||
def test_should_pickup_default_layout
|
||||
mail = AutoLayoutMailer.create_hello(@recipient)
|
||||
assert_equal "Hello from layout Inside", mail.body.strip
|
||||
end
|
||||
|
||||
def test_should_pickup_layout_given_to_render
|
||||
mail = AutoLayoutMailer.create_spam(@recipient)
|
||||
assert_equal "Spammer layout Hello, Earth", mail.body.strip
|
||||
end
|
||||
|
||||
def test_should_respect_layout_false
|
||||
mail = AutoLayoutMailer.create_nolayout(@recipient)
|
||||
assert_equal "Hello, Earth", mail.body.strip
|
||||
end
|
||||
|
||||
def test_explicit_class_layout
|
||||
mail = ExplicitLayoutMailer.create_signup(@recipient)
|
||||
assert_equal "Spammer layout We do not spam", mail.body.strip
|
||||
end
|
||||
|
||||
def test_explicit_layout_exceptions
|
||||
mail = ExplicitLayoutMailer.create_logout(@recipient)
|
||||
assert_equal "You logged out", mail.body.strip
|
||||
end
|
||||
end
|
|
@ -20,13 +20,13 @@ class RenderMailer < ActionMailer::Base
|
|||
subject "rendering rxml template"
|
||||
from "tester@example.com"
|
||||
end
|
||||
|
||||
|
||||
def included_subtemplate(recipient)
|
||||
recipients recipient
|
||||
subject "Including another template in the one being rendered"
|
||||
from "tester@example.com"
|
||||
end
|
||||
|
||||
|
||||
def included_old_subtemplate(recipient)
|
||||
recipients recipient
|
||||
subject "Including another template in the one being rendered"
|
||||
|
@ -83,17 +83,11 @@ class RenderHelperTest < Test::Unit::TestCase
|
|||
mail = RenderMailer.deliver_rxml_template(@recipient)
|
||||
assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<test/>", mail.body.strip
|
||||
end
|
||||
|
||||
|
||||
def test_included_subtemplate
|
||||
mail = RenderMailer.deliver_included_subtemplate(@recipient)
|
||||
assert_equal "Hey Ho, let's go!", mail.body.strip
|
||||
end
|
||||
|
||||
def test_deprecated_old_subtemplate
|
||||
assert_raises ActionView::ActionViewError do
|
||||
RenderMailer.deliver_included_old_subtemplate(@recipient)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FirstSecondHelperTest < Test::Unit::TestCase
|
||||
|
|
|
@ -219,7 +219,7 @@ class TestMailer < ActionMailer::Base
|
|||
end
|
||||
attachment :content_type => "application/octet-stream",:filename => "test.txt", :body => "test abcdefghijklmnopqstuvwxyz"
|
||||
end
|
||||
|
||||
|
||||
def nested_multipart_with_body(recipient)
|
||||
recipients recipient
|
||||
subject "nested multipart with body"
|
||||
|
@ -273,6 +273,13 @@ class TestMailer < ActionMailer::Base
|
|||
headers "return-path" => "another@somewhere.test"
|
||||
end
|
||||
|
||||
def body_ivar(recipient)
|
||||
recipients recipient
|
||||
subject "Body as a local variable"
|
||||
from "test@example.com"
|
||||
body :body => "foo", :bar => "baz"
|
||||
end
|
||||
|
||||
class <<self
|
||||
attr_accessor :received_body
|
||||
end
|
||||
|
@ -321,7 +328,7 @@ class ActionMailerTest < Test::Unit::TestCase
|
|||
assert_nothing_raised { created = TestMailer.create_nested_multipart(@recipient)}
|
||||
assert_equal 2,created.parts.size
|
||||
assert_equal 2,created.parts.first.parts.size
|
||||
|
||||
|
||||
assert_equal "multipart/mixed", created.content_type
|
||||
assert_equal "multipart/alternative", created.parts.first.content_type
|
||||
assert_equal "bar", created.parts.first.header['foo'].to_s
|
||||
|
@ -366,7 +373,7 @@ class ActionMailerTest < Test::Unit::TestCase
|
|||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
||||
end
|
||||
|
||||
|
||||
def test_custom_template
|
||||
expected = new_mail
|
||||
expected.to = @recipient
|
||||
|
@ -382,7 +389,6 @@ class ActionMailerTest < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def test_custom_templating_extension
|
||||
#
|
||||
# N.b., custom_templating_extension.text.plain.haml is expected to be in fixtures/test_mailer directory
|
||||
expected = new_mail
|
||||
expected.to = @recipient
|
||||
|
@ -390,18 +396,10 @@ class ActionMailerTest < Test::Unit::TestCase
|
|||
expected.body = "Hello there, \n\nMr. #{@recipient}"
|
||||
expected.from = "system@loudthinking.com"
|
||||
expected.date = Time.local(2004, 12, 12)
|
||||
|
||||
|
||||
# Stub the render method so no alternative renderers need be present.
|
||||
ActionView::Base.any_instance.stubs(:render).returns("Hello there, \n\nMr. #{@recipient}")
|
||||
|
||||
# If the template is not registered, there should be no parts.
|
||||
created = nil
|
||||
assert_nothing_raised { created = TestMailer.create_custom_templating_extension(@recipient) }
|
||||
assert_not_nil created
|
||||
assert_equal 0, created.parts.length
|
||||
|
||||
ActionMailer::Base.register_template_extension('haml')
|
||||
|
||||
|
||||
# Now that the template is registered, there should be one part. The text/plain part.
|
||||
created = nil
|
||||
assert_nothing_raised { created = TestMailer.create_custom_templating_extension(@recipient) }
|
||||
|
@ -428,7 +426,7 @@ class ActionMailerTest < Test::Unit::TestCase
|
|||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
||||
end
|
||||
|
||||
|
||||
def test_cc_bcc
|
||||
expected = new_mail
|
||||
expected.to = @recipient
|
||||
|
@ -550,7 +548,7 @@ class ActionMailerTest < Test::Unit::TestCase
|
|||
TestMailer.deliver_signed_up(@recipient)
|
||||
assert_equal 1, ActionMailer::Base.deliveries.size
|
||||
end
|
||||
|
||||
|
||||
def test_doesnt_raise_errors_when_raise_delivery_errors_is_false
|
||||
ActionMailer::Base.raise_delivery_errors = false
|
||||
TestMailer.any_instance.expects(:perform_delivery_test).raises(Exception)
|
||||
|
@ -670,7 +668,7 @@ EOF
|
|||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
||||
end
|
||||
|
||||
|
||||
def test_utf8_body_is_not_quoted
|
||||
@recipient = "Foo áëô îü <extended@example.net>"
|
||||
expected = new_mail "utf-8"
|
||||
|
@ -760,7 +758,7 @@ EOF
|
|||
mail = TestMailer.create_multipart_with_mime_version(@recipient)
|
||||
assert_equal "1.1", mail.mime_version
|
||||
end
|
||||
|
||||
|
||||
def test_multipart_with_utf8_subject
|
||||
mail = TestMailer.create_multipart_with_utf8_subject(@recipient)
|
||||
assert_match(/\nSubject: =\?utf-8\?Q\?Foo_.*?\?=/, mail.encoded)
|
||||
|
@ -825,7 +823,7 @@ EOF
|
|||
mail = TestMailer.create_implicitly_multipart_example(@recipient, 'iso-8859-1')
|
||||
|
||||
assert_equal "multipart/alternative", mail.header['content-type'].body
|
||||
|
||||
|
||||
assert_equal 'iso-8859-1', mail.parts[0].sub_header("content-type", "charset")
|
||||
assert_equal 'iso-8859-1', mail.parts[1].sub_header("content-type", "charset")
|
||||
assert_equal 'iso-8859-1', mail.parts[2].sub_header("content-type", "charset")
|
||||
|
@ -852,7 +850,7 @@ EOF
|
|||
assert_equal "line #1\nline #2\nline #3\nline #4\n\n", mail.parts[0].body
|
||||
assert_equal "<p>line #1</p>\n<p>line #2</p>\n<p>line #3</p>\n<p>line #4</p>\n\n", mail.parts[1].body
|
||||
end
|
||||
|
||||
|
||||
def test_headers_removed_on_smtp_delivery
|
||||
ActionMailer::Base.delivery_method = :smtp
|
||||
TestMailer.deliver_cc_bcc(@recipient)
|
||||
|
@ -935,6 +933,11 @@ EOF
|
|||
TestMailer.deliver_return_path
|
||||
assert_match %r{^Return-Path: <another@somewhere.test>}, MockSMTP.deliveries[0][0]
|
||||
end
|
||||
|
||||
def test_body_is_stored_as_an_ivar
|
||||
mail = TestMailer.create_body_ivar(@recipient)
|
||||
assert_equal "body: foo\nbar: baz", mail.body
|
||||
end
|
||||
end
|
||||
|
||||
end # uses_mocha
|
||||
|
@ -977,3 +980,55 @@ class MethodNamingTest < Test::Unit::TestCase
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
class RespondToTest < Test::Unit::TestCase
|
||||
class RespondToMailer < ActionMailer::Base; end
|
||||
|
||||
def setup
|
||||
set_delivery_method :test
|
||||
end
|
||||
|
||||
def teardown
|
||||
restore_delivery_method
|
||||
end
|
||||
|
||||
def test_should_respond_to_new
|
||||
assert RespondToMailer.respond_to?(:new)
|
||||
end
|
||||
|
||||
def test_should_respond_to_create_with_template_suffix
|
||||
assert RespondToMailer.respond_to?(:create_any_old_template)
|
||||
end
|
||||
|
||||
def test_should_respond_to_deliver_with_template_suffix
|
||||
assert RespondToMailer.respond_to?(:deliver_any_old_template)
|
||||
end
|
||||
|
||||
def test_should_not_respond_to_new_with_template_suffix
|
||||
assert !RespondToMailer.respond_to?(:new_any_old_template)
|
||||
end
|
||||
|
||||
def test_should_not_respond_to_create_with_template_suffix_unless_it_is_separated_by_an_underscore
|
||||
assert !RespondToMailer.respond_to?(:createany_old_template)
|
||||
end
|
||||
|
||||
def test_should_not_respond_to_deliver_with_template_suffix_unless_it_is_separated_by_an_underscore
|
||||
assert !RespondToMailer.respond_to?(:deliverany_old_template)
|
||||
end
|
||||
|
||||
def test_should_not_respond_to_create_with_template_suffix_if_it_begins_with_a_uppercase_letter
|
||||
assert !RespondToMailer.respond_to?(:create_Any_old_template)
|
||||
end
|
||||
|
||||
def test_should_not_respond_to_deliver_with_template_suffix_if_it_begins_with_a_uppercase_letter
|
||||
assert !RespondToMailer.respond_to?(:deliver_Any_old_template)
|
||||
end
|
||||
|
||||
def test_should_not_respond_to_create_with_template_suffix_if_it_begins_with_a_digit
|
||||
assert !RespondToMailer.respond_to?(:create_1_template)
|
||||
end
|
||||
|
||||
def test_should_not_respond_to_deliver_with_template_suffix_if_it_begins_with_a_digit
|
||||
assert !RespondToMailer.respond_to?(:deliver_1_template)
|
||||
end
|
||||
end
|
||||
|
|
197
vendor/rails/actionpack/CHANGELOG
vendored
197
vendor/rails/actionpack/CHANGELOG
vendored
|
@ -1,19 +1,206 @@
|
|||
*2.1.1 (September 4th, 2008)*
|
||||
*2.2.0 [RC1] (October 24th, 2008)*
|
||||
|
||||
* Fix incorrect closing CDATA delimiter and that HTML::Node.parse would blow up on unclosed CDATA sections [packagethief]
|
||||
|
||||
* Added stale? and fresh_when methods to provide a layer of abstraction above request.fresh? and friends [DHH]. Example:
|
||||
|
||||
class ArticlesController < ApplicationController
|
||||
def show_with_respond_to_block
|
||||
@article = Article.find(params[:id])
|
||||
|
||||
|
||||
# If the request sends headers that differs from the options provided to stale?, then
|
||||
# the request is indeed stale and the respond_to block is triggered (and the options
|
||||
# to the stale? call is set on the response).
|
||||
#
|
||||
# If the request headers match, then the request is fresh and the respond_to block is
|
||||
# not triggered. Instead the default render will occur, which will check the last-modified
|
||||
# and etag headers and conclude that it only needs to send a "304 Not Modified" instead
|
||||
# of rendering the template.
|
||||
if stale?(:last_modified => @article.published_at.utc, :etag => @article)
|
||||
respond_to do |wants|
|
||||
# normal response processing
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show_with_implied_render
|
||||
@article = Article.find(params[:id])
|
||||
|
||||
# Sets the response headers and checks them against the request, if the request is stale
|
||||
# (i.e. no match of either etag or last-modified), then the default render of the template happens.
|
||||
# If the request is fresh, then the default render will return a "304 Not Modified"
|
||||
# instead of rendering the template.
|
||||
fresh_when(:last_modified => @article.published_at.utc, :etag => @article)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
* Added inline builder yield to atom_feed_helper tags where appropriate [Sam Ruby]. Example:
|
||||
|
||||
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
|
||||
|
||||
* Make PrototypeHelper#submit_to_remote a wrapper around PrototypeHelper#button_to_remote. [Tarmo Tänav]
|
||||
|
||||
* Set HttpOnly for the cookie session store's cookie. #1046
|
||||
|
||||
* Added FormTagHelper#image_submit_tag confirm option #784 [Alastair Brunton]
|
||||
|
||||
* Fixed FormTagHelper#submit_tag with :disable_with option wouldn't submit the button's value when was clicked #633 [Jose Fernandez]
|
||||
|
||||
* Stopped logging template compiles as it only clogs up the log [DHH]
|
||||
|
||||
* Changed the X-Runtime header to report in milliseconds [DHH]
|
||||
|
||||
* Changed BenchmarkHelper#benchmark to report in milliseconds [DHH]
|
||||
|
||||
* Changed logging format to be millisecond based and skip misleading stats [DHH]. Went from:
|
||||
|
||||
Completed in 0.10000 (4 reqs/sec) | Rendering: 0.04000 (40%) | DB: 0.00400 (4%) | 200 OK [http://example.com]
|
||||
|
||||
...to:
|
||||
|
||||
Completed in 100ms (View: 40, DB: 4) | 200 OK [http://example.com]
|
||||
|
||||
* Add support for shallow nesting of routes. #838 [S. Brent Faulkner]
|
||||
|
||||
Example :
|
||||
|
||||
map.resources :users, :shallow => true do |user|
|
||||
user.resources :posts
|
||||
end
|
||||
|
||||
- GET /users/1/posts (maps to PostsController#index action as usual)
|
||||
named route "user_posts" is added as usual.
|
||||
|
||||
- GET /posts/2 (maps to PostsController#show action as if it were not nested)
|
||||
Additionally, named route "post" is added too.
|
||||
|
||||
* Added button_to_remote helper. #3641 [Donald Piret, Tarmo Tänav]
|
||||
|
||||
* Deprecate render_component. Please use render_component plugin from http://github.com/rails/render_component/tree/master [Pratik]
|
||||
|
||||
* Routes may be restricted to lists of HTTP methods instead of a single method or :any. #407 [Brennan Dunn, Gaius Centus Novus]
|
||||
map.resource :posts, :collection => { :search => [:get, :post] }
|
||||
map.session 'session', :requirements => { :method => [:get, :post, :delete] }
|
||||
|
||||
* Deprecated implicit local assignments when rendering partials [Josh Peek]
|
||||
|
||||
* Introduce current_cycle helper method to return the current value without bumping the cycle. #417 [Ken Collins]
|
||||
|
||||
* Allow polymorphic_url helper to take url options. #880 [Tarmo Tänav]
|
||||
|
||||
* Switched integration test runner to use Rack processor instead of CGI [Josh Peek]
|
||||
|
||||
* Made AbstractRequest.if_modified_sense return nil if the header could not be parsed [Jamis Buck]
|
||||
|
||||
* Added back ActionController::Base.allow_concurrency flag [Josh Peek]
|
||||
|
||||
* AbstractRequest.relative_url_root is no longer automatically configured by a HTTP header. It can now be set in your configuration environment with config.action_controller.relative_url_root [Josh Peek]
|
||||
|
||||
* Update Prototype to 1.6.0.2 #599 [Patrick Joyce]
|
||||
|
||||
* Conditional GET utility methods. [Jeremy Kemper]
|
||||
response.last_modified = @post.updated_at
|
||||
response.etag = [:admin, @post, current_user]
|
||||
|
||||
if request.fresh?(response)
|
||||
head :not_modified
|
||||
else
|
||||
# render ...
|
||||
end
|
||||
|
||||
* All 2xx requests are considered successful [Josh Peek]
|
||||
|
||||
* Deprecate the limited follow_redirect in functional tests. If you wish to follow redirects, use integration tests. [Michael Koziarski]
|
||||
|
||||
* Fixed that AssetTagHelper#compute_public_path shouldn't cache the asset_host along with the source or per-request proc's won't run [DHH]
|
||||
|
||||
* Deprecate define_javascript_functions, javascript_include_tag and friends are much better [Michael Koziarski]
|
||||
* Removed config.action_view.cache_template_loading, use config.cache_classes instead [Josh Peek]
|
||||
|
||||
* Get buffer for fragment cache from template's @output_buffer [Josh Peek]
|
||||
|
||||
* Set config.action_view.warn_cache_misses = true to receive a warning if you perform an action that results in an expensive disk operation that could be cached [Josh Peek]
|
||||
|
||||
* Refactor template preloading. New abstractions include Renderable mixins and a refactored Template class [Josh Peek]
|
||||
|
||||
* Changed ActionView::TemplateHandler#render API method signature to render(template, local_assigns = {}) [Josh Peek]
|
||||
|
||||
* Changed PrototypeHelper#submit_to_remote to PrototypeHelper#button_to_remote to stay consistent with link_to_remote (submit_to_remote still works as an alias) #8994 [clemens]
|
||||
|
||||
* Add :recursive option to javascript_include_tag and stylesheet_link_tag to be used along with :all. #480 [Damian Janowski]
|
||||
|
||||
* Allow users to disable the use of the Accept header [Michael Koziarski]
|
||||
|
||||
The accept header is poorly implemented by browsers and causes strange
|
||||
errors when used on public sites where crawlers make requests too. You
|
||||
can use formatted urls (e.g. /people/1.xml) to support API clients in a
|
||||
much simpler way.
|
||||
|
||||
To disable the header you need to set:
|
||||
|
||||
config.action_controller.use_accept_header = false
|
||||
|
||||
* Do not stat template files in production mode before rendering. You will no longer be able to modify templates in production mode without restarting the server [Josh Peek]
|
||||
|
||||
* Deprecated TemplateHandler line offset [Josh Peek]
|
||||
|
||||
* Allow caches_action to accept cache store options. #416. [José Valim]. Example:
|
||||
|
||||
caches_action :index, :redirected, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour
|
||||
|
||||
* Remove define_javascript_functions, javascript_include_tag and friends are far superior. [Michael Koziarski]
|
||||
|
||||
* Deprecate :use_full_path render option. The supplying the option no longer has an effect [Josh Peek]
|
||||
|
||||
* Add :as option to render a collection of partials with a custom local variable name. #509 [Simon Jefford, Pratik Naik]
|
||||
|
||||
render :partial => 'other_people', :collection => @people, :as => :person
|
||||
|
||||
This will let you access objects of @people as 'person' local variable inside 'other_people' partial template.
|
||||
|
||||
* time_zone_select: support for regexp matching of priority zones. Resolves #195 [Ernie Miller]
|
||||
|
||||
* Made ActionView::Base#render_file private [Josh Peek]
|
||||
|
||||
* Refactor and simplify the implementation of assert_redirected_to. Arguments are now normalised relative to the controller being tested, not the root of the application. [Michael Koziarski]
|
||||
|
||||
This could cause some erroneous test failures if you were redirecting between controllers
|
||||
in different namespaces and wrote your assertions relative to the root of the application.
|
||||
|
||||
* Remove follow_redirect from controller functional tests.
|
||||
|
||||
If you want to follow redirects you can use integration tests. The functional test
|
||||
version was only useful if you were using redirect_to :id=>...
|
||||
|
||||
* Fix polymorphic_url with singleton resources. #461 [Tammer Saleh]
|
||||
|
||||
* Deprecate ActionView::Base.erb_variable. Use the concat helper method instead of appending to it directly. [Jeremy Kemper]
|
||||
* Replaced TemplateFinder abstraction with ViewLoadPaths [Josh Peek]
|
||||
|
||||
* Added block-call style to link_to [Sam Stephenson/DHH]. Example:
|
||||
|
||||
<% link_to(@profile) do %>
|
||||
<strong><%= @profile.name %></strong> -- <span>Check it out!!</span>
|
||||
<% end %>
|
||||
|
||||
* Performance: integration test benchmarking and profiling. [Jeremy Kemper]
|
||||
|
||||
* Make caching more aware of mime types. Ensure request format is not considered while expiring cache. [Jonathan del Strother]
|
||||
|
||||
* Drop ActionController::Base.allow_concurrency flag [Josh Peek]
|
||||
|
||||
* More efficient concat and capture helpers. Remove ActionView::Base.erb_variable. [Jeremy Kemper]
|
||||
|
||||
* Added page.reload functionality. Resolves #277. [Sean Huber]
|
||||
|
||||
* Fixed Request#remote_ip to only raise hell if the HTTP_CLIENT_IP and HTTP_X_FORWARDED_FOR doesn't match (not just if they're both present) [Mark Imbriaco, Bradford Folkens]
|
||||
|
||||
* Allow caches_action to accept a layout option [José Valim]
|
||||
|
||||
* Added Rack processor [Ezra Zygmuntowicz, Josh Peek]
|
||||
|
||||
|
||||
*2.1.0 (May 31st, 2008)*
|
||||
|
||||
|
|
18
vendor/rails/actionpack/README
vendored
18
vendor/rails/actionpack/README
vendored
|
@ -31,7 +31,7 @@ http://www.rubyonrails.org.
|
|||
A short rundown of the major features:
|
||||
|
||||
* Actions grouped in controller as methods instead of separate command objects
|
||||
and can therefore share helper methods.
|
||||
and can therefore share helper methods
|
||||
|
||||
BlogController < ActionController::Base
|
||||
def show
|
||||
|
@ -168,7 +168,7 @@ A short rundown of the major features:
|
|||
{Learn more}[link:classes/ActionController/Base.html]
|
||||
|
||||
|
||||
* Javascript and Ajax integration.
|
||||
* Javascript and Ajax integration
|
||||
|
||||
link_to_function "Greeting", "alert('Hello world!')"
|
||||
link_to_remote "Delete this post", :update => "posts",
|
||||
|
@ -177,7 +177,7 @@ A short rundown of the major features:
|
|||
{Learn more}[link:classes/ActionView/Helpers/JavaScriptHelper.html]
|
||||
|
||||
|
||||
* Pagination for navigating lists of results.
|
||||
* Pagination for navigating lists of results
|
||||
|
||||
# controller
|
||||
def list
|
||||
|
@ -192,15 +192,9 @@ A short rundown of the major features:
|
|||
{Learn more}[link:classes/ActionController/Pagination.html]
|
||||
|
||||
|
||||
* Easy testing of both controller and template result through TestRequest/Response
|
||||
|
||||
class LoginControllerTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@controller = LoginController.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
end
|
||||
* Easy testing of both controller and rendered template through ActionController::TestCase
|
||||
|
||||
class LoginControllerTest < ActionController::TestCase
|
||||
def test_failing_authenticate
|
||||
process :authenticate, :user_name => "nop", :password => ""
|
||||
assert flash.has_key?(:alert)
|
||||
|
@ -208,7 +202,7 @@ A short rundown of the major features:
|
|||
end
|
||||
end
|
||||
|
||||
{Learn more}[link:classes/ActionController/TestRequest.html]
|
||||
{Learn more}[link:classes/ActionController/TestCase.html]
|
||||
|
||||
|
||||
* Automated benchmarking and integrated logging
|
||||
|
|
22
vendor/rails/actionpack/Rakefile
vendored
22
vendor/rails/actionpack/Rakefile
vendored
|
@ -5,8 +5,6 @@ require 'rake/rdoctask'
|
|||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rake/contrib/sshpublisher'
|
||||
require 'rake/contrib/rubyforgepublisher'
|
||||
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version')
|
||||
|
||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
||||
|
@ -27,14 +25,16 @@ task :default => [ :test ]
|
|||
desc "Run all unit tests"
|
||||
task :test => [:test_action_pack, :test_active_record_integration]
|
||||
|
||||
Rake::TestTask.new(:test_action_pack) { |t|
|
||||
Rake::TestTask.new(:test_action_pack) do |t|
|
||||
t.libs << "test"
|
||||
# make sure we include the tests in alphabetical order as on some systems
|
||||
# this will not happen automatically and the tests (as a whole) will error
|
||||
t.test_files=Dir.glob( "test/[cft]*/**/*_test.rb" ).sort
|
||||
# t.pattern = 'test/*/*_test.rb'
|
||||
|
||||
# make sure we include the tests in alphabetical order as on some systems
|
||||
# this will not happen automatically and the tests (as a whole) will error
|
||||
t.test_files = Dir.glob( "test/[cft]*/**/*_test.rb" ).sort
|
||||
|
||||
t.verbose = true
|
||||
}
|
||||
#t.warning = true
|
||||
end
|
||||
|
||||
desc 'ActiveRecord Integration Tests'
|
||||
Rake::TestTask.new(:test_active_record_integration) do |t|
|
||||
|
@ -80,7 +80,7 @@ spec = Gem::Specification.new do |s|
|
|||
s.has_rdoc = true
|
||||
s.requirements << 'none'
|
||||
|
||||
s.add_dependency('activesupport', '= 2.1.1' + PKG_BUILD)
|
||||
s.add_dependency('activesupport', '= 2.2.0' + PKG_BUILD)
|
||||
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'action_controller'
|
||||
|
@ -136,8 +136,8 @@ task :update_js => [ :update_scriptaculous ]
|
|||
|
||||
desc "Publish the API documentation"
|
||||
task :pgem => [:package] do
|
||||
Rake::SshFilePublisher.new("david@greed.loudthinking.com", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||
`ssh david@greed.loudthinking.com '/u/sites/gems/gemupdate.sh'`
|
||||
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
|
||||
end
|
||||
|
||||
desc "Publish the API documentation"
|
||||
|
|
18
vendor/rails/actionpack/lib/action_controller.rb
vendored
18
vendor/rails/actionpack/lib/action_controller.rb
vendored
|
@ -21,16 +21,13 @@
|
|||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#++
|
||||
|
||||
$:.unshift(File.dirname(__FILE__)) unless
|
||||
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
||||
|
||||
unless defined?(ActiveSupport)
|
||||
begin
|
||||
$:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib"
|
||||
begin
|
||||
require 'active_support'
|
||||
rescue LoadError
|
||||
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
|
||||
if File.directory?(activesupport_path)
|
||||
$:.unshift activesupport_path
|
||||
require 'active_support'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
gem 'activesupport'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -53,9 +50,11 @@ require 'action_controller/streaming'
|
|||
require 'action_controller/session_management'
|
||||
require 'action_controller/http_authentication'
|
||||
require 'action_controller/components'
|
||||
require 'action_controller/rack_process'
|
||||
require 'action_controller/record_identifier'
|
||||
require 'action_controller/request_forgery_protection'
|
||||
require 'action_controller/headers'
|
||||
require 'action_controller/translation'
|
||||
|
||||
require 'action_view'
|
||||
|
||||
|
@ -76,4 +75,5 @@ ActionController::Base.class_eval do
|
|||
include ActionController::Components
|
||||
include ActionController::RecordIdentifier
|
||||
include ActionController::RequestForgeryProtection
|
||||
include ActionController::Translation
|
||||
end
|
||||
|
|
|
@ -56,74 +56,24 @@ module ActionController
|
|||
# # assert that the redirection was to the named route login_url
|
||||
# assert_redirected_to login_url
|
||||
#
|
||||
# # assert that the redirection was to the url for @customer
|
||||
# assert_redirected_to @customer
|
||||
#
|
||||
def assert_redirected_to(options = {}, message=nil)
|
||||
clean_backtrace do
|
||||
assert_response(:redirect, message)
|
||||
return true if options == @response.redirected_to
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
|
||||
# Support partial arguments for hash redirections
|
||||
if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
|
||||
return true if options.all? {|(key, value)| @response.redirected_to[key] == value}
|
||||
end
|
||||
|
||||
redirected_to_after_normalisation = normalize_argument_to_redirection(@response.redirected_to)
|
||||
options_after_normalisation = normalize_argument_to_redirection(options)
|
||||
|
||||
begin
|
||||
url = {}
|
||||
original = { :expected => options, :actual => @response.redirected_to.is_a?(Symbol) ? @response.redirected_to : @response.redirected_to.dup }
|
||||
original.each do |key, value|
|
||||
if value.is_a?(Symbol)
|
||||
value = @controller.respond_to?(value, true) ? @controller.send(value) : @controller.send("hash_for_#{value}_url")
|
||||
end
|
||||
|
||||
unless value.is_a?(Hash)
|
||||
request = case value
|
||||
when NilClass then nil
|
||||
when /^\w+:\/\// then recognized_request_for(%r{^(\w+://.*?(/|$|\?))(.*)$} =~ value ? $3 : nil)
|
||||
else recognized_request_for(value)
|
||||
end
|
||||
value = request.path_parameters if request
|
||||
end
|
||||
|
||||
if value.is_a?(Hash) # stringify 2 levels of hash keys
|
||||
if name = value.delete(:use_route)
|
||||
route = ActionController::Routing::Routes.named_routes[name]
|
||||
value.update(route.parameter_shell)
|
||||
end
|
||||
|
||||
value.stringify_keys!
|
||||
value.values.select { |v| v.is_a?(Hash) }.collect { |v| v.stringify_keys! }
|
||||
if key == :expected && value['controller'] == @controller.controller_name && original[:actual].is_a?(Hash)
|
||||
original[:actual].stringify_keys!
|
||||
value.delete('controller') if original[:actual]['controller'].nil? || original[:actual]['controller'] == value['controller']
|
||||
end
|
||||
end
|
||||
|
||||
if value.respond_to?(:[]) && value['controller']
|
||||
value['controller'] = value['controller'].to_s
|
||||
if key == :actual && value['controller'].first != '/' && !value['controller'].include?('/')
|
||||
new_controller_path = ActionController::Routing.controller_relative_to(value['controller'], @controller.class.controller_path)
|
||||
value['controller'] = new_controller_path if value['controller'] != new_controller_path && ActionController::Routing.possible_controllers.include?(new_controller_path) && @response.redirected_to.is_a?(Hash)
|
||||
end
|
||||
value['controller'] = value['controller'][1..-1] if value['controller'].first == '/' # strip leading hash
|
||||
end
|
||||
url[key] = value
|
||||
end
|
||||
|
||||
@response_diff = url[:actual].diff(url[:expected]) if url[:actual]
|
||||
msg = build_message(message, "expected a redirect to <?>, found one to <?>, a difference of <?> ", url[:expected], url[:actual], @response_diff)
|
||||
|
||||
assert_block(msg) do
|
||||
url[:expected].keys.all? do |k|
|
||||
if k == :controller then url[:expected][k] == ActionController::Routing.controller_relative_to(url[:actual][k], @controller.class.controller_path)
|
||||
else parameterize(url[:expected][k]) == parameterize(url[:actual][k])
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue ActionController::RoutingError # routing failed us, so match the strings only.
|
||||
msg = build_message(message, "expected a redirect to <?>, found one to <?>", options, @response.redirect_url)
|
||||
url_regexp = %r{^(\w+://.*?(/|$|\?))(.*)$}
|
||||
eurl, epath, url, path = [options, @response.redirect_url].collect do |url|
|
||||
u, p = (url_regexp =~ url) ? [$1, $3] : [nil, url]
|
||||
[u, (p.first == '/') ? p : '/' + p]
|
||||
end.flatten
|
||||
|
||||
assert_equal(eurl, url, msg) if eurl && url
|
||||
assert_equal(epath, path, msg) if epath && path
|
||||
if redirected_to_after_normalisation != options_after_normalisation
|
||||
flunk "Expected response to be a redirect to <#{options_after_normalisation}> but was a redirect to <#{redirected_to_after_normalisation}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -137,36 +87,37 @@ module ActionController
|
|||
#
|
||||
def assert_template(expected = nil, message=nil)
|
||||
clean_backtrace do
|
||||
rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file
|
||||
rendered = @response.rendered_template.to_s
|
||||
msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
|
||||
assert_block(msg) do
|
||||
if expected.nil?
|
||||
!@response.rendered_with_file?
|
||||
@response.rendered_template.blank?
|
||||
else
|
||||
expected == rendered
|
||||
rendered.to_s.match(expected)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Recognizes the route for a given path.
|
||||
def recognized_request_for(path, request_method = nil)
|
||||
path = "/#{path}" unless path.first == '/'
|
||||
|
||||
# Assume given controller
|
||||
request = ActionController::TestRequest.new({}, {}, nil)
|
||||
request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
|
||||
request.path = path
|
||||
|
||||
ActionController::Routing::Routes.recognize(request)
|
||||
request
|
||||
end
|
||||
|
||||
# Proxy to to_param if the object will respond to it.
|
||||
def parameterize(value)
|
||||
value.respond_to?(:to_param) ? value.to_param : value
|
||||
end
|
||||
|
||||
def normalize_argument_to_redirection(fragment)
|
||||
after_routing = @controller.url_for(fragment)
|
||||
if after_routing =~ %r{^\w+://.*}
|
||||
after_routing
|
||||
else
|
||||
# FIXME - this should probably get removed.
|
||||
if after_routing.first != '/'
|
||||
after_routing = '/' + after_routing
|
||||
end
|
||||
@request.protocol + @request.host_with_port + after_routing
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@ module ActionController
|
|||
module Assertions
|
||||
# Suite of assertions to test routes generated by Rails and the handling of requests made to them.
|
||||
module RoutingAssertions
|
||||
# Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
|
||||
# Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
|
||||
# match +path+. Basically, it asserts that Rails recognizes the route given by +expected_options+.
|
||||
#
|
||||
# Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
|
||||
|
@ -10,32 +10,32 @@ module ActionController
|
|||
# and a :method containing the required HTTP verb.
|
||||
#
|
||||
# # assert that POSTing to /items will call the create action on ItemsController
|
||||
# assert_recognizes({:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post})
|
||||
# assert_recognizes {:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post}
|
||||
#
|
||||
# You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
|
||||
# to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
|
||||
# extras argument, appending the query string on the path directly will not work. For example:
|
||||
# extras argument, appending the query string on the path directly will not work. For example:
|
||||
#
|
||||
# # assert that a path of '/items/list/1?view=print' returns the correct options
|
||||
# assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" })
|
||||
# assert_recognizes {:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" }
|
||||
#
|
||||
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
|
||||
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Check the default route (i.e., the index action)
|
||||
# assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
|
||||
# assert_recognizes {:controller => 'items', :action => 'index'}, 'items'
|
||||
#
|
||||
# # Test a specific action
|
||||
# assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list')
|
||||
# assert_recognizes {:controller => 'items', :action => 'list'}, 'items/list'
|
||||
#
|
||||
# # Test an action with a parameter
|
||||
# assert_recognizes({:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1')
|
||||
# assert_recognizes {:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1'
|
||||
#
|
||||
# # Test a custom route
|
||||
# assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1')
|
||||
# assert_recognizes {:controller => 'items', :action => 'show', :id => '1'}, 'view/item1'
|
||||
#
|
||||
# # Check a Simply RESTful generated route
|
||||
# assert_recognizes(list_items_url, 'items/list')
|
||||
# assert_recognizes list_items_url, 'items/list'
|
||||
def assert_recognizes(expected_options, path, extras={}, message=nil)
|
||||
if path.is_a? Hash
|
||||
request_method = path[:method]
|
||||
|
@ -44,16 +44,16 @@ module ActionController
|
|||
request_method = nil
|
||||
end
|
||||
|
||||
clean_backtrace do
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
clean_backtrace do
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
request = recognized_request_for(path, request_method)
|
||||
|
||||
|
||||
expected_options = expected_options.clone
|
||||
extras.each_key { |key| expected_options.delete key } unless extras.nil?
|
||||
|
||||
|
||||
expected_options.stringify_keys!
|
||||
routing_diff = expected_options.diff(request.path_parameters)
|
||||
msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>",
|
||||
msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>",
|
||||
request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
|
||||
assert_block(msg) { request.path_parameters == expected_options }
|
||||
end
|
||||
|
@ -64,67 +64,67 @@ module ActionController
|
|||
# a query string. The +message+ parameter allows you to specify a custom error message for assertion failures.
|
||||
#
|
||||
# The +defaults+ parameter is unused.
|
||||
#
|
||||
#
|
||||
# ==== Examples
|
||||
# # Asserts that the default action is generated for a route with no action
|
||||
# assert_generates("/items", :controller => "items", :action => "index")
|
||||
# assert_generates "/items", :controller => "items", :action => "index"
|
||||
#
|
||||
# # Tests that the list action is properly routed
|
||||
# assert_generates("/items/list", :controller => "items", :action => "list")
|
||||
# assert_generates "/items/list", :controller => "items", :action => "list"
|
||||
#
|
||||
# # Tests the generation of a route with a parameter
|
||||
# assert_generates("/items/list/1", { :controller => "items", :action => "list", :id => "1" })
|
||||
# assert_generates "/items/list/1", { :controller => "items", :action => "list", :id => "1" }
|
||||
#
|
||||
# # Asserts that the generated route gives us our custom route
|
||||
# assert_generates "changesets/12", { :controller => 'scm', :action => 'show_diff', :revision => "12" }
|
||||
def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
|
||||
clean_backtrace do
|
||||
clean_backtrace do
|
||||
expected_path = "/#{expected_path}" unless expected_path[0] == ?/
|
||||
# Load routes.rb if it hasn't been loaded.
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
|
||||
generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, defaults)
|
||||
found_extras = options.reject {|k, v| ! extra_keys.include? k}
|
||||
|
||||
msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
|
||||
assert_block(msg) { found_extras == extras }
|
||||
|
||||
msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
|
||||
|
||||
msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
|
||||
expected_path)
|
||||
assert_block(msg) { expected_path == generated_path }
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
|
||||
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
|
||||
# <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines +assert_recognizes+
|
||||
# and +assert_generates+ into one step.
|
||||
#
|
||||
# The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
|
||||
# +message+ parameter allows you to specify a custom error message to display upon failure.
|
||||
# +message+ parameter allows you to specify a custom error message to display upon failure.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Assert a basic route: a controller with the default action (index)
|
||||
# assert_routing('/home', :controller => 'home', :action => 'index')
|
||||
# assert_routing '/home', :controller => 'home', :action => 'index'
|
||||
#
|
||||
# # Test a route generated with a specific controller, action, and parameter (id)
|
||||
# assert_routing('/entries/show/23', :controller => 'entries', :action => 'show', id => 23)
|
||||
# assert_routing '/entries/show/23', :controller => 'entries', :action => 'show', id => 23
|
||||
#
|
||||
# # Assert a basic route (controller + default action), with an error message if it fails
|
||||
# assert_routing('/store', { :controller => 'store', :action => 'index' }, {}, {}, 'Route for store index not generated properly')
|
||||
# assert_routing '/store', { :controller => 'store', :action => 'index' }, {}, {}, 'Route for store index not generated properly'
|
||||
#
|
||||
# # Tests a route, providing a defaults hash
|
||||
# assert_routing 'controller/action/9', {:id => "9", :item => "square"}, {:controller => "controller", :action => "action"}, {}, {:item => "square"}
|
||||
#
|
||||
# # Tests a route with a HTTP method
|
||||
# assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" })
|
||||
# assert_routing { :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" }
|
||||
def assert_routing(path, options, defaults={}, extras={}, message=nil)
|
||||
assert_recognizes(options, path, extras, message)
|
||||
|
||||
controller, default_controller = options[:controller], defaults[:controller]
|
||||
|
||||
controller, default_controller = options[:controller], defaults[:controller]
|
||||
if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
|
||||
options[:controller] = "/#{controller}"
|
||||
end
|
||||
|
||||
|
||||
assert_generates(path.is_a?(Hash) ? path[:path] : path, options, defaults, extras, message)
|
||||
end
|
||||
|
||||
|
|
|
@ -21,10 +21,8 @@ module ActionController
|
|||
# from the response HTML or elements selected by the enclosing assertion.
|
||||
#
|
||||
# In addition to HTML responses, you can make the following assertions:
|
||||
# * +assert_select_rjs+ - Assertions on HTML content of RJS update and
|
||||
# insertion operations.
|
||||
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML,
|
||||
# for example for dealing with feed item descriptions.
|
||||
# * +assert_select_rjs+ - Assertions on HTML content of RJS update and insertion operations.
|
||||
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions.
|
||||
# * +assert_select_email+ - Assertions on the HTML body of an e-mail.
|
||||
#
|
||||
# Also see HTML::Selector to learn how to use selectors.
|
||||
|
@ -451,7 +449,13 @@ module ActionController
|
|||
matches
|
||||
else
|
||||
# RJS statement not found.
|
||||
flunk args.shift || "No RJS statement that replaces or inserts HTML content."
|
||||
case rjs_type
|
||||
when :remove, :show, :hide, :toggle
|
||||
flunk_message = "No RJS statement that #{rjs_type.to_s}s '#{id}' was rendered."
|
||||
else
|
||||
flunk_message = "No RJS statement that replaces or inserts HTML content."
|
||||
end
|
||||
flunk args.shift || flunk_message
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -252,7 +252,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# def do_something
|
||||
# redirect_to(:action => "elsewhere") and return if monkeys.nil?
|
||||
# render :action => "overthere" # won't be called unless monkeys is nil
|
||||
# render :action => "overthere" # won't be called if monkeys is nil
|
||||
# end
|
||||
#
|
||||
class Base
|
||||
|
@ -260,10 +260,11 @@ module ActionController #:nodoc:
|
|||
|
||||
include StatusCodes
|
||||
|
||||
cattr_reader :protected_instance_variables
|
||||
# Controller specific instance variables which will not be accessible inside views.
|
||||
@@protected_view_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller
|
||||
@action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params
|
||||
@_flash @_response)
|
||||
@@protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller
|
||||
@action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params
|
||||
@_flash @_response)
|
||||
|
||||
# Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
|
||||
# and images to a dedicated asset server away from the main web server. Example:
|
||||
|
@ -283,10 +284,9 @@ module ActionController #:nodoc:
|
|||
@@debug_routes = true
|
||||
cattr_accessor :debug_routes
|
||||
|
||||
# Indicates to Mongrel or Webrick whether to allow concurrent action
|
||||
# processing. Your controller actions and any other code they call must
|
||||
# also behave well when called from concurrent threads. Turned off by
|
||||
# default.
|
||||
# Indicates whether to allow concurrent action processing. Your
|
||||
# controller actions and any other code they call must also behave well
|
||||
# when called from concurrent threads. Turned off by default.
|
||||
@@allow_concurrency = false
|
||||
cattr_accessor :allow_concurrency
|
||||
|
||||
|
@ -347,10 +347,29 @@ module ActionController #:nodoc:
|
|||
cattr_accessor :optimise_named_routes
|
||||
self.optimise_named_routes = true
|
||||
|
||||
# Indicates whether the response format should be determined by examining the Accept HTTP header,
|
||||
# or by using the simpler params + ajax rules.
|
||||
#
|
||||
# If this is set to +true+ (the default) then +respond_to+ and +Request#format+ will take the Accept
|
||||
# header into account. If it is set to false then the request format will be determined solely
|
||||
# by examining params[:format]. If params format is missing, the format will be either HTML or
|
||||
# Javascript depending on whether the request is an AJAX request.
|
||||
cattr_accessor :use_accept_header
|
||||
self.use_accept_header = true
|
||||
|
||||
# Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode.
|
||||
class_inheritable_accessor :allow_forgery_protection
|
||||
self.allow_forgery_protection = true
|
||||
|
||||
# If you are deploying to a subdirectory, you will need to set
|
||||
# <tt>config.action_controller.relative_url_root</tt>
|
||||
# This defaults to ENV['RAILS_RELATIVE_URL_ROOT']
|
||||
cattr_writer :relative_url_root
|
||||
|
||||
def self.relative_url_root
|
||||
@@relative_url_root || ENV['RAILS_RELATIVE_URL_ROOT']
|
||||
end
|
||||
|
||||
# Holds the request object that's primarily used to get environment variables through access like
|
||||
# <tt>request.env["REQUEST_URI"]</tt>.
|
||||
attr_internal :request
|
||||
|
@ -373,16 +392,9 @@ module ActionController #:nodoc:
|
|||
# directive. Values should always be specified as strings.
|
||||
attr_internal :headers
|
||||
|
||||
# Holds the hash of variables that are passed on to the template class to be made available to the view. This hash
|
||||
# is generated by taking a snapshot of all the instance variables in the current scope just before a template is rendered.
|
||||
attr_accessor :assigns
|
||||
|
||||
# Returns the name of the action this controller is processing.
|
||||
attr_accessor :action_name
|
||||
|
||||
# Templates that are exempt from layouts
|
||||
@@exempt_from_layout = Set.new([/\.rjs$/])
|
||||
|
||||
class << self
|
||||
# Factory for the standard create, process loop where the controller is discarded after processing.
|
||||
def process(request, response) #:nodoc:
|
||||
|
@ -408,28 +420,27 @@ module ActionController #:nodoc:
|
|||
# By default, all methods defined in ActionController::Base and included modules are hidden.
|
||||
# More methods can be hidden using <tt>hide_actions</tt>.
|
||||
def hidden_actions
|
||||
unless read_inheritable_attribute(:hidden_actions)
|
||||
write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map(&:to_s))
|
||||
end
|
||||
|
||||
read_inheritable_attribute(:hidden_actions)
|
||||
read_inheritable_attribute(:hidden_actions) || write_inheritable_attribute(:hidden_actions, [])
|
||||
end
|
||||
|
||||
# Hide each of the given methods from being callable as actions.
|
||||
def hide_action(*names)
|
||||
write_inheritable_attribute(:hidden_actions, hidden_actions | names.map(&:to_s))
|
||||
write_inheritable_attribute(:hidden_actions, hidden_actions | names.map { |name| name.to_s })
|
||||
end
|
||||
|
||||
## View load paths determine the bases from which template references can be made. So a call to
|
||||
## render("test/template") will be looked up in the view load paths array and the closest match will be
|
||||
## returned.
|
||||
# View load paths determine the bases from which template references can be made. So a call to
|
||||
# render("test/template") will be looked up in the view load paths array and the closest match will be
|
||||
# returned.
|
||||
def view_paths
|
||||
@view_paths || superclass.view_paths
|
||||
if defined? @view_paths
|
||||
@view_paths
|
||||
else
|
||||
superclass.view_paths
|
||||
end
|
||||
end
|
||||
|
||||
def view_paths=(value)
|
||||
@view_paths = value
|
||||
ActionView::TemplateFinder.process_view_paths(value)
|
||||
@view_paths = ActionView::Base.process_view_paths(value) if value
|
||||
end
|
||||
|
||||
# Adds a view_path to the front of the view_paths array.
|
||||
|
@ -440,9 +451,8 @@ module ActionController #:nodoc:
|
|||
# ArticleController.prepend_view_path(["views/default", "views/custom"])
|
||||
#
|
||||
def prepend_view_path(path)
|
||||
@view_paths = superclass.view_paths.dup if @view_paths.nil?
|
||||
view_paths.unshift(*path)
|
||||
ActionView::TemplateFinder.process_view_paths(path)
|
||||
@view_paths = superclass.view_paths.dup if !defined?(@view_paths) || @view_paths.nil?
|
||||
@view_paths.unshift(*path)
|
||||
end
|
||||
|
||||
# Adds a view_path to the end of the view_paths array.
|
||||
|
@ -454,8 +464,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
def append_view_path(path)
|
||||
@view_paths = superclass.view_paths.dup if @view_paths.nil?
|
||||
view_paths.push(*path)
|
||||
ActionView::TemplateFinder.process_view_paths(path)
|
||||
@view_paths.push(*path)
|
||||
end
|
||||
|
||||
# Replace sensitive parameter data from the request log.
|
||||
|
@ -507,38 +516,34 @@ module ActionController #:nodoc:
|
|||
protected :filter_parameters
|
||||
end
|
||||
|
||||
# Don't render layouts for templates with the given extensions.
|
||||
def exempt_from_layout(*extensions)
|
||||
regexps = extensions.collect do |extension|
|
||||
extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/
|
||||
end
|
||||
@@exempt_from_layout.merge regexps
|
||||
end
|
||||
delegate :exempt_from_layout, :to => 'ActionView::Base'
|
||||
end
|
||||
|
||||
public
|
||||
# Extracts the action_name from the request parameters and performs that action.
|
||||
def process(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||
response.request = request
|
||||
|
||||
initialize_template_class(response)
|
||||
assign_shortcuts(request, response)
|
||||
initialize_current_url
|
||||
assign_names
|
||||
forget_variables_added_to_assigns
|
||||
|
||||
log_processing
|
||||
send(method, *arguments)
|
||||
|
||||
assign_default_content_type_and_charset
|
||||
|
||||
response.request = request
|
||||
response.prepare! unless component_request?
|
||||
response
|
||||
send_response
|
||||
ensure
|
||||
process_cleanup
|
||||
end
|
||||
|
||||
# Returns a URL that has been rewritten according to the options hash and the defined Routes.
|
||||
# (For doing a complete redirect, use redirect_to).
|
||||
def send_response
|
||||
response.prepare! unless component_request?
|
||||
response
|
||||
end
|
||||
|
||||
# Returns a URL that has been rewritten according to the options hash and the defined routes.
|
||||
# (For doing a complete redirect, use +redirect_to+).
|
||||
#
|
||||
# <tt>url_for</tt> is used to:
|
||||
#
|
||||
|
@ -578,7 +583,15 @@ module ActionController #:nodoc:
|
|||
# missing values in the current request's parameters. Routes attempts to guess when a value should and should not be
|
||||
# taken from the defaults. There are a few simple rules on how this is performed:
|
||||
#
|
||||
# * If the controller name begins with a slash, no defaults are used: <tt>url_for :controller => '/home'</tt>
|
||||
# * If the controller name begins with a slash no defaults are used:
|
||||
#
|
||||
# url_for :controller => '/home'
|
||||
#
|
||||
# In particular, a leading slash ensures no namespace is assumed. Thus,
|
||||
# while <tt>url_for :controller => 'users'</tt> may resolve to
|
||||
# <tt>Admin::UsersController</tt> if the current controller lives under
|
||||
# that module, <tt>url_for :controller => '/users'</tt> ensures you link
|
||||
# to <tt>::UsersController</tt> no matter what.
|
||||
# * If the controller changes, the action will default to index unless provided
|
||||
#
|
||||
# The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the
|
||||
|
@ -648,11 +661,11 @@ module ActionController #:nodoc:
|
|||
|
||||
# View load paths for controller.
|
||||
def view_paths
|
||||
@template.finder.view_paths
|
||||
@template.view_paths
|
||||
end
|
||||
|
||||
def view_paths=(value)
|
||||
@template.finder.view_paths = value # Mutex needed
|
||||
@template.view_paths = ActionView::Base.process_view_paths(value)
|
||||
end
|
||||
|
||||
# Adds a view_path to the front of the view_paths array.
|
||||
|
@ -662,7 +675,7 @@ module ActionController #:nodoc:
|
|||
# self.prepend_view_path(["views/default", "views/custom"])
|
||||
#
|
||||
def prepend_view_path(path)
|
||||
@template.finder.prepend_view_path(path) # Mutex needed
|
||||
@template.view_paths.unshift(*path)
|
||||
end
|
||||
|
||||
# Adds a view_path to the end of the view_paths array.
|
||||
|
@ -672,7 +685,7 @@ module ActionController #:nodoc:
|
|||
# self.append_view_path(["views/default", "views/custom"])
|
||||
#
|
||||
def append_view_path(path)
|
||||
@template.finder.append_view_path(path) # Mutex needed
|
||||
@template.view_paths.push(*path)
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -713,6 +726,9 @@ module ActionController #:nodoc:
|
|||
# # builds the complete response.
|
||||
# render :partial => "person", :collection => @winners
|
||||
#
|
||||
# # Renders a collection of partials but with a custom local variable name
|
||||
# render :partial => "admin_person", :collection => @winners, :as => :person
|
||||
#
|
||||
# # Renders the same collection of partials, but also renders the
|
||||
# # person_divider partial between each person partial.
|
||||
# render :partial => "person", :collection => @winners, :spacer_template => "person_divider"
|
||||
|
@ -760,9 +776,6 @@ module ActionController #:nodoc:
|
|||
# render :file => "/path/to/some/template.erb", :layout => true, :status => 404
|
||||
# render :file => "c:/path/to/some/template.erb", :layout => true, :status => 404
|
||||
#
|
||||
# # Renders a template relative to the template root and chooses the proper file extension
|
||||
# render :file => "some/template", :use_full_path => true
|
||||
#
|
||||
# === Rendering text
|
||||
#
|
||||
# Rendering of text is usually used for tests or for rendering prepared content, such as a cache. By default, text
|
||||
|
@ -842,7 +855,7 @@ module ActionController #:nodoc:
|
|||
raise DoubleRenderError, "Can only render or redirect once per action" if performed?
|
||||
|
||||
if options.nil?
|
||||
return render_for_file(default_template_name, nil, true)
|
||||
return render(:file => default_template_name, :layout => true)
|
||||
elsif !extra_options.is_a?(Hash)
|
||||
raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}"
|
||||
else
|
||||
|
@ -853,6 +866,9 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
response.layout = layout = pick_layout(options)
|
||||
logger.info("Rendering template within #{layout}") if logger && layout
|
||||
|
||||
if content_type = options[:content_type]
|
||||
response.content_type = content_type.to_s
|
||||
end
|
||||
|
@ -862,27 +878,21 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
if options.has_key?(:text)
|
||||
render_for_text(options[:text], options[:status])
|
||||
text = layout ? @template.render(options.merge(:text => options[:text], :layout => layout)) : options[:text]
|
||||
render_for_text(text, options[:status])
|
||||
|
||||
else
|
||||
if file = options[:file]
|
||||
render_for_file(file, options[:status], options[:use_full_path], options[:locals] || {})
|
||||
render_for_file(file, options[:status], layout, options[:locals] || {})
|
||||
|
||||
elsif template = options[:template]
|
||||
render_for_file(template, options[:status], true, options[:locals] || {})
|
||||
render_for_file(template, options[:status], layout, options[:locals] || {})
|
||||
|
||||
elsif inline = options[:inline]
|
||||
add_variables_to_assigns
|
||||
tmpl = ActionView::InlineTemplate.new(@template, options[:inline], options[:locals], options[:type])
|
||||
render_for_text(@template.render_template(tmpl), options[:status])
|
||||
render_for_text(@template.render(options.merge(:layout => layout)), options[:status])
|
||||
|
||||
elsif action_name = options[:action]
|
||||
template = default_template_name(action_name.to_s)
|
||||
if options[:layout] && !template_exempt_from_layout?(template)
|
||||
render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true)
|
||||
else
|
||||
render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true)
|
||||
end
|
||||
render_for_file(default_template_name(action_name.to_s), options[:status], layout)
|
||||
|
||||
elsif xml = options[:xml]
|
||||
response.content_type ||= Mime::XML
|
||||
|
@ -894,36 +904,26 @@ module ActionController #:nodoc:
|
|||
response.content_type ||= Mime::JSON
|
||||
render_for_text(json, options[:status])
|
||||
|
||||
elsif partial = options[:partial]
|
||||
partial = default_template_name if partial == true
|
||||
add_variables_to_assigns
|
||||
|
||||
if collection = options[:collection]
|
||||
render_for_text(
|
||||
@template.send!(:render_partial_collection, partial, collection,
|
||||
options[:spacer_template], options[:locals]), options[:status]
|
||||
)
|
||||
elsif options[:partial]
|
||||
options[:partial] = default_template_name if options[:partial] == true
|
||||
if layout
|
||||
render_for_text(@template.render(:text => @template.render(options), :layout => layout), options[:status])
|
||||
else
|
||||
render_for_text(
|
||||
@template.send!(:render_partial, partial,
|
||||
ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]), options[:status]
|
||||
)
|
||||
render_for_text(@template.render(options), options[:status])
|
||||
end
|
||||
|
||||
elsif options[:update]
|
||||
add_variables_to_assigns
|
||||
@template.send! :evaluate_assigns
|
||||
@template.send(:_evaluate_assigns_and_ivars)
|
||||
|
||||
generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
|
||||
response.content_type = Mime::JS
|
||||
render_for_text(generator.to_s, options[:status])
|
||||
|
||||
elsif options[:nothing]
|
||||
# Safari doesn't pass the headers of the return if the response is zero length
|
||||
render_for_text(" ", options[:status])
|
||||
render_for_text(nil, options[:status])
|
||||
|
||||
else
|
||||
render_for_file(default_template_name, options[:status], true)
|
||||
render_for_file(default_template_name, options[:status], layout)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -934,7 +934,6 @@ module ActionController #:nodoc:
|
|||
render(options, &block)
|
||||
ensure
|
||||
erase_render_results
|
||||
forget_variables_added_to_assigns
|
||||
reset_variables_added_to_assigns
|
||||
end
|
||||
|
||||
|
@ -966,7 +965,6 @@ module ActionController #:nodoc:
|
|||
render :nothing => true, :status => status
|
||||
end
|
||||
|
||||
|
||||
# Clears the rendered results, allowing for another render to be performed.
|
||||
def erase_render_results #:nodoc:
|
||||
response.body = nil
|
||||
|
@ -1051,26 +1049,73 @@ module ActionController #:nodoc:
|
|||
status = 302
|
||||
end
|
||||
|
||||
response.redirected_to= options
|
||||
logger.info("Redirected to #{options}") if logger && logger.info?
|
||||
|
||||
case options
|
||||
when %r{^\w+://.*}
|
||||
raise DoubleRenderError if performed?
|
||||
logger.info("Redirected to #{options}") if logger && logger.info?
|
||||
response.redirect(options, interpret_status(status))
|
||||
response.redirected_to = options
|
||||
@performed_redirect = true
|
||||
|
||||
redirect_to_full_url(options, status)
|
||||
when String
|
||||
redirect_to(request.protocol + request.host_with_port + options, :status=>status)
|
||||
|
||||
redirect_to_full_url(request.protocol + request.host_with_port + options, status)
|
||||
when :back
|
||||
request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"], :status=>status) : raise(RedirectBackError)
|
||||
|
||||
when Hash
|
||||
redirect_to(url_for(options), :status=>status)
|
||||
response.redirected_to = options
|
||||
|
||||
if referer = request.headers["Referer"]
|
||||
redirect_to(referer, :status=>status)
|
||||
else
|
||||
raise RedirectBackError
|
||||
end
|
||||
else
|
||||
redirect_to(url_for(options), :status=>status)
|
||||
redirect_to_full_url(url_for(options), status)
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_to_full_url(url, status)
|
||||
raise DoubleRenderError if performed?
|
||||
response.redirect(url, interpret_status(status))
|
||||
@performed_redirect = true
|
||||
end
|
||||
|
||||
# Sets the etag and/or last_modified on the response and checks it against
|
||||
# the client request. If the request doesn't match the options provided, the
|
||||
# request is considered stale and should be generated from scratch. Otherwise,
|
||||
# it's fresh and we don't need to generate anything and a reply of "304 Not Modified" is sent.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# def show
|
||||
# @article = Article.find(params[:id])
|
||||
#
|
||||
# if stale?(:etag => @article, :last_modified => @article.created_at.utc)
|
||||
# @statistics = @article.really_expensive_call
|
||||
# respond_to do |format|
|
||||
# # all the supported formats
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
def stale?(options)
|
||||
fresh_when(options)
|
||||
!request.fresh?(response)
|
||||
end
|
||||
|
||||
# Sets the etag, last_modified, or both on the response and renders a
|
||||
# "304 Not Modified" response if the request is already fresh.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# def show
|
||||
# @article = Article.find(params[:id])
|
||||
# fresh_when(:etag => @article, :last_modified => @article.created_at.utc)
|
||||
# end
|
||||
#
|
||||
# This will render the show template if the request isn't sending a matching etag or
|
||||
# If-Modified-Since header and just a "304 Not Modified" response if there's a match.
|
||||
def fresh_when(options)
|
||||
options.assert_valid_keys(:etag, :last_modified)
|
||||
|
||||
response.etag = options[:etag] if options[:etag]
|
||||
response.last_modified = options[:last_modified] if options[:last_modified]
|
||||
|
||||
if request.fresh?(response)
|
||||
head :not_modified
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1106,10 +1151,9 @@ module ActionController #:nodoc:
|
|||
|
||||
|
||||
private
|
||||
def render_for_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc:
|
||||
add_variables_to_assigns
|
||||
def render_for_file(template_path, status = nil, layout = nil, locals = {}) #:nodoc:
|
||||
logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
|
||||
render_for_text(@template.render_file(template_path, use_full_path, locals), status)
|
||||
render_for_text @template.render(:file => template_path, :locals => locals, :layout => layout), status
|
||||
end
|
||||
|
||||
def render_for_text(text = nil, status = nil, append_response = false) #:nodoc:
|
||||
|
@ -1121,13 +1165,17 @@ module ActionController #:nodoc:
|
|||
response.body ||= ''
|
||||
response.body << text.to_s
|
||||
else
|
||||
response.body = text.is_a?(Proc) ? text : text.to_s
|
||||
response.body = case text
|
||||
when Proc then text
|
||||
when nil then " " # Safari doesn't pass the headers of the return if the response is zero length
|
||||
else text.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize_template_class(response)
|
||||
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
|
||||
response.template.extend self.class.master_helper_module
|
||||
response.template.helpers.send :include, self.class.master_helper_module
|
||||
response.redirected_to = nil
|
||||
@performed_render = @performed_redirect = false
|
||||
end
|
||||
|
@ -1140,7 +1188,6 @@ module ActionController #:nodoc:
|
|||
|
||||
@_session = @_response.session
|
||||
@template = @_response.template
|
||||
@assigns = @_response.template.assigns
|
||||
|
||||
@_headers = @_response.headers
|
||||
end
|
||||
|
@ -1162,16 +1209,16 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def perform_action
|
||||
if self.class.action_methods.include?(action_name)
|
||||
if action_methods.include?(action_name)
|
||||
send(action_name)
|
||||
default_render unless performed?
|
||||
elsif respond_to? :method_missing
|
||||
method_missing action_name
|
||||
default_render unless performed?
|
||||
elsif template_exists? && template_public?
|
||||
elsif template_exists?
|
||||
default_render
|
||||
else
|
||||
raise UnknownAction, "No action responded to #{action_name}", caller
|
||||
raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence}", caller
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1184,43 +1231,30 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def assign_default_content_type_and_charset
|
||||
response.content_type ||= Mime::HTML
|
||||
response.charset ||= self.class.default_charset unless sending_file?
|
||||
end
|
||||
|
||||
def sending_file?
|
||||
response.headers["Content-Transfer-Encoding"] == "binary"
|
||||
response.assign_default_content_type_and_charset!
|
||||
end
|
||||
deprecate :assign_default_content_type_and_charset => :'response.assign_default_content_type_and_charset!'
|
||||
|
||||
def action_methods
|
||||
self.class.action_methods
|
||||
end
|
||||
|
||||
def self.action_methods
|
||||
@action_methods ||= Set.new(public_instance_methods.map(&:to_s)) - hidden_actions
|
||||
end
|
||||
|
||||
def add_variables_to_assigns
|
||||
unless @variables_added
|
||||
add_instance_variables_to_assigns
|
||||
@variables_added = true
|
||||
end
|
||||
end
|
||||
|
||||
def forget_variables_added_to_assigns
|
||||
@variables_added = nil
|
||||
@action_methods ||=
|
||||
# All public instance methods of this class, including ancestors
|
||||
public_instance_methods(true).map { |m| m.to_s }.to_set -
|
||||
# Except for public instance methods of Base and its ancestors
|
||||
Base.public_instance_methods(true).map { |m| m.to_s } +
|
||||
# Be sure to include shadowed public instance methods of this class
|
||||
public_instance_methods(false).map { |m| m.to_s } -
|
||||
# And always exclude explicitly hidden actions
|
||||
hidden_actions
|
||||
end
|
||||
|
||||
def reset_variables_added_to_assigns
|
||||
@template.instance_variable_set("@assigns_added", nil)
|
||||
end
|
||||
|
||||
def add_instance_variables_to_assigns
|
||||
(instance_variable_names - @@protected_view_variables).each do |var|
|
||||
@assigns[var[1..-1]] = instance_variable_get(var)
|
||||
end
|
||||
end
|
||||
|
||||
def request_origin
|
||||
# this *needs* to be cached!
|
||||
# otherwise you'd get different results if calling it more than once
|
||||
|
@ -1236,17 +1270,9 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def template_exists?(template_name = default_template_name)
|
||||
@template.finder.file_exists?(template_name)
|
||||
end
|
||||
|
||||
def template_public?(template_name = default_template_name)
|
||||
@template.file_public?(template_name)
|
||||
end
|
||||
|
||||
def template_exempt_from_layout?(template_name = default_template_name)
|
||||
extension = @template && @template.finder.pick_template_extension(template_name)
|
||||
name_with_extension = !template_name.include?('.') && extension ? "#{template_name}.#{extension}" : template_name
|
||||
@@exempt_from_layout.any? { |ext| name_with_extension =~ ext }
|
||||
@template.send(:_pick_template, template_name) ? true : false
|
||||
rescue ActionView::MissingTemplate
|
||||
false
|
||||
end
|
||||
|
||||
def default_template_name(action_name = self.action_name)
|
||||
|
@ -1256,7 +1282,7 @@ module ActionController #:nodoc:
|
|||
action_name = strip_out_controller(action_name)
|
||||
end
|
||||
end
|
||||
"#{self.class.controller_path}/#{action_name}"
|
||||
"#{self.controller_path}/#{action_name}"
|
||||
end
|
||||
|
||||
def strip_out_controller(path)
|
||||
|
@ -1264,7 +1290,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def template_path_includes_controller?(path)
|
||||
self.class.controller_path.split('/')[-1] == path.split('/')[0]
|
||||
self.controller_path.split('/')[-1] == path.split('/')[0]
|
||||
end
|
||||
|
||||
def process_cleanup
|
||||
|
|
|
@ -24,7 +24,7 @@ module ActionController #:nodoc:
|
|||
if logger && logger.level == log_level
|
||||
result = nil
|
||||
seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
|
||||
logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
|
||||
logger.add(log_level, "#{title} (#{('%.1f' % (seconds * 1000))}ms)")
|
||||
result
|
||||
else
|
||||
yield
|
||||
|
@ -42,53 +42,66 @@ module ActionController #:nodoc:
|
|||
|
||||
protected
|
||||
def render_with_benchmark(options = nil, extra_options = {}, &block)
|
||||
unless logger
|
||||
render_without_benchmark(options, extra_options, &block)
|
||||
else
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
if logger
|
||||
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime
|
||||
end
|
||||
|
||||
render_output = nil
|
||||
@rendering_runtime = Benchmark::realtime{ render_output = render_without_benchmark(options, extra_options, &block) }
|
||||
@view_runtime = Benchmark::realtime { render_output = render_without_benchmark(options, extra_options, &block) }
|
||||
|
||||
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
@db_rt_before_render = db_runtime
|
||||
@db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
|
||||
@rendering_runtime -= @db_rt_after_render
|
||||
@view_runtime -= @db_rt_after_render
|
||||
end
|
||||
|
||||
render_output
|
||||
else
|
||||
render_without_benchmark(options, extra_options, &block)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def perform_action_with_benchmark
|
||||
unless logger
|
||||
perform_action_without_benchmark
|
||||
else
|
||||
runtime = [ Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001 ].max
|
||||
if logger
|
||||
seconds = [ Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001 ].max
|
||||
logging_view = defined?(@view_runtime)
|
||||
logging_active_record = Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
|
||||
log_message = "Completed in #{sprintf("%.0f", seconds * 1000)}ms"
|
||||
|
||||
if logging_view || logging_active_record
|
||||
log_message << " ("
|
||||
log_message << view_runtime if logging_view
|
||||
|
||||
if logging_active_record
|
||||
log_message << ", " if logging_view
|
||||
log_message << active_record_runtime + ")"
|
||||
else
|
||||
")"
|
||||
end
|
||||
end
|
||||
|
||||
log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
|
||||
log_message << rendering_runtime(runtime) if defined?(@rendering_runtime)
|
||||
log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
log_message << " | #{headers["Status"]}"
|
||||
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
||||
|
||||
logger.info(log_message)
|
||||
response.headers["X-Runtime"] = sprintf("%.5f", runtime)
|
||||
response.headers["X-Runtime"] = "#{sprintf("%.0f", seconds * 1000)}ms"
|
||||
else
|
||||
perform_action_without_benchmark
|
||||
end
|
||||
end
|
||||
|
||||
def rendering_runtime(runtime)
|
||||
percentage = @rendering_runtime * 100 / runtime
|
||||
" | Rendering: %.5f (%d%%)" % [@rendering_runtime, percentage.to_i]
|
||||
def view_runtime
|
||||
"View: %.0f" % (@view_runtime * 1000)
|
||||
end
|
||||
|
||||
def active_record_runtime(runtime)
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime
|
||||
db_runtime += @db_rt_before_render if @db_rt_before_render
|
||||
db_runtime += @db_rt_after_render if @db_rt_after_render
|
||||
db_percentage = db_runtime * 100 / runtime
|
||||
" | DB: %.5f (%d%%)" % [db_runtime, db_percentage.to_i]
|
||||
def active_record_runtime
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime
|
||||
db_runtime += @db_rt_before_render if @db_rt_before_render
|
||||
db_runtime += @db_rt_after_render if @db_rt_after_render
|
||||
"DB: %.0f" % (db_runtime * 1000)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,19 +27,23 @@ module ActionController #:nodoc:
|
|||
# You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy
|
||||
# for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
|
||||
#
|
||||
# And you can also use :if to pass a Proc that specifies when the action should be cached.
|
||||
# And you can also use :if (or :unless) to pass a Proc that specifies when the action should be cached.
|
||||
#
|
||||
# Finally, if you are using memcached, you can also pass :expires_in.
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# before_filter :authenticate, :except => :public
|
||||
# caches_page :public
|
||||
# caches_action :index, :if => Proc.new { |c| !c.request.format.json? } # cache if is not a JSON request
|
||||
# caches_action :show, :cache_path => { :project => 1 }
|
||||
# caches_action :show, :cache_path => { :project => 1 }, :expires_in => 1.hour
|
||||
# caches_action :feed, :cache_path => Proc.new { |controller|
|
||||
# controller.params[:user_id] ?
|
||||
# controller.send(:user_list_url, c.params[:user_id], c.params[:id]) :
|
||||
# controller.send(:list_url, c.params[:id]) }
|
||||
# controller.send(:user_list_url, controller.params[:user_id], controller.params[:id]) :
|
||||
# controller.send(:list_url, controller.params[:id]) }
|
||||
# end
|
||||
#
|
||||
# If you pass :layout => false, it will only cache your action content. It is useful when your layout has dynamic information.
|
||||
#
|
||||
module Actions
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
|
@ -54,7 +58,10 @@ module ActionController #:nodoc:
|
|||
def caches_action(*actions)
|
||||
return unless cache_configured?
|
||||
options = actions.extract_options!
|
||||
around_filter(ActionCacheFilter.new(:cache_path => options.delete(:cache_path)), {:only => actions}.merge(options))
|
||||
filter_options = { :only => actions, :if => options.delete(:if), :unless => options.delete(:unless) }
|
||||
|
||||
cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options)
|
||||
around_filter(cache_filter, filter_options)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -64,10 +71,10 @@ module ActionController #:nodoc:
|
|||
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action })))
|
||||
expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action }), false))
|
||||
end
|
||||
else
|
||||
expire_fragment(ActionCachePath.path_for(self, options))
|
||||
expire_fragment(ActionCachePath.path_for(self, options, false))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -77,11 +84,13 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def before(controller)
|
||||
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options))
|
||||
if cache = controller.read_fragment(cache_path.path)
|
||||
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options.slice(:cache_path)))
|
||||
if cache = controller.read_fragment(cache_path.path, @options[:store_options])
|
||||
controller.rendered_action_cache = true
|
||||
set_content_type!(controller, cache_path.extension)
|
||||
controller.send!(:render_for_text, cache)
|
||||
options = { :text => cache }
|
||||
options.merge!(:layout => true) if cache_layout?
|
||||
controller.__send__(:render, options)
|
||||
false
|
||||
else
|
||||
controller.action_cache_path = cache_path
|
||||
|
@ -90,7 +99,8 @@ module ActionController #:nodoc:
|
|||
|
||||
def after(controller)
|
||||
return if controller.rendered_action_cache || !caching_allowed(controller)
|
||||
controller.write_fragment(controller.action_cache_path.path, controller.response.body)
|
||||
action_content = cache_layout? ? content_for_layout(controller) : controller.response.body
|
||||
controller.write_fragment(controller.action_cache_path.path, action_content, @options[:store_options])
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -105,22 +115,38 @@ module ActionController #:nodoc:
|
|||
def caching_allowed(controller)
|
||||
controller.request.get? && controller.response.headers['Status'].to_i == 200
|
||||
end
|
||||
|
||||
def cache_layout?
|
||||
@options[:layout] == false
|
||||
end
|
||||
|
||||
def content_for_layout(controller)
|
||||
controller.response.layout && controller.response.template.instance_variable_get('@cached_content_for_layout')
|
||||
end
|
||||
end
|
||||
|
||||
class ActionCachePath
|
||||
attr_reader :path, :extension
|
||||
|
||||
class << self
|
||||
def path_for(controller, options)
|
||||
new(controller, options).path
|
||||
def path_for(controller, options, infer_extension=true)
|
||||
new(controller, options, infer_extension).path
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(controller, options = {})
|
||||
@extension = extract_extension(controller.request.path)
|
||||
|
||||
# When true, infer_extension will look up the cache path extension from the request's path & format.
|
||||
# This is desirable when reading and writing the cache, but not when expiring the cache - expire_action should expire the same files regardless of the request format.
|
||||
def initialize(controller, options = {}, infer_extension=true)
|
||||
if infer_extension and options.is_a? Hash
|
||||
request_extension = extract_extension(controller.request)
|
||||
options = options.reverse_merge(:format => request_extension)
|
||||
end
|
||||
path = controller.url_for(options).split('://').last
|
||||
normalize!(path)
|
||||
add_extension!(path, @extension)
|
||||
if infer_extension
|
||||
@extension = request_extension
|
||||
add_extension!(path, @extension)
|
||||
end
|
||||
@path = URI.unescape(path)
|
||||
end
|
||||
|
||||
|
@ -130,13 +156,19 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def add_extension!(path, extension)
|
||||
path << ".#{extension}" if extension
|
||||
path << ".#{extension}" if extension and !path.ends_with?(extension)
|
||||
end
|
||||
|
||||
def extract_extension(file_path)
|
||||
|
||||
def extract_extension(request)
|
||||
# Don't want just what comes after the last '.' to accommodate multi part extensions
|
||||
# such as tar.gz.
|
||||
file_path[/^[^.]+\.(.+)$/, 1]
|
||||
extension = request.path[/^[^.]+\.(.+)$/, 1]
|
||||
|
||||
# If there's no extension in the path, check request.format
|
||||
if extension.nil?
|
||||
extension = request.cache_format
|
||||
end
|
||||
extension
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@ module ActionController #:nodoc:
|
|||
module Caching
|
||||
# Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when
|
||||
# certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple
|
||||
# parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like:
|
||||
# parties. The caching is done using the cache helper available in the Action View. A template with caching might look something like:
|
||||
#
|
||||
# <b>Hello <%= @name %></b>
|
||||
# <% cache do %>
|
||||
|
@ -26,32 +26,6 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")
|
||||
module Fragments
|
||||
def self.included(base) #:nodoc:
|
||||
base.class_eval do
|
||||
class << self
|
||||
def fragment_cache_store=(store_option) #:nodoc:
|
||||
ActiveSupport::Deprecation.warn('The fragment_cache_store= method is now use cache_store=')
|
||||
self.cache_store = store_option
|
||||
end
|
||||
|
||||
def fragment_cache_store #:nodoc:
|
||||
ActiveSupport::Deprecation.warn('The fragment_cache_store method is now use cache_store')
|
||||
cache_store
|
||||
end
|
||||
end
|
||||
|
||||
def fragment_cache_store=(store_option) #:nodoc:
|
||||
ActiveSupport::Deprecation.warn('The fragment_cache_store= method is now use cache_store=')
|
||||
self.cache_store = store_option
|
||||
end
|
||||
|
||||
def fragment_cache_store #:nodoc:
|
||||
ActiveSupport::Deprecation.warn('The fragment_cache_store method is now use cache_store')
|
||||
cache_store
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Given a key (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading,
|
||||
# writing, or expiring a cached fragment. If the key is a hash, the generated key is the return
|
||||
# value of url_for on that hash (without the protocol). All keys are prefixed with "views/" and uses
|
||||
|
@ -60,17 +34,17 @@ module ActionController #:nodoc:
|
|||
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
|
||||
end
|
||||
|
||||
def fragment_for(block, name = {}, options = nil) #:nodoc:
|
||||
unless perform_caching then block.call; return end
|
||||
|
||||
buffer = yield
|
||||
|
||||
if cache = read_fragment(name, options)
|
||||
buffer.concat(cache)
|
||||
def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
|
||||
if perform_caching
|
||||
if cache = read_fragment(name, options)
|
||||
buffer.concat(cache)
|
||||
else
|
||||
pos = buffer.length
|
||||
block.call
|
||||
write_fragment(name, buffer[pos..-1], options)
|
||||
end
|
||||
else
|
||||
pos = buffer.length
|
||||
block.call
|
||||
write_fragment(name, buffer[pos..-1], options)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -83,13 +83,13 @@ module ActionController #:nodoc:
|
|||
controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
|
||||
action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
|
||||
|
||||
send!(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
|
||||
send!(action_callback_method_name) if respond_to?(action_callback_method_name, true)
|
||||
__send__(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
|
||||
__send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
|
||||
end
|
||||
|
||||
def method_missing(method, *arguments)
|
||||
return if @controller.nil?
|
||||
@controller.send!(method, *arguments)
|
||||
@controller.__send__(method, *arguments)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,28 +6,8 @@ class CGI #:nodoc:
|
|||
# * Expose the CGI instance to session stores.
|
||||
# * Don't require 'digest/md5' whenever a new session id is generated.
|
||||
class Session #:nodoc:
|
||||
begin
|
||||
require 'securerandom'
|
||||
|
||||
# Generate a 32-character unique id using SecureRandom.
|
||||
# This is used to generate session ids but may be reused elsewhere.
|
||||
def self.generate_unique_id(constant = nil)
|
||||
SecureRandom.hex(16)
|
||||
end
|
||||
rescue LoadError
|
||||
# Generate an 32-character unique id based on a hash of the current time,
|
||||
# a random number, the process id, and a constant string. This is used
|
||||
# to generate session ids but may be reused elsewhere.
|
||||
def self.generate_unique_id(constant = 'foobar')
|
||||
md5 = Digest::MD5.new
|
||||
now = Time.now
|
||||
md5 << now.to_s
|
||||
md5 << String(now.usec)
|
||||
md5 << String(rand(0))
|
||||
md5 << String($$)
|
||||
md5 << constant
|
||||
md5.hexdigest
|
||||
end
|
||||
def self.generate_unique_id(constant = nil)
|
||||
ActiveSupport::SecureRandom.hex(16)
|
||||
end
|
||||
|
||||
# Make the CGI instance available to session stores.
|
||||
|
|
|
@ -42,13 +42,14 @@ module ActionController #:nodoc:
|
|||
:prefix => "ruby_sess.", # prefix session file names
|
||||
:session_path => "/", # available to all paths in app
|
||||
:session_key => "_session_id",
|
||||
:cookie_only => true
|
||||
} unless const_defined?(:DEFAULT_SESSION_OPTIONS)
|
||||
:cookie_only => true,
|
||||
:session_http_only=> true
|
||||
}
|
||||
|
||||
def initialize(cgi, session_options = {})
|
||||
@cgi = cgi
|
||||
@session_options = session_options
|
||||
@env = @cgi.send!(:env_table)
|
||||
@env = @cgi.__send__(:env_table)
|
||||
super()
|
||||
end
|
||||
|
||||
|
@ -61,53 +62,14 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# The request body is an IO input stream. If the RAW_POST_DATA environment
|
||||
# variable is already set, wrap it in a StringIO.
|
||||
def body
|
||||
if raw_post = env['RAW_POST_DATA']
|
||||
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
|
||||
StringIO.new(raw_post)
|
||||
else
|
||||
@cgi.stdinput
|
||||
end
|
||||
end
|
||||
|
||||
def query_parameters
|
||||
@query_parameters ||= self.class.parse_query_parameters(query_string)
|
||||
end
|
||||
|
||||
def request_parameters
|
||||
@request_parameters ||= parse_formatted_request_parameters
|
||||
def body_stream #:nodoc:
|
||||
@cgi.stdinput
|
||||
end
|
||||
|
||||
def cookies
|
||||
@cgi.cookies.freeze
|
||||
end
|
||||
|
||||
def host_with_port_without_standard_port_handling
|
||||
if forwarded = env["HTTP_X_FORWARDED_HOST"]
|
||||
forwarded.split(/,\s?/).last
|
||||
elsif http_host = env['HTTP_HOST']
|
||||
http_host
|
||||
elsif server_name = env['SERVER_NAME']
|
||||
server_name
|
||||
else
|
||||
"#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
|
||||
end
|
||||
end
|
||||
|
||||
def host
|
||||
host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
|
||||
end
|
||||
|
||||
def port
|
||||
if host_with_port_without_standard_port_handling =~ /:(\d+)$/
|
||||
$1.to_i
|
||||
else
|
||||
standard_port
|
||||
end
|
||||
end
|
||||
|
||||
def session
|
||||
unless defined?(@session)
|
||||
if @session_options == false
|
||||
|
@ -146,7 +108,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def method_missing(method_id, *arguments)
|
||||
@cgi.send!(method_id, *arguments) rescue super
|
||||
@cgi.__send__(method_id, *arguments) rescue super
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -203,7 +165,7 @@ end_msg
|
|||
begin
|
||||
output.write(@cgi.header(@headers))
|
||||
|
||||
if @cgi.send!(:env_table)['REQUEST_METHOD'] == 'HEAD'
|
||||
if @cgi.__send__(:env_table)['REQUEST_METHOD'] == 'HEAD'
|
||||
return
|
||||
elsif @body.respond_to?(:call)
|
||||
# Flush the output now in case the @body Proc uses
|
||||
|
|
|
@ -38,6 +38,7 @@ module ActionController #:nodoc:
|
|||
def self.included(base) #:nodoc:
|
||||
base.class_eval do
|
||||
include InstanceMethods
|
||||
include ActiveSupport::Deprecation
|
||||
extend ClassMethods
|
||||
helper HelperMethods
|
||||
|
||||
|
@ -64,7 +65,7 @@ module ActionController #:nodoc:
|
|||
|
||||
module HelperMethods
|
||||
def render_component(options)
|
||||
@controller.send!(:render_component_as_string, options)
|
||||
@controller.__send__(:render_component_as_string, options)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -82,6 +83,7 @@ module ActionController #:nodoc:
|
|||
render_for_text(component_response(options, true).body, response.headers["Status"])
|
||||
end
|
||||
end
|
||||
deprecate :render_component => "Please install render_component plugin from http://github.com/rails/render_component/tree/master"
|
||||
|
||||
# Returns the component response as a string
|
||||
def render_component_as_string(options) #:doc:
|
||||
|
@ -95,6 +97,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
end
|
||||
deprecate :render_component_as_string => "Please install render_component plugin from http://github.com/rails/render_component/tree/master"
|
||||
|
||||
def flash_with_components(refresh = false) #:nodoc:
|
||||
if !defined?(@_flash) || refresh
|
||||
|
|
|
@ -22,6 +22,16 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# cookies.delete :user_name
|
||||
#
|
||||
# Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
|
||||
#
|
||||
# cookies[:key] = {
|
||||
# :value => 'a yummy cookie',
|
||||
# :expires => 1.year.from_now,
|
||||
# :domain => 'domain.com'
|
||||
# }
|
||||
#
|
||||
# cookies.delete(:key, :domain => 'domain.com')
|
||||
#
|
||||
# The option symbols for setting cookies are:
|
||||
#
|
||||
# * <tt>:value</tt> - The cookie's value or list of values (as an array).
|
||||
|
|
|
@ -22,11 +22,12 @@ module ActionController
|
|||
end
|
||||
|
||||
if defined?(ActiveRecord)
|
||||
after_dispatch :checkin_connections
|
||||
before_dispatch { ActiveRecord::Base.verify_active_connections! }
|
||||
to_prepare(:activerecord_instantiate_observers) { ActiveRecord::Base.instantiate_observers }
|
||||
end
|
||||
|
||||
after_dispatch :flush_logger if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:flush)
|
||||
after_dispatch :flush_logger if Base.logger && Base.logger.respond_to?(:flush)
|
||||
end
|
||||
|
||||
# Backward-compatible class method takes CGI-specific args. Deprecated
|
||||
|
@ -38,7 +39,7 @@ module ActionController
|
|||
# Add a preparation callback. Preparation callbacks are run before every
|
||||
# request in development mode, and before the first request in production
|
||||
# mode.
|
||||
#
|
||||
#
|
||||
# An optional identifier may be supplied for the callback. If provided,
|
||||
# to_prepare may be called again with the same identifier to replace the
|
||||
# existing callback. Passing an identifier is a suggested practice if the
|
||||
|
@ -46,7 +47,7 @@ module ActionController
|
|||
def to_prepare(identifier = nil, &block)
|
||||
@prepare_dispatch_callbacks ||= ActiveSupport::Callbacks::CallbackChain.new
|
||||
callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier)
|
||||
@prepare_dispatch_callbacks | callback
|
||||
@prepare_dispatch_callbacks.replace_or_append!(callback)
|
||||
end
|
||||
|
||||
# If the block raises, send status code as a last-ditch response.
|
||||
|
@ -96,19 +97,27 @@ module ActionController
|
|||
include ActiveSupport::Callbacks
|
||||
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
|
||||
|
||||
def initialize(output, request = nil, response = nil)
|
||||
def initialize(output = $stdout, request = nil, response = nil)
|
||||
@output, @request, @response = output, request, response
|
||||
end
|
||||
|
||||
def dispatch_unlocked
|
||||
begin
|
||||
run_callbacks :before_dispatch
|
||||
handle_request
|
||||
rescue Exception => exception
|
||||
failsafe_rescue exception
|
||||
ensure
|
||||
run_callbacks :after_dispatch, :enumerator => :reverse_each
|
||||
end
|
||||
end
|
||||
|
||||
def dispatch
|
||||
@@guard.synchronize do
|
||||
begin
|
||||
run_callbacks :before_dispatch
|
||||
handle_request
|
||||
rescue Exception => exception
|
||||
failsafe_rescue exception
|
||||
ensure
|
||||
run_callbacks :after_dispatch, :enumerator => :reverse_each
|
||||
if ActionController::Base.allow_concurrency
|
||||
dispatch_unlocked
|
||||
else
|
||||
@@guard.synchronize do
|
||||
dispatch_unlocked
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -123,12 +132,19 @@ module ActionController
|
|||
failsafe_rescue exception
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@request = RackRequest.new(env)
|
||||
@response = RackResponse.new(@request)
|
||||
dispatch
|
||||
end
|
||||
|
||||
def reload_application
|
||||
# Run prepare callbacks before every request in development mode
|
||||
run_callbacks :prepare_dispatch
|
||||
|
||||
Routing::Routes.reload
|
||||
ActionView::TemplateFinder.reload! unless ActionView::Base.cache_template_loading
|
||||
ActionController::Base.view_paths.reload!
|
||||
ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear
|
||||
end
|
||||
|
||||
# Cleanup the application by clearing out loaded classes so they can
|
||||
|
@ -140,7 +156,22 @@ module ActionController
|
|||
end
|
||||
|
||||
def flush_logger
|
||||
RAILS_DEFAULT_LOGGER.flush
|
||||
Base.logger.flush
|
||||
end
|
||||
|
||||
def mark_as_test_request!
|
||||
@test_request = true
|
||||
self
|
||||
end
|
||||
|
||||
def test_request?
|
||||
@test_request
|
||||
end
|
||||
|
||||
def checkin_connections
|
||||
# Don't return connection (and peform implicit rollback) if this request is a part of integration test
|
||||
return if test_request?
|
||||
ActiveRecord::Base.clear_active_connections!
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
@ -94,7 +94,7 @@ module ActionController #:nodoc:
|
|||
map! do |filter|
|
||||
if filters.include?(filter)
|
||||
new_filter = filter.dup
|
||||
new_filter.options.merge!(options)
|
||||
new_filter.update_options!(options)
|
||||
new_filter
|
||||
else
|
||||
filter
|
||||
|
@ -104,16 +104,34 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
|
||||
def initialize(kind, method, options = {})
|
||||
super
|
||||
update_options! options
|
||||
end
|
||||
|
||||
# override these to return true in appropriate subclass
|
||||
def before?
|
||||
self.class == BeforeFilter
|
||||
false
|
||||
end
|
||||
|
||||
def after?
|
||||
self.class == AfterFilter
|
||||
false
|
||||
end
|
||||
|
||||
def around?
|
||||
self.class == AroundFilter
|
||||
false
|
||||
end
|
||||
|
||||
# Make sets of strings from :only/:except options
|
||||
def update_options!(other)
|
||||
if other
|
||||
convert_only_and_except_options_to_sets_of_strings(other)
|
||||
if other[:skip]
|
||||
convert_only_and_except_options_to_sets_of_strings(other[:skip])
|
||||
end
|
||||
end
|
||||
|
||||
options.update(other)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -127,9 +145,9 @@ module ActionController #:nodoc:
|
|||
|
||||
def included_in_action?(controller, options)
|
||||
if options[:only]
|
||||
Array(options[:only]).map(&:to_s).include?(controller.action_name)
|
||||
options[:only].include?(controller.action_name)
|
||||
elsif options[:except]
|
||||
!Array(options[:except]).map(&:to_s).include?(controller.action_name)
|
||||
!options[:except].include?(controller.action_name)
|
||||
else
|
||||
true
|
||||
end
|
||||
|
@ -138,6 +156,14 @@ module ActionController #:nodoc:
|
|||
def should_run_callback?(controller)
|
||||
should_not_skip?(controller) && included_in_action?(controller, options) && super
|
||||
end
|
||||
|
||||
def convert_only_and_except_options_to_sets_of_strings(opts)
|
||||
[:only, :except].each do |key|
|
||||
if values = opts[key]
|
||||
opts[key] = Array(values).map(&:to_s).to_set
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AroundFilter < Filter #:nodoc:
|
||||
|
@ -145,6 +171,10 @@ module ActionController #:nodoc:
|
|||
:around
|
||||
end
|
||||
|
||||
def around?
|
||||
true
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
if should_run_callback?(controller)
|
||||
method = filter_responds_to_before_and_after? ? around_proc : self.method
|
||||
|
@ -169,8 +199,8 @@ module ActionController #:nodoc:
|
|||
Proc.new do |controller, action|
|
||||
method.before(controller)
|
||||
|
||||
if controller.send!(:performed?)
|
||||
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
|
||||
if controller.__send__(:performed?)
|
||||
controller.__send__(:halt_filter_chain, method, :rendered_or_redirected)
|
||||
else
|
||||
begin
|
||||
action.call
|
||||
|
@ -187,10 +217,14 @@ module ActionController #:nodoc:
|
|||
:before
|
||||
end
|
||||
|
||||
def before?
|
||||
true
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
super
|
||||
if controller.send!(:performed?)
|
||||
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
|
||||
if controller.__send__(:performed?)
|
||||
controller.__send__(:halt_filter_chain, method, :rendered_or_redirected)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -199,6 +233,10 @@ module ActionController #:nodoc:
|
|||
def type
|
||||
:after
|
||||
end
|
||||
|
||||
def after?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do
|
||||
|
|
|
@ -1,31 +1,33 @@
|
|||
require 'active_support/memoizable'
|
||||
|
||||
module ActionController
|
||||
module Http
|
||||
class Headers < ::Hash
|
||||
|
||||
def initialize(constructor = {})
|
||||
if constructor.is_a?(Hash)
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
def initialize(*args)
|
||||
if args.size == 1 && args[0].is_a?(Hash)
|
||||
super()
|
||||
update(constructor)
|
||||
update(args[0])
|
||||
else
|
||||
super(constructor)
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def [](header_name)
|
||||
if include?(header_name)
|
||||
super
|
||||
super
|
||||
else
|
||||
super(normalize_header(header_name))
|
||||
super(env_name(header_name))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
private
|
||||
# Takes an HTTP header name and returns it in the
|
||||
# format
|
||||
def normalize_header(header_name)
|
||||
# Converts a HTTP header name to an environment variable name.
|
||||
def env_name(header_name)
|
||||
"HTTP_#{header_name.upcase.gsub(/-/, '_')}"
|
||||
end
|
||||
memoize :env_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -204,8 +204,8 @@ module ActionController #:nodoc:
|
|||
|
||||
begin
|
||||
child.master_helper_module = Module.new
|
||||
child.master_helper_module.send! :include, master_helper_module
|
||||
child.send! :default_helper_module!
|
||||
child.master_helper_module.__send__ :include, master_helper_module
|
||||
child.__send__ :default_helper_module!
|
||||
rescue MissingSourceFile => e
|
||||
raise unless e.is_missing?("helpers/#{child.controller_path}_helper")
|
||||
end
|
||||
|
|
|
@ -117,7 +117,7 @@ module ActionController
|
|||
|
||||
def authentication_request(controller, realm)
|
||||
controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
|
||||
controller.send! :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
|
||||
controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
require 'stringio'
|
||||
require 'uri'
|
||||
|
||||
require 'active_support/test_case'
|
||||
require 'action_controller/dispatcher'
|
||||
require 'action_controller/test_process'
|
||||
|
||||
require 'stringio'
|
||||
require 'uri'
|
||||
|
||||
module ActionController
|
||||
module Integration #:nodoc:
|
||||
# An integration Session instance represents a set of requests and responses
|
||||
|
@ -100,7 +101,7 @@ module ActionController
|
|||
@https = flag
|
||||
end
|
||||
|
||||
# Return +true+ if the session is mimicing a secure HTTPS request.
|
||||
# Return +true+ if the session is mimicking a secure HTTPS request.
|
||||
#
|
||||
# if session.https?
|
||||
# ...
|
||||
|
@ -164,11 +165,19 @@ module ActionController
|
|||
status/100 == 3
|
||||
end
|
||||
|
||||
# Performs a GET request with the given parameters. The parameters may
|
||||
# be +nil+, a Hash, or a string that is appropriately encoded
|
||||
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
|
||||
# The headers should be a hash. The keys will automatically be upcased, with the
|
||||
# prefix 'HTTP_' added if needed.
|
||||
# Performs a GET request with the given parameters.
|
||||
#
|
||||
# - +path+: The URI (as a String) on which you want to perform a GET request.
|
||||
# - +parameters+: The HTTP parameters that you want to pass. This may be +nil+,
|
||||
# a Hash, or a String that is appropriately encoded
|
||||
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
|
||||
# - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
|
||||
# automatically be upcased, with the prefix 'HTTP_' added if needed.
|
||||
#
|
||||
# This method returns an AbstractResponse object, which one can use to inspect
|
||||
# the details of the response. Furthermore, if this method was called from an
|
||||
# ActionController::IntegrationTest object, then that object's <tt>@response</tt>
|
||||
# instance variable will point to the same response object.
|
||||
#
|
||||
# You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
|
||||
# +put+, +delete+, and +head+.
|
||||
|
@ -219,21 +228,6 @@ module ActionController
|
|||
end
|
||||
|
||||
private
|
||||
class StubCGI < CGI #:nodoc:
|
||||
attr_accessor :stdinput, :stdoutput, :env_table
|
||||
|
||||
def initialize(env, stdinput = nil)
|
||||
self.env_table = env
|
||||
self.stdoutput = StringIO.new
|
||||
|
||||
super
|
||||
|
||||
stdinput.set_encoding(Encoding::BINARY) if stdinput.respond_to?(:set_encoding)
|
||||
stdinput.force_encoding(Encoding::BINARY) if stdinput.respond_to?(:force_encoding)
|
||||
@stdinput = stdinput.is_a?(IO) ? stdinput : StringIO.new(stdinput || '')
|
||||
end
|
||||
end
|
||||
|
||||
# Tailors the session based on the given URI, setting the HTTPS value
|
||||
# and the hostname.
|
||||
def interpret_uri(path)
|
||||
|
@ -281,9 +275,8 @@ module ActionController
|
|||
|
||||
ActionController::Base.clear_last_instantiation!
|
||||
|
||||
cgi = StubCGI.new(env, data)
|
||||
ActionController::Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
|
||||
@result = cgi.stdoutput.string
|
||||
env['rack.input'] = data.is_a?(IO) ? data : StringIO.new(data || '')
|
||||
@status, @headers, result_body = ActionController::Dispatcher.new.mark_as_test_request!.call(env)
|
||||
@request_count += 1
|
||||
|
||||
@controller = ActionController::Base.last_instantiation
|
||||
|
@ -297,7 +290,29 @@ module ActionController
|
|||
|
||||
@html_document = nil
|
||||
|
||||
parse_result
|
||||
# Inject status back in for backwords compatibility with CGI
|
||||
@headers['Status'] = @status
|
||||
|
||||
@status, @status_message = @status.split(/ /)
|
||||
@status = @status.to_i
|
||||
|
||||
cgi_headers = Hash.new { |h,k| h[k] = [] }
|
||||
@headers.each do |key, value|
|
||||
cgi_headers[key.downcase] << value
|
||||
end
|
||||
cgi_headers['set-cookie'] = cgi_headers['set-cookie'].first
|
||||
@headers = cgi_headers
|
||||
|
||||
@response.headers['cookie'] ||= []
|
||||
(@headers['set-cookie'] || []).each do |cookie|
|
||||
name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
|
||||
@cookies[name] = value
|
||||
|
||||
# Fake CGI cookie header
|
||||
# DEPRECATE: Use response.headers["Set-Cookie"] instead
|
||||
@response.headers['cookie'] << CGI::Cookie::new("name" => name, "value" => value)
|
||||
end
|
||||
|
||||
return status
|
||||
rescue MultiPartNeededException
|
||||
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
|
||||
|
@ -305,26 +320,6 @@ module ActionController
|
|||
return status
|
||||
end
|
||||
|
||||
# Parses the result of the response and extracts the various values,
|
||||
# like cookies, status, headers, etc.
|
||||
def parse_result
|
||||
response_headers, result_body = @result.split(/\r\n\r\n/, 2)
|
||||
|
||||
@headers = Hash.new { |h,k| h[k] = [] }
|
||||
response_headers.to_s.each_line do |line|
|
||||
key, value = line.strip.split(/:\s*/, 2)
|
||||
@headers[key.downcase] << value
|
||||
end
|
||||
|
||||
(@headers['set-cookie'] || [] ).each do |string|
|
||||
name, value = string.match(/^([^=]*)=([^;]*);/)[1,2]
|
||||
@cookies[name] = value
|
||||
end
|
||||
|
||||
@status, @status_message = @headers["status"].first.to_s.split(/ /)
|
||||
@status = @status.to_i
|
||||
end
|
||||
|
||||
# Encode the cookies hash in a format suitable for passing to a
|
||||
# request.
|
||||
def encode_cookies
|
||||
|
@ -335,13 +330,15 @@ module ActionController
|
|||
|
||||
# Get a temporary URL writer object
|
||||
def generic_url_rewriter
|
||||
cgi = StubCGI.new('REQUEST_METHOD' => "GET",
|
||||
'QUERY_STRING' => "",
|
||||
"REQUEST_URI" => "/",
|
||||
"HTTP_HOST" => host,
|
||||
"SERVER_PORT" => https? ? "443" : "80",
|
||||
"HTTPS" => https? ? "on" : "off")
|
||||
ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {})
|
||||
env = {
|
||||
'REQUEST_METHOD' => "GET",
|
||||
'QUERY_STRING' => "",
|
||||
"REQUEST_URI" => "/",
|
||||
"HTTP_HOST" => host,
|
||||
"SERVER_PORT" => https? ? "443" : "80",
|
||||
"HTTPS" => https? ? "on" : "off"
|
||||
}
|
||||
ActionController::UrlRewriter.new(ActionController::RackRequest.new(env), {})
|
||||
end
|
||||
|
||||
def name_with_prefix(prefix, name)
|
||||
|
@ -442,12 +439,12 @@ EOF
|
|||
end
|
||||
|
||||
%w(get post put head delete cookies assigns
|
||||
xml_http_request get_via_redirect post_via_redirect).each do |method|
|
||||
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
|
||||
define_method(method) do |*args|
|
||||
reset! unless @integration_session
|
||||
# reset the html_document variable, but only for new get/post calls
|
||||
@html_document = nil unless %w(cookies assigns).include?(method)
|
||||
returning @integration_session.send!(method, *args) do
|
||||
returning @integration_session.__send__(method, *args) do
|
||||
copy_session_variables!
|
||||
end
|
||||
end
|
||||
|
@ -472,12 +469,12 @@ EOF
|
|||
self.class.fixture_table_names.each do |table_name|
|
||||
name = table_name.tr(".", "_")
|
||||
next unless respond_to?(name)
|
||||
extras.send!(:define_method, name) { |*args| delegate.send(name, *args) }
|
||||
extras.__send__(:define_method, name) { |*args| delegate.send(name, *args) }
|
||||
end
|
||||
end
|
||||
|
||||
# delegate add_assertion to the test case
|
||||
extras.send!(:define_method, :add_assertion) { test_result.add_assertion }
|
||||
extras.__send__(:define_method, :add_assertion) { test_result.add_assertion }
|
||||
session.extend(extras)
|
||||
session.delegate = self
|
||||
session.test_result = @_result
|
||||
|
@ -491,14 +488,14 @@ EOF
|
|||
def copy_session_variables! #:nodoc:
|
||||
return unless @integration_session
|
||||
%w(controller response request).each do |var|
|
||||
instance_variable_set("@#{var}", @integration_session.send!(var))
|
||||
instance_variable_set("@#{var}", @integration_session.__send__(var))
|
||||
end
|
||||
end
|
||||
|
||||
# Delegate unhandled messages to the current session instance.
|
||||
def method_missing(sym, *args, &block)
|
||||
reset! unless @integration_session
|
||||
returning @integration_session.send!(sym, *args, &block) do
|
||||
returning @integration_session.__send__(sym, *args, &block) do
|
||||
copy_session_variables!
|
||||
end
|
||||
end
|
||||
|
@ -580,7 +577,7 @@ EOF
|
|||
# end
|
||||
# end
|
||||
# end
|
||||
class IntegrationTest < Test::Unit::TestCase
|
||||
class IntegrationTest < ActiveSupport::TestCase
|
||||
include Integration::Runner
|
||||
|
||||
# Work around a bug in test/unit caused by the default test being named
|
||||
|
|
|
@ -3,11 +3,6 @@ module ActionController #:nodoc:
|
|||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
# NOTE: Can't use alias_method_chain here because +render_without_layout+ is already
|
||||
# defined as a publicly exposed method
|
||||
alias_method :render_with_no_layout, :render
|
||||
alias_method :render, :render_with_a_layout
|
||||
|
||||
class << self
|
||||
alias_method_chain :inherited, :layout
|
||||
end
|
||||
|
@ -169,17 +164,17 @@ module ActionController #:nodoc:
|
|||
# performance and have access to them as any normal template would.
|
||||
def layout(template_name, conditions = {}, auto = false)
|
||||
add_layout_conditions(conditions)
|
||||
write_inheritable_attribute "layout", template_name
|
||||
write_inheritable_attribute "auto_layout", auto
|
||||
write_inheritable_attribute(:layout, template_name)
|
||||
write_inheritable_attribute(:auto_layout, auto)
|
||||
end
|
||||
|
||||
def layout_conditions #:nodoc:
|
||||
@layout_conditions ||= read_inheritable_attribute("layout_conditions")
|
||||
@layout_conditions ||= read_inheritable_attribute(:layout_conditions)
|
||||
end
|
||||
|
||||
def default_layout(format) #:nodoc:
|
||||
layout = read_inheritable_attribute("layout")
|
||||
return layout unless read_inheritable_attribute("auto_layout")
|
||||
layout = read_inheritable_attribute(:layout)
|
||||
return layout unless read_inheritable_attribute(:auto_layout)
|
||||
@default_layout ||= {}
|
||||
@default_layout[format] ||= default_layout_with_format(format, layout)
|
||||
@default_layout[format]
|
||||
|
@ -199,7 +194,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def add_layout_conditions(conditions)
|
||||
write_inheritable_hash "layout_conditions", normalize_conditions(conditions)
|
||||
write_inheritable_hash(:layout_conditions, normalize_conditions(conditions))
|
||||
end
|
||||
|
||||
def normalize_conditions(conditions)
|
||||
|
@ -221,10 +216,10 @@ module ActionController #:nodoc:
|
|||
# object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
|
||||
# weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
|
||||
def active_layout(passed_layout = nil)
|
||||
layout = passed_layout || self.class.default_layout(response.template.template_format)
|
||||
layout = passed_layout || self.class.default_layout(default_template_format)
|
||||
active_layout = case layout
|
||||
when String then layout
|
||||
when Symbol then send!(layout)
|
||||
when Symbol then __send__(layout)
|
||||
when Proc then layout.call(self)
|
||||
end
|
||||
|
||||
|
@ -240,51 +235,24 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def render_with_a_layout(options = nil, extra_options = {}, &block) #:nodoc:
|
||||
template_with_options = options.is_a?(Hash)
|
||||
|
||||
if (layout = pick_layout(template_with_options, options)) && apply_layout?(template_with_options, options)
|
||||
options = options.merge :layout => false if template_with_options
|
||||
logger.info("Rendering template within #{layout}") if logger
|
||||
|
||||
content_for_layout = render_with_no_layout(options, extra_options, &block)
|
||||
erase_render_results
|
||||
add_variables_to_assigns
|
||||
@template.instance_variable_set("@content_for_layout", content_for_layout)
|
||||
response.layout = layout
|
||||
status = template_with_options ? options[:status] : nil
|
||||
render_for_text(@template.render_file(layout, true), status)
|
||||
else
|
||||
render_with_no_layout(options, extra_options, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def apply_layout?(template_with_options, options)
|
||||
return false if options == :update
|
||||
template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout?
|
||||
end
|
||||
|
||||
def candidate_for_layout?(options)
|
||||
(options.has_key?(:layout) && options[:layout] != false) ||
|
||||
options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? &&
|
||||
!template_exempt_from_layout?(options[:template] || default_template_name(options[:action]))
|
||||
options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing, :update).compact.empty? &&
|
||||
!@template.__send__(:_exempt_from_layout?, options[:template] || default_template_name(options[:action]))
|
||||
end
|
||||
|
||||
def pick_layout(template_with_options, options)
|
||||
if template_with_options
|
||||
case layout = options[:layout]
|
||||
when FalseClass
|
||||
nil
|
||||
when NilClass, TrueClass
|
||||
active_layout if action_has_layout?
|
||||
else
|
||||
active_layout(layout)
|
||||
def pick_layout(options)
|
||||
if options.has_key?(:layout)
|
||||
case layout = options.delete(:layout)
|
||||
when FalseClass
|
||||
nil
|
||||
when NilClass, TrueClass
|
||||
active_layout if action_has_layout? && !@template.__send__(:_exempt_from_layout?, default_template_name)
|
||||
else
|
||||
active_layout(layout)
|
||||
end
|
||||
else
|
||||
active_layout if action_has_layout?
|
||||
active_layout if action_has_layout? && candidate_for_layout?(options)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -304,7 +272,13 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def layout_directory?(layout_name)
|
||||
@template.finder.find_template_extension_from_handler(File.join('layouts', layout_name))
|
||||
@template.__send__(:_pick_template, "#{File.join('layouts', layout_name)}.#{@template.template_format}") ? true : false
|
||||
rescue ActionView::MissingTemplate
|
||||
false
|
||||
end
|
||||
|
||||
def default_template_format
|
||||
response.template.template_format
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -114,7 +114,11 @@ module ActionController #:nodoc:
|
|||
@request = controller.request
|
||||
@response = controller.response
|
||||
|
||||
@mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
|
||||
if ActionController::Base.use_accept_header
|
||||
@mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
|
||||
else
|
||||
@mime_type_priority = [@request.format]
|
||||
end
|
||||
|
||||
@order = []
|
||||
@responses = {}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require 'set'
|
||||
|
||||
module Mime
|
||||
SET = []
|
||||
EXTENSION_LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
|
||||
|
@ -72,57 +74,61 @@ module Mime
|
|||
end
|
||||
|
||||
def parse(accept_header)
|
||||
# keep track of creation order to keep the subsequent sort stable
|
||||
list = []
|
||||
accept_header.split(/,/).each_with_index do |header, index|
|
||||
params, q = header.split(/;\s*q=/)
|
||||
if params
|
||||
params.strip!
|
||||
list << AcceptItem.new(index, params, q) unless params.empty?
|
||||
end
|
||||
end
|
||||
list.sort!
|
||||
|
||||
# Take care of the broken text/xml entry by renaming or deleting it
|
||||
text_xml = list.index("text/xml")
|
||||
app_xml = list.index(Mime::XML.to_s)
|
||||
|
||||
if text_xml && app_xml
|
||||
# set the q value to the max of the two
|
||||
list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
|
||||
|
||||
# make sure app_xml is ahead of text_xml in the list
|
||||
if app_xml > text_xml
|
||||
list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
|
||||
app_xml, text_xml = text_xml, app_xml
|
||||
end
|
||||
|
||||
# delete text_xml from the list
|
||||
list.delete_at(text_xml)
|
||||
|
||||
elsif text_xml
|
||||
list[text_xml].name = Mime::XML.to_s
|
||||
end
|
||||
|
||||
# Look for more specific XML-based types and sort them ahead of app/xml
|
||||
|
||||
if app_xml
|
||||
idx = app_xml
|
||||
app_xml_type = list[app_xml]
|
||||
|
||||
while(idx < list.length)
|
||||
type = list[idx]
|
||||
break if type.q < app_xml_type.q
|
||||
if type.name =~ /\+xml$/
|
||||
list[app_xml], list[idx] = list[idx], list[app_xml]
|
||||
app_xml = idx
|
||||
if accept_header !~ /,/
|
||||
[Mime::Type.lookup(accept_header)]
|
||||
else
|
||||
# keep track of creation order to keep the subsequent sort stable
|
||||
list = []
|
||||
accept_header.split(/,/).each_with_index do |header, index|
|
||||
params, q = header.split(/;\s*q=/)
|
||||
if params
|
||||
params.strip!
|
||||
list << AcceptItem.new(index, params, q) unless params.empty?
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
list.sort!
|
||||
|
||||
list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
|
||||
list
|
||||
# Take care of the broken text/xml entry by renaming or deleting it
|
||||
text_xml = list.index("text/xml")
|
||||
app_xml = list.index(Mime::XML.to_s)
|
||||
|
||||
if text_xml && app_xml
|
||||
# set the q value to the max of the two
|
||||
list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
|
||||
|
||||
# make sure app_xml is ahead of text_xml in the list
|
||||
if app_xml > text_xml
|
||||
list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
|
||||
app_xml, text_xml = text_xml, app_xml
|
||||
end
|
||||
|
||||
# delete text_xml from the list
|
||||
list.delete_at(text_xml)
|
||||
|
||||
elsif text_xml
|
||||
list[text_xml].name = Mime::XML.to_s
|
||||
end
|
||||
|
||||
# Look for more specific XML-based types and sort them ahead of app/xml
|
||||
|
||||
if app_xml
|
||||
idx = app_xml
|
||||
app_xml_type = list[app_xml]
|
||||
|
||||
while(idx < list.length)
|
||||
type = list[idx]
|
||||
break if type.q < app_xml_type.q
|
||||
if type.name =~ /\+xml$/
|
||||
list[app_xml], list[idx] = list[idx], list[app_xml]
|
||||
app_xml = idx
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
|
||||
list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
|
||||
list
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -17,4 +17,5 @@ Mime::Type.register "multipart/form-data", :multipart_form
|
|||
Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
|
||||
|
||||
# http://www.ietf.org/rfc/rfc4627.txt
|
||||
Mime::Type.register "application/json", :json, %w( text/x-json )
|
||||
# http://www.json.org/JSONRequest.html
|
||||
Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
|
16
vendor/rails/actionpack/lib/action_controller/performance_test.rb
vendored
Normal file
16
vendor/rails/actionpack/lib/action_controller/performance_test.rb
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
require 'action_controller/integration'
|
||||
require 'active_support/testing/performance'
|
||||
require 'active_support/testing/default'
|
||||
|
||||
module ActionController
|
||||
# An integration test that runs a code profiler on your test methods.
|
||||
# Profiling output for combinations of each test method, measurement, and
|
||||
# output format are written to your tmp/performance directory.
|
||||
#
|
||||
# By default, process_time is measured and both flat and graph_html output
|
||||
# formats are written, so you'll have two output files per test method.
|
||||
class PerformanceTest < ActionController::IntegrationTest
|
||||
include ActiveSupport::Testing::Performance
|
||||
include ActiveSupport::Testing::Default
|
||||
end
|
||||
end
|
|
@ -102,7 +102,13 @@ module ActionController
|
|||
args << format if format
|
||||
|
||||
named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
|
||||
send!(named_route, *args)
|
||||
|
||||
url_options = options.except(:action, :routing_type, :format)
|
||||
unless url_options.empty?
|
||||
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
|
||||
end
|
||||
|
||||
__send__(named_route, *args)
|
||||
end
|
||||
|
||||
# Returns the path component of a URL for the given record. It uses
|
||||
|
@ -114,19 +120,19 @@ module ActionController
|
|||
|
||||
%w(edit new formatted).each do |action|
|
||||
module_eval <<-EOT, __FILE__, __LINE__
|
||||
def #{action}_polymorphic_url(record_or_hash)
|
||||
polymorphic_url(record_or_hash, :action => "#{action}")
|
||||
def #{action}_polymorphic_url(record_or_hash, options = {})
|
||||
polymorphic_url(record_or_hash, options.merge(:action => "#{action}"))
|
||||
end
|
||||
|
||||
def #{action}_polymorphic_path(record_or_hash)
|
||||
polymorphic_url(record_or_hash, :action => "#{action}", :routing_type => :path)
|
||||
def #{action}_polymorphic_path(record_or_hash, options = {})
|
||||
polymorphic_url(record_or_hash, options.merge(:action => "#{action}", :routing_type => :path))
|
||||
end
|
||||
EOT
|
||||
end
|
||||
|
||||
private
|
||||
def action_prefix(options)
|
||||
options[:action] ? "#{options[:action]}_" : ""
|
||||
options[:action] ? "#{options[:action]}_" : options[:format] ? "formatted_" : ""
|
||||
end
|
||||
|
||||
def routing_type(options)
|
||||
|
@ -143,7 +149,7 @@ module ActionController
|
|||
if parent.is_a?(Symbol) || parent.is_a?(String)
|
||||
string << "#{parent}_"
|
||||
else
|
||||
string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
|
||||
string << "#{RecordIdentifier.__send__("singular_class_name", parent)}_"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -151,7 +157,7 @@ module ActionController
|
|||
if record.is_a?(Symbol) || record.is_a?(String)
|
||||
route << "#{record}_"
|
||||
else
|
||||
route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
|
||||
route << "#{RecordIdentifier.__send__("#{inflection}_class_name", record)}_"
|
||||
end
|
||||
|
||||
action_prefix(options) + namespace + route + routing_type(options).to_s
|
||||
|
|
303
vendor/rails/actionpack/lib/action_controller/rack_process.rb
vendored
Normal file
303
vendor/rails/actionpack/lib/action_controller/rack_process.rb
vendored
Normal file
|
@ -0,0 +1,303 @@
|
|||
require 'action_controller/cgi_ext'
|
||||
require 'action_controller/session/cookie_store'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class RackRequest < AbstractRequest #:nodoc:
|
||||
attr_accessor :session_options
|
||||
attr_reader :cgi
|
||||
|
||||
class SessionFixationAttempt < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
DEFAULT_SESSION_OPTIONS = {
|
||||
:database_manager => CGI::Session::CookieStore, # store data in cookie
|
||||
:prefix => "ruby_sess.", # prefix session file names
|
||||
:session_path => "/", # available to all paths in app
|
||||
:session_key => "_session_id",
|
||||
:cookie_only => true,
|
||||
:session_http_only=> true
|
||||
}
|
||||
|
||||
def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
|
||||
@session_options = session_options
|
||||
@env = env
|
||||
@cgi = CGIWrapper.new(self)
|
||||
super()
|
||||
end
|
||||
|
||||
%w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO
|
||||
PATH_TRANSLATED REMOTE_HOST
|
||||
REMOTE_IDENT REMOTE_USER SCRIPT_NAME
|
||||
SERVER_NAME SERVER_PROTOCOL
|
||||
|
||||
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
|
||||
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
|
||||
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
|
||||
define_method(env.sub(/^HTTP_/n, '').downcase) do
|
||||
@env[env]
|
||||
end
|
||||
end
|
||||
|
||||
def query_string
|
||||
qs = super
|
||||
if !qs.blank?
|
||||
qs
|
||||
else
|
||||
@env['QUERY_STRING']
|
||||
end
|
||||
end
|
||||
|
||||
def body_stream #:nodoc:
|
||||
@env['rack.input']
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
@env.key?(key)
|
||||
end
|
||||
|
||||
def cookies
|
||||
return {} unless @env["HTTP_COOKIE"]
|
||||
|
||||
unless @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
|
||||
@env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
|
||||
@env["rack.request.cookie_hash"] = CGI::Cookie::parse(@env["rack.request.cookie_string"])
|
||||
end
|
||||
|
||||
@env["rack.request.cookie_hash"]
|
||||
end
|
||||
|
||||
def server_port
|
||||
@env['SERVER_PORT'].to_i
|
||||
end
|
||||
|
||||
def server_software
|
||||
@env['SERVER_SOFTWARE'].split("/").first
|
||||
end
|
||||
|
||||
def session
|
||||
unless defined?(@session)
|
||||
if @session_options == false
|
||||
@session = Hash.new
|
||||
else
|
||||
stale_session_check! do
|
||||
if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
|
||||
raise SessionFixationAttempt
|
||||
end
|
||||
case value = session_options_with_string_keys['new_session']
|
||||
when true
|
||||
@session = new_session
|
||||
when false
|
||||
begin
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
# CGI::Session raises ArgumentError if 'new_session' == false
|
||||
# and no session cookie or query param is present.
|
||||
rescue ArgumentError
|
||||
@session = Hash.new
|
||||
end
|
||||
when nil
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
else
|
||||
raise ArgumentError, "Invalid new_session option: #{value}"
|
||||
end
|
||||
@session['__valid_session']
|
||||
end
|
||||
end
|
||||
end
|
||||
@session
|
||||
end
|
||||
|
||||
def reset_session
|
||||
@session.delete if defined?(@session) && @session.is_a?(CGI::Session)
|
||||
@session = new_session
|
||||
end
|
||||
|
||||
private
|
||||
# Delete an old session if it exists then create a new one.
|
||||
def new_session
|
||||
if @session_options == false
|
||||
Hash.new
|
||||
else
|
||||
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
|
||||
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
|
||||
end
|
||||
end
|
||||
|
||||
def cookie_only?
|
||||
session_options_with_string_keys['cookie_only']
|
||||
end
|
||||
|
||||
def stale_session_check!
|
||||
yield
|
||||
rescue ArgumentError => argument_error
|
||||
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
||||
begin
|
||||
# Note that the regexp does not allow $1 to end with a ':'
|
||||
$1.constantize
|
||||
rescue LoadError, NameError => const_error
|
||||
raise ActionController::SessionRestoreError, <<-end_msg
|
||||
Session contains objects whose class definition isn\'t available.
|
||||
Remember to require the classes for all objects kept in the session.
|
||||
(Original exception: #{const_error.message} [#{const_error.class}])
|
||||
end_msg
|
||||
end
|
||||
|
||||
retry
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def session_options_with_string_keys
|
||||
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
|
||||
end
|
||||
end
|
||||
|
||||
class RackResponse < AbstractResponse #:nodoc:
|
||||
def initialize(request)
|
||||
@cgi = request.cgi
|
||||
@writer = lambda { |x| @body << x }
|
||||
@block = nil
|
||||
super()
|
||||
end
|
||||
|
||||
# Retrieve status from instance variable if has already been delete
|
||||
def status
|
||||
@status || super
|
||||
end
|
||||
|
||||
def out(output = $stdout, &block)
|
||||
# Nasty hack because CGI sessions are closed after the normal
|
||||
# prepare! statement
|
||||
set_cookies!
|
||||
|
||||
@block = block
|
||||
@status = headers.delete("Status")
|
||||
if [204, 304].include?(status.to_i)
|
||||
headers.delete("Content-Type")
|
||||
[status, headers.to_hash, []]
|
||||
else
|
||||
[status, headers.to_hash, self]
|
||||
end
|
||||
end
|
||||
alias to_a out
|
||||
|
||||
def each(&callback)
|
||||
if @body.respond_to?(:call)
|
||||
@writer = lambda { |x| callback.call(x) }
|
||||
@body.call(self, self)
|
||||
elsif @body.is_a?(String)
|
||||
@body.each_line(&callback)
|
||||
else
|
||||
@body.each(&callback)
|
||||
end
|
||||
|
||||
@writer = callback
|
||||
@block.call(self) if @block
|
||||
end
|
||||
|
||||
def write(str)
|
||||
@writer.call str.to_s
|
||||
str
|
||||
end
|
||||
|
||||
def close
|
||||
@body.close if @body.respond_to?(:close)
|
||||
end
|
||||
|
||||
def empty?
|
||||
@block == nil && @body.empty?
|
||||
end
|
||||
|
||||
def prepare!
|
||||
super
|
||||
|
||||
convert_language!
|
||||
convert_expires!
|
||||
set_status!
|
||||
# set_cookies!
|
||||
end
|
||||
|
||||
private
|
||||
def convert_language!
|
||||
headers["Content-Language"] = headers.delete("language") if headers["language"]
|
||||
end
|
||||
|
||||
def convert_expires!
|
||||
headers["Expires"] = headers.delete("") if headers["expires"]
|
||||
end
|
||||
|
||||
def convert_content_type!
|
||||
super
|
||||
headers['Content-Type'] = headers.delete('type') || "text/html"
|
||||
headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset']
|
||||
end
|
||||
|
||||
def set_content_length!
|
||||
super
|
||||
headers["Content-Length"] = headers["Content-Length"].to_s if headers["Content-Length"]
|
||||
end
|
||||
|
||||
def set_status!
|
||||
self.status ||= "200 OK"
|
||||
end
|
||||
|
||||
def set_cookies!
|
||||
# Convert 'cookie' header to 'Set-Cookie' headers.
|
||||
# Because Set-Cookie header can appear more the once in the response body,
|
||||
# we store it in a line break separated string that will be translated to
|
||||
# multiple Set-Cookie header by the handler.
|
||||
if cookie = headers.delete('cookie')
|
||||
cookies = []
|
||||
|
||||
case cookie
|
||||
when Array then cookie.each { |c| cookies << c.to_s }
|
||||
when Hash then cookie.each { |_, c| cookies << c.to_s }
|
||||
else cookies << cookie.to_s
|
||||
end
|
||||
|
||||
@cgi.output_cookies.each { |c| cookies << c.to_s } if @cgi.output_cookies
|
||||
|
||||
headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class CGIWrapper < ::CGI
|
||||
attr_reader :output_cookies
|
||||
|
||||
def initialize(request, *args)
|
||||
@request = request
|
||||
@args = *args
|
||||
@input = request.body
|
||||
|
||||
super *args
|
||||
end
|
||||
|
||||
def params
|
||||
@params ||= @request.params
|
||||
end
|
||||
|
||||
def cookies
|
||||
@request.cookies
|
||||
end
|
||||
|
||||
def query_string
|
||||
@request.query_string
|
||||
end
|
||||
|
||||
# Used to wrap the normal args variable used inside CGI.
|
||||
def args
|
||||
@args
|
||||
end
|
||||
|
||||
# Used to wrap the normal env_table variable used inside CGI.
|
||||
def env_table
|
||||
@request.env
|
||||
end
|
||||
|
||||
# Used to wrap the normal stdinput variable used inside CGI.
|
||||
def stdinput
|
||||
@input
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,33 +2,37 @@ require 'tempfile'
|
|||
require 'stringio'
|
||||
require 'strscan'
|
||||
|
||||
module ActionController
|
||||
# HTTP methods which are accepted by default.
|
||||
ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options ))
|
||||
require 'active_support/memoizable'
|
||||
|
||||
module ActionController
|
||||
# CgiRequest and TestRequest provide concrete implementations.
|
||||
class AbstractRequest
|
||||
cattr_accessor :relative_url_root
|
||||
remove_method :relative_url_root
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
def self.relative_url_root=(*args)
|
||||
ActiveSupport::Deprecation.warn(
|
||||
"ActionController::AbstractRequest.relative_url_root= has been renamed." +
|
||||
"You can now set it with config.action_controller.relative_url_root=", caller)
|
||||
end
|
||||
|
||||
HTTP_METHODS = %w(get head put post delete options)
|
||||
HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
|
||||
|
||||
# The hash of environment variables for this request,
|
||||
# such as { 'RAILS_ENV' => 'production' }.
|
||||
attr_reader :env
|
||||
|
||||
# The true HTTP request method as a lowercase symbol, such as <tt>:get</tt>.
|
||||
# The true HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
|
||||
# UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
|
||||
def request_method
|
||||
@request_method ||= begin
|
||||
method = ((@env['REQUEST_METHOD'] == 'POST' && !parameters[:_method].blank?) ? parameters[:_method].to_s : @env['REQUEST_METHOD']).downcase
|
||||
if ACCEPTED_HTTP_METHODS.include?(method)
|
||||
method.to_sym
|
||||
else
|
||||
raise UnknownHttpMethod, "#{method}, accepted HTTP methods are #{ACCEPTED_HTTP_METHODS.to_a.to_sentence}"
|
||||
end
|
||||
end
|
||||
end
|
||||
method = @env['REQUEST_METHOD']
|
||||
method = parameters[:_method] if method == 'POST' && !parameters[:_method].blank?
|
||||
|
||||
# The HTTP request method as a lowercase symbol, such as <tt>:get</tt>.
|
||||
HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
|
||||
end
|
||||
memoize :request_method
|
||||
|
||||
# The HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
|
||||
# Note, HEAD is returned as <tt>:get</tt> since the two are functionally
|
||||
# equivalent from the application's perspective.
|
||||
def method
|
||||
|
@ -55,57 +59,107 @@ module ActionController
|
|||
request_method == :delete
|
||||
end
|
||||
|
||||
# Is this a HEAD request? <tt>request.method</tt> sees HEAD as <tt>:get</tt>,
|
||||
# so check the HTTP method directly.
|
||||
# Is this a HEAD request? Since <tt>request.method</tt> sees HEAD as <tt>:get</tt>,
|
||||
# this \method checks the actual HTTP \method directly.
|
||||
def head?
|
||||
request_method == :head
|
||||
end
|
||||
|
||||
# Provides acccess to the request's HTTP headers, for example:
|
||||
# request.headers["Content-Type"] # => "text/plain"
|
||||
# Provides access to the request's HTTP headers, for example:
|
||||
#
|
||||
# request.headers["Content-Type"] # => "text/plain"
|
||||
def headers
|
||||
@headers ||= ActionController::Http::Headers.new(@env)
|
||||
ActionController::Http::Headers.new(@env)
|
||||
end
|
||||
memoize :headers
|
||||
|
||||
# Returns the content length of the request as an integer.
|
||||
def content_length
|
||||
@content_length ||= env['CONTENT_LENGTH'].to_i
|
||||
@env['CONTENT_LENGTH'].to_i
|
||||
end
|
||||
memoize :content_length
|
||||
|
||||
# The MIME type of the HTTP request, such as Mime::XML.
|
||||
#
|
||||
# For backward compatibility, the post format is extracted from the
|
||||
# For backward compatibility, the post \format is extracted from the
|
||||
# X-Post-Data-Format HTTP header if present.
|
||||
def content_type
|
||||
@content_type ||= Mime::Type.lookup(content_type_without_parameters)
|
||||
Mime::Type.lookup(content_type_without_parameters)
|
||||
end
|
||||
memoize :content_type
|
||||
|
||||
# Returns the accepted MIME type for the request
|
||||
# Returns the accepted MIME type for the request.
|
||||
def accepts
|
||||
@accepts ||=
|
||||
if @env['HTTP_ACCEPT'].to_s.strip.empty?
|
||||
[ content_type, Mime::ALL ].compact # make sure content_type being nil is not included
|
||||
else
|
||||
Mime::Type.parse(@env['HTTP_ACCEPT'])
|
||||
end
|
||||
header = @env['HTTP_ACCEPT'].to_s.strip
|
||||
|
||||
if header.empty?
|
||||
[content_type, Mime::ALL].compact
|
||||
else
|
||||
Mime::Type.parse(header)
|
||||
end
|
||||
end
|
||||
memoize :accepts
|
||||
|
||||
def if_modified_since
|
||||
if since = env['HTTP_IF_MODIFIED_SINCE']
|
||||
Time.rfc2822(since) rescue nil
|
||||
end
|
||||
end
|
||||
memoize :if_modified_since
|
||||
|
||||
def if_none_match
|
||||
env['HTTP_IF_NONE_MATCH']
|
||||
end
|
||||
|
||||
# Returns the Mime type for the format used in the request. If there is no format available, the first of the
|
||||
# accept types will be used. Examples:
|
||||
def not_modified?(modified_at)
|
||||
if_modified_since && modified_at && if_modified_since >= modified_at
|
||||
end
|
||||
|
||||
def etag_matches?(etag)
|
||||
if_none_match && if_none_match == etag
|
||||
end
|
||||
|
||||
# Check response freshness (Last-Modified and ETag) against request
|
||||
# If-Modified-Since and If-None-Match conditions. If both headers are
|
||||
# supplied, both must match, or the request is not considered fresh.
|
||||
def fresh?(response)
|
||||
case
|
||||
when if_modified_since && if_none_match
|
||||
not_modified?(response.last_modified) && etag_matches?(response.etag)
|
||||
when if_modified_since
|
||||
not_modified?(response.last_modified)
|
||||
when if_none_match
|
||||
etag_matches?(response.etag)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the Mime type for the \format used in the request.
|
||||
#
|
||||
# GET /posts/5.xml | request.format => Mime::XML
|
||||
# GET /posts/5.xhtml | request.format => Mime::HTML
|
||||
# GET /posts/5 | request.format => request.accepts.first (usually Mime::HTML for browsers)
|
||||
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
|
||||
def format
|
||||
@format ||= parameters[:format] ? Mime::Type.lookup_by_extension(parameters[:format]) : accepts.first
|
||||
@format ||=
|
||||
if parameters[:format]
|
||||
Mime::Type.lookup_by_extension(parameters[:format])
|
||||
elsif ActionController::Base.use_accept_header
|
||||
accepts.first
|
||||
elsif xhr?
|
||||
Mime::Type.lookup_by_extension("js")
|
||||
else
|
||||
Mime::Type.lookup_by_extension("html")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Sets the format by string extension, which can be used to force custom formats that are not controlled by the extension.
|
||||
# Example:
|
||||
|
||||
|
||||
# Sets the \format by string extension, which can be used to force custom formats
|
||||
# that are not controlled by the extension.
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# before_filter :adjust_format_for_iphone
|
||||
#
|
||||
#
|
||||
# private
|
||||
# def adjust_format_for_iphone
|
||||
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
|
||||
|
@ -116,6 +170,25 @@ module ActionController
|
|||
@format = Mime::Type.lookup_by_extension(parameters[:format])
|
||||
end
|
||||
|
||||
# Returns a symbolized version of the <tt>:format</tt> parameter of the request.
|
||||
# If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt>
|
||||
# otherwise.
|
||||
def template_format
|
||||
parameter_format = parameters[:format]
|
||||
|
||||
if parameter_format
|
||||
parameter_format
|
||||
elsif xhr?
|
||||
:js
|
||||
else
|
||||
:html
|
||||
end
|
||||
end
|
||||
|
||||
def cache_format
|
||||
parameters[:format]
|
||||
end
|
||||
|
||||
# Returns true if the request's "X-Requested-With" header contains
|
||||
# "XMLHttpRequest". (The Prototype Javascript library sends this header with
|
||||
# every Ajax request.)
|
||||
|
@ -128,7 +201,7 @@ module ActionController
|
|||
# the right-hand-side of X-Forwarded-For
|
||||
TRUSTED_PROXIES = /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
|
||||
|
||||
# Determine originating IP address. REMOTE_ADDR is the standard
|
||||
# Determines originating IP address. REMOTE_ADDR is the standard
|
||||
# but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
|
||||
# HTTP_X_FORWARDED_FOR are set by proxies so check for these if
|
||||
# REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
|
||||
|
@ -166,44 +239,65 @@ EOM
|
|||
|
||||
@env['REMOTE_ADDR']
|
||||
end
|
||||
memoize :remote_ip
|
||||
|
||||
# Returns the lowercase name of the HTTP server software.
|
||||
def server_software
|
||||
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
|
||||
end
|
||||
memoize :server_software
|
||||
|
||||
|
||||
# Returns the complete URL used for this request
|
||||
# Returns the complete URL used for this request.
|
||||
def url
|
||||
protocol + host_with_port + request_uri
|
||||
end
|
||||
memoize :url
|
||||
|
||||
# Return 'https://' if this is an SSL request and 'http://' otherwise.
|
||||
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
|
||||
def protocol
|
||||
ssl? ? 'https://' : 'http://'
|
||||
end
|
||||
memoize :protocol
|
||||
|
||||
# Is this an SSL request?
|
||||
def ssl?
|
||||
@env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
|
||||
end
|
||||
|
||||
# Returns the host for this request, such as example.com.
|
||||
def host
|
||||
# Returns the \host for this request, such as "example.com".
|
||||
def raw_host_with_port
|
||||
if forwarded = env["HTTP_X_FORWARDED_HOST"]
|
||||
forwarded.split(/,\s?/).last
|
||||
else
|
||||
env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a host:port string for this request, such as example.com or
|
||||
# example.com:8080.
|
||||
def host_with_port
|
||||
@host_with_port ||= host + port_string
|
||||
# Returns the host for this request, such as example.com.
|
||||
def host
|
||||
raw_host_with_port.sub(/:\d+$/, '')
|
||||
end
|
||||
memoize :host
|
||||
|
||||
# Returns a \host:\port string for this request, such as "example.com" or
|
||||
# "example.com:8080".
|
||||
def host_with_port
|
||||
"#{host}#{port_string}"
|
||||
end
|
||||
memoize :host_with_port
|
||||
|
||||
# Returns the port number of this request as an integer.
|
||||
def port
|
||||
@port_as_int ||= @env['SERVER_PORT'].to_i
|
||||
if raw_host_with_port =~ /:(\d+)$/
|
||||
$1.to_i
|
||||
else
|
||||
standard_port
|
||||
end
|
||||
end
|
||||
memoize :port
|
||||
|
||||
# Returns the standard port number for this request's protocol
|
||||
# Returns the standard \port number for this request's protocol.
|
||||
def standard_port
|
||||
case protocol
|
||||
when 'https://' then 443
|
||||
|
@ -211,13 +305,13 @@ EOM
|
|||
end
|
||||
end
|
||||
|
||||
# Returns a port suffix like ":8080" if the port number of this request
|
||||
# is not the default HTTP port 80 or HTTPS port 443.
|
||||
# Returns a \port suffix like ":8080" if the \port number of this request
|
||||
# is not the default HTTP \port 80 or HTTPS \port 443.
|
||||
def port_string
|
||||
(port == standard_port) ? '' : ":#{port}"
|
||||
port == standard_port ? '' : ":#{port}"
|
||||
end
|
||||
|
||||
# Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify
|
||||
# Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
|
||||
# a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
|
||||
def domain(tld_length = 1)
|
||||
return nil unless named_host?(host)
|
||||
|
@ -225,8 +319,9 @@ EOM
|
|||
host.split('.').last(1 + tld_length).join('.')
|
||||
end
|
||||
|
||||
# Returns all the subdomains as an array, so ["dev", "www"] would be returned for "dev.www.rubyonrails.org".
|
||||
# You can specify a different <tt>tld_length</tt>, such as 2 to catch ["www"] instead of ["www", "rubyonrails"]
|
||||
# Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
|
||||
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
|
||||
# such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
|
||||
# in "www.rubyonrails.co.uk".
|
||||
def subdomains(tld_length = 1)
|
||||
return [] unless named_host?(host)
|
||||
|
@ -234,7 +329,7 @@ EOM
|
|||
parts[0..-(tld_length+2)]
|
||||
end
|
||||
|
||||
# Return the query string, accounting for server idiosyncracies.
|
||||
# Returns the query string, accounting for server idiosyncrasies.
|
||||
def query_string
|
||||
if uri = @env['REQUEST_URI']
|
||||
uri.split('?', 2)[1] || ''
|
||||
|
@ -242,8 +337,9 @@ EOM
|
|||
@env['QUERY_STRING'] || ''
|
||||
end
|
||||
end
|
||||
memoize :query_string
|
||||
|
||||
# Return the request URI, accounting for server idiosyncracies.
|
||||
# Returns the request URI, accounting for server idiosyncrasies.
|
||||
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
|
||||
def request_uri
|
||||
if uri = @env['REQUEST_URI']
|
||||
|
@ -251,48 +347,36 @@ EOM
|
|||
(%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
|
||||
else
|
||||
# Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
|
||||
script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
|
||||
uri = @env['PATH_INFO']
|
||||
uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil?
|
||||
unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty?
|
||||
uri << '?' << env_qs
|
||||
uri = @env['PATH_INFO'].to_s
|
||||
|
||||
if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
|
||||
uri = uri.sub(/#{script_filename}\//, '')
|
||||
end
|
||||
|
||||
if uri.nil?
|
||||
env_qs = @env['QUERY_STRING'].to_s
|
||||
uri += "?#{env_qs}" unless env_qs.empty?
|
||||
|
||||
if uri.blank?
|
||||
@env.delete('REQUEST_URI')
|
||||
uri
|
||||
else
|
||||
@env['REQUEST_URI'] = uri
|
||||
end
|
||||
end
|
||||
end
|
||||
memoize :request_uri
|
||||
|
||||
# Returns the interpreted path to requested resource after all the installation directory of this application was taken into account
|
||||
# Returns the interpreted \path to requested resource after all the installation
|
||||
# directory of this application was taken into account.
|
||||
def path
|
||||
path = (uri = request_uri) ? uri.split('?').first.to_s : ''
|
||||
|
||||
# Cut off the path to the installation directory if given
|
||||
path.sub!(%r/^#{relative_url_root}/, '')
|
||||
path || ''
|
||||
end
|
||||
|
||||
# Returns the path minus the web server relative installation directory.
|
||||
# This can be set with the environment variable RAILS_RELATIVE_URL_ROOT.
|
||||
# It can be automatically extracted for Apache setups. If the server is not
|
||||
# Apache, this method returns an empty string.
|
||||
def relative_url_root
|
||||
@@relative_url_root ||= case
|
||||
when @env["RAILS_RELATIVE_URL_ROOT"]
|
||||
@env["RAILS_RELATIVE_URL_ROOT"]
|
||||
when server_software == 'apache'
|
||||
@env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '')
|
||||
else
|
||||
''
|
||||
end
|
||||
path.sub!(%r/^#{ActionController::Base.relative_url_root}/, '')
|
||||
path || ''
|
||||
end
|
||||
memoize :path
|
||||
|
||||
|
||||
# Read the request body. This is useful for web services that need to
|
||||
# Read the request \body. This is useful for web services that need to
|
||||
# work with raw requests directly.
|
||||
def raw_post
|
||||
unless env.include? 'RAW_POST_DATA'
|
||||
|
@ -302,7 +386,7 @@ EOM
|
|||
env['RAW_POST_DATA']
|
||||
end
|
||||
|
||||
# Returns both GET and POST parameters in a single hash.
|
||||
# Returns both GET and POST \parameters in a single hash.
|
||||
def parameters
|
||||
@parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
|
||||
end
|
||||
|
@ -312,34 +396,56 @@ EOM
|
|||
@symbolized_path_parameters = @parameters = nil
|
||||
end
|
||||
|
||||
# The same as <tt>path_parameters</tt> with explicitly symbolized keys
|
||||
def symbolized_path_parameters
|
||||
# The same as <tt>path_parameters</tt> with explicitly symbolized keys.
|
||||
def symbolized_path_parameters
|
||||
@symbolized_path_parameters ||= path_parameters.symbolize_keys
|
||||
end
|
||||
|
||||
# Returns a hash with the parameters used to form the path of the request.
|
||||
# Returned hash keys are strings. See <tt>symbolized_path_parameters</tt> for symbolized keys.
|
||||
#
|
||||
# Example:
|
||||
# Returns a hash with the \parameters used to form the \path of the request.
|
||||
# Returned hash keys are strings:
|
||||
#
|
||||
# {'action' => 'my_action', 'controller' => 'my_controller'}
|
||||
#
|
||||
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
|
||||
def path_parameters
|
||||
@path_parameters ||= {}
|
||||
end
|
||||
|
||||
# The request body is an IO input stream. If the RAW_POST_DATA environment
|
||||
# variable is already set, wrap it in a StringIO.
|
||||
def body
|
||||
if raw_post = env['RAW_POST_DATA']
|
||||
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
|
||||
StringIO.new(raw_post)
|
||||
else
|
||||
body_stream
|
||||
end
|
||||
end
|
||||
|
||||
def remote_addr
|
||||
@env['REMOTE_ADDR']
|
||||
end
|
||||
|
||||
def referrer
|
||||
@env['HTTP_REFERER']
|
||||
end
|
||||
alias referer referrer
|
||||
|
||||
|
||||
def query_parameters
|
||||
@query_parameters ||= self.class.parse_query_parameters(query_string)
|
||||
end
|
||||
|
||||
def request_parameters
|
||||
@request_parameters ||= parse_formatted_request_parameters
|
||||
end
|
||||
|
||||
|
||||
#--
|
||||
# Must be implemented in the concrete request
|
||||
#++
|
||||
|
||||
# The request body is an IO input stream.
|
||||
def body
|
||||
end
|
||||
|
||||
def query_parameters #:nodoc:
|
||||
end
|
||||
|
||||
def request_parameters #:nodoc:
|
||||
def body_stream #:nodoc:
|
||||
end
|
||||
|
||||
def cookies #:nodoc:
|
||||
|
@ -366,8 +472,9 @@ EOM
|
|||
|
||||
# The raw content type string with its parameters stripped off.
|
||||
def content_type_without_parameters
|
||||
@content_type_without_parameters ||= self.class.extract_content_type_without_parameters(content_type_with_parameters)
|
||||
self.class.extract_content_type_without_parameters(content_type_with_parameters)
|
||||
end
|
||||
memoize :content_type_without_parameters
|
||||
|
||||
private
|
||||
def content_type_from_legacy_post_data_format_header
|
||||
|
|
|
@ -17,7 +17,7 @@ module ActionController #:nodoc:
|
|||
# forged link from another site, is done by embedding a token based on the session (which an attacker wouldn't know) in all
|
||||
# forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only
|
||||
# HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
|
||||
# scheme there anyway). Also, GET requests are not protected as these should be indempotent anyway.
|
||||
# scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway.
|
||||
#
|
||||
# This is turned on with the <tt>protect_from_forgery</tt> method, which will check the token and raise an
|
||||
# ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the error message in
|
||||
|
|
|
@ -41,10 +41,9 @@ module ActionController #:nodoc:
|
|||
base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE)
|
||||
base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES
|
||||
|
||||
base.class_inheritable_array :rescue_handlers
|
||||
base.rescue_handlers = []
|
||||
|
||||
base.extend(ClassMethods)
|
||||
base.send :include, ActiveSupport::Rescuable
|
||||
|
||||
base.class_eval do
|
||||
alias_method_chain :perform_action, :rescue
|
||||
end
|
||||
|
@ -54,78 +53,12 @@ module ActionController #:nodoc:
|
|||
def process_with_exception(request, response, exception) #:nodoc:
|
||||
new.process(request, response, :rescue_action, exception)
|
||||
end
|
||||
|
||||
# Rescue exceptions raised in controller actions.
|
||||
#
|
||||
# <tt>rescue_from</tt> receives a series of exception classes or class
|
||||
# names, and a trailing <tt>:with</tt> option with the name of a method
|
||||
# or a Proc object to be called to handle them. Alternatively a block can
|
||||
# be given.
|
||||
#
|
||||
# Handlers that take one argument will be called with the exception, so
|
||||
# that the exception can be inspected when dealing with it.
|
||||
#
|
||||
# Handlers are inherited. They are searched from right to left, from
|
||||
# bottom to top, and up the hierarchy. The handler of the first class for
|
||||
# which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if
|
||||
# any.
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
|
||||
# rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
|
||||
#
|
||||
# rescue_from 'MyAppError::Base' do |exception|
|
||||
# render :xml => exception, :status => 500
|
||||
# end
|
||||
#
|
||||
# protected
|
||||
# def deny_access
|
||||
# ...
|
||||
# end
|
||||
#
|
||||
# def show_errors(exception)
|
||||
# exception.record.new_record? ? ...
|
||||
# end
|
||||
# end
|
||||
def rescue_from(*klasses, &block)
|
||||
options = klasses.extract_options!
|
||||
unless options.has_key?(:with)
|
||||
block_given? ? options[:with] = block : raise(ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument.")
|
||||
end
|
||||
|
||||
klasses.each do |klass|
|
||||
key = if klass.is_a?(Class) && klass <= Exception
|
||||
klass.name
|
||||
elsif klass.is_a?(String)
|
||||
klass
|
||||
else
|
||||
raise(ArgumentError, "#{klass} is neither an Exception nor a String")
|
||||
end
|
||||
|
||||
# Order is important, we put the pair at the end. When dealing with an
|
||||
# exception we will follow the documented order going from right to left.
|
||||
rescue_handlers << [key, options[:with]]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
# Exception handler called when the performance of an action raises an exception.
|
||||
def rescue_action(exception)
|
||||
log_error(exception) if logger
|
||||
erase_results if performed?
|
||||
|
||||
# Let the exception alter the response if it wants.
|
||||
# For example, MethodNotAllowed sets the Allow header.
|
||||
if exception.respond_to?(:handle_response!)
|
||||
exception.handle_response!(response)
|
||||
end
|
||||
|
||||
if consider_all_requests_local || local_request?
|
||||
rescue_action_locally(exception)
|
||||
else
|
||||
rescue_action_in_public(exception)
|
||||
end
|
||||
rescue_with_handler(exception) || rescue_action_without_handler(exception)
|
||||
end
|
||||
|
||||
# Overwrite to implement custom logging of errors. By default logs as fatal.
|
||||
|
@ -144,7 +77,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
# Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
|
||||
# default will call render_optional_error_file. Override this method to provide more user friendly error messages.s
|
||||
# default will call render_optional_error_file. Override this method to provide more user friendly error messages.
|
||||
def rescue_action_in_public(exception) #:doc:
|
||||
render_optional_error_file response_code_for_rescue(exception)
|
||||
end
|
||||
|
@ -173,26 +106,28 @@ module ActionController #:nodoc:
|
|||
# Render detailed diagnostics for unhandled exceptions rescued from
|
||||
# a controller action.
|
||||
def rescue_action_locally(exception)
|
||||
add_variables_to_assigns
|
||||
@template.instance_variable_set("@exception", exception)
|
||||
@template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
|
||||
@template.send!(:assign_variables_from_controller)
|
||||
|
||||
@template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false))
|
||||
@template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception)))
|
||||
|
||||
response.content_type = Mime::HTML
|
||||
render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
|
||||
end
|
||||
|
||||
# Tries to rescue the exception by looking up and calling a registered handler.
|
||||
def rescue_action_with_handler(exception)
|
||||
if handler = handler_for_rescue(exception)
|
||||
if handler.arity != 0
|
||||
handler.call(exception)
|
||||
else
|
||||
handler.call
|
||||
end
|
||||
true # don't rely on the return value of the handler
|
||||
def rescue_action_without_handler(exception)
|
||||
log_error(exception) if logger
|
||||
erase_results if performed?
|
||||
|
||||
# Let the exception alter the response if it wants.
|
||||
# For example, MethodNotAllowed sets the Allow header.
|
||||
if exception.respond_to?(:handle_response!)
|
||||
exception.handle_response!(response)
|
||||
end
|
||||
|
||||
if consider_all_requests_local || local_request?
|
||||
rescue_action_locally(exception)
|
||||
else
|
||||
rescue_action_in_public(exception)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -200,7 +135,7 @@ module ActionController #:nodoc:
|
|||
def perform_action_with_rescue #:nodoc:
|
||||
perform_action_without_rescue
|
||||
rescue Exception => exception
|
||||
rescue_action_with_handler(exception) || rescue_action(exception)
|
||||
rescue_action(exception)
|
||||
end
|
||||
|
||||
def rescues_path(template_name)
|
||||
|
@ -215,36 +150,6 @@ module ActionController #:nodoc:
|
|||
rescue_responses[exception.class.name]
|
||||
end
|
||||
|
||||
def handler_for_rescue(exception)
|
||||
# We go from right to left because pairs are pushed onto rescue_handlers
|
||||
# as rescue_from declarations are found.
|
||||
_, handler = *rescue_handlers.reverse.detect do |klass_name, handler|
|
||||
# The purpose of allowing strings in rescue_from is to support the
|
||||
# declaration of handler associations for exception classes whose
|
||||
# definition is yet unknown.
|
||||
#
|
||||
# Since this loop needs the constants it would be inconsistent to
|
||||
# assume they should exist at this point. An early raised exception
|
||||
# could trigger some other handler and the array could include
|
||||
# precisely a string whose corresponding constant has not yet been
|
||||
# seen. This is why we are tolerant to unknown constants.
|
||||
#
|
||||
# Note that this tolerance only matters if the exception was given as
|
||||
# a string, otherwise a NameError will be raised by the interpreter
|
||||
# itself when rescue_from CONSTANT is executed.
|
||||
klass = self.class.const_get(klass_name) rescue nil
|
||||
klass ||= klass_name.constantize rescue nil
|
||||
exception.is_a?(klass) if klass
|
||||
end
|
||||
|
||||
case handler
|
||||
when Symbol
|
||||
method(handler)
|
||||
when Proc
|
||||
handler.bind(self)
|
||||
end
|
||||
end
|
||||
|
||||
def clean_backtrace(exception)
|
||||
if backtrace = exception.backtrace
|
||||
if defined?(RAILS_ROOT)
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
module ActionController
|
||||
# == Overview
|
||||
#
|
||||
# ActionController::Resources are a way of defining RESTful resources. A RESTful resource, in basic terms,
|
||||
# ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
|
||||
# is something that can be pointed at and it will respond with a representation of the data requested.
|
||||
# In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
|
||||
# requests XML data.
|
||||
#
|
||||
# RESTful design is based on the assumption that there are four generic verbs that a user of an
|
||||
# application can request from a resource (the noun).
|
||||
# application can request from a \resource (the noun).
|
||||
#
|
||||
# Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
|
||||
# \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
|
||||
# denotes the type of action that should take place.
|
||||
#
|
||||
# === The Different Methods and their Usage
|
||||
#
|
||||
# +GET+ Requests for a resource, no saving or editing of a resource should occur in a GET request
|
||||
# +POST+ Creation of resources
|
||||
# +PUT+ Editing of attributes on a resource
|
||||
# +DELETE+ Deletion of a resource
|
||||
# * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
|
||||
# * POST - Creation of \resources.
|
||||
# * PUT - Editing of attributes on a \resource.
|
||||
# * DELETE - Deletion of a \resource.
|
||||
#
|
||||
# === Examples
|
||||
#
|
||||
|
@ -72,7 +72,7 @@ module ActionController
|
|||
end
|
||||
|
||||
def conditions
|
||||
@conditions = @options[:conditions] || {}
|
||||
@conditions ||= @options[:conditions] || {}
|
||||
end
|
||||
|
||||
def path
|
||||
|
@ -80,21 +80,29 @@ module ActionController
|
|||
end
|
||||
|
||||
def new_path
|
||||
new_action = self.options[:path_names][:new] if self.options[:path_names]
|
||||
new_action = self.options[:path_names][:new] if self.options[:path_names]
|
||||
new_action ||= Base.resources_path_names[:new]
|
||||
@new_path ||= "#{path}/#{new_action}"
|
||||
@new_path ||= "#{path}/#{new_action}"
|
||||
end
|
||||
|
||||
def shallow_path_prefix
|
||||
@shallow_path_prefix ||= "#{path_prefix unless @options[:shallow]}"
|
||||
end
|
||||
|
||||
def member_path
|
||||
@member_path ||= "#{path}/:id"
|
||||
@member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
|
||||
end
|
||||
|
||||
def nesting_path_prefix
|
||||
@nesting_path_prefix ||= "#{path}/:#{singular}_id"
|
||||
@nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
|
||||
end
|
||||
|
||||
def shallow_name_prefix
|
||||
@shallow_name_prefix ||= "#{name_prefix unless @options[:shallow]}"
|
||||
end
|
||||
|
||||
def nesting_name_prefix
|
||||
"#{name_prefix}#{singular}_"
|
||||
"#{shallow_name_prefix}#{singular}_"
|
||||
end
|
||||
|
||||
def action_separator
|
||||
|
@ -141,12 +149,14 @@ module ActionController
|
|||
super
|
||||
end
|
||||
|
||||
alias_method :shallow_path_prefix, :path_prefix
|
||||
alias_method :shallow_name_prefix, :name_prefix
|
||||
alias_method :member_path, :path
|
||||
alias_method :nesting_path_prefix, :path
|
||||
end
|
||||
|
||||
# Creates named routes for implementing verb-oriented controllers
|
||||
# for a collection resource.
|
||||
# for a collection \resource.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
|
@ -238,23 +248,24 @@ module ActionController
|
|||
#
|
||||
# The +resources+ method accepts the following options to customize the resulting routes:
|
||||
# * <tt>:collection</tt> - Add named routes for other actions that operate on the collection.
|
||||
# Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>
|
||||
# or <tt>:any</tt> if the method does not matter. These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
|
||||
# Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>,
|
||||
# an array of any of the previous, or <tt>:any</tt> if the method does not matter.
|
||||
# These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
|
||||
# * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member.
|
||||
# * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new resource action.
|
||||
# * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
|
||||
# * <tt>:controller</tt> - Specify the controller name for the routes.
|
||||
# * <tt>:singular</tt> - Specify the singular name used in the member routes.
|
||||
# * <tt>:requirements</tt> - Set custom routing parameter requirements.
|
||||
# * <tt>:conditions</tt> - Specify custom routing recognition conditions. Resources sets the <tt>:method</tt> value for the method-specific routes.
|
||||
# * <tt>:as</tt> - Specify a different resource name to use in the URL path. For example:
|
||||
# * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
|
||||
# * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
|
||||
# # products_path == '/productos'
|
||||
# map.resources :products, :as => 'productos' do |product|
|
||||
# # product_reviews_path(product) == '/productos/1234/comentarios'
|
||||
# product.resources :product_reviews, :as => 'comentarios'
|
||||
# end
|
||||
#
|
||||
# * <tt>:has_one</tt> - Specify nested resources, this is a shorthand for mapping singleton resources beneath the current.
|
||||
# * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural resources.
|
||||
# * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
|
||||
# * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources.
|
||||
#
|
||||
# You may directly specify the routing association with +has_one+ and +has_many+ like:
|
||||
#
|
||||
|
@ -277,18 +288,18 @@ module ActionController
|
|||
#
|
||||
# * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables.
|
||||
#
|
||||
# Weblog comments usually belong to a post, so you might use resources like:
|
||||
# Weblog comments usually belong to a post, so you might use +resources+ like:
|
||||
#
|
||||
# map.resources :articles
|
||||
# map.resources :comments, :path_prefix => '/articles/:article_id'
|
||||
#
|
||||
# You can nest resources calls to set this automatically:
|
||||
# You can nest +resources+ calls to set this automatically:
|
||||
#
|
||||
# map.resources :articles do |article|
|
||||
# article.resources :comments
|
||||
# end
|
||||
#
|
||||
# The comment resources work the same, but must now include a value for <tt>:article_id</tt>.
|
||||
# The comment \resources work the same, but must now include a value for <tt>:article_id</tt>.
|
||||
#
|
||||
# article_comments_url(@article)
|
||||
# article_comment_url(@article, @comment)
|
||||
|
@ -296,23 +307,52 @@ module ActionController
|
|||
# article_comments_url(:article_id => @article)
|
||||
# article_comment_url(:article_id => @article, :id => @comment)
|
||||
#
|
||||
# If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly:
|
||||
#
|
||||
# articles_comments_url(@comment.article_id, @comment)
|
||||
#
|
||||
# * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore.
|
||||
# Use this if you have named routes that may clash.
|
||||
#
|
||||
# map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
|
||||
# map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
|
||||
#
|
||||
# You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested resource:
|
||||
#
|
||||
# You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource:
|
||||
#
|
||||
# map.resources :articles do |article|
|
||||
# article.resources :comments, :name_prefix => nil
|
||||
# end
|
||||
#
|
||||
# This will yield named resources like so:
|
||||
#
|
||||
# end
|
||||
#
|
||||
# This will yield named \resources like so:
|
||||
#
|
||||
# comments_url(@article)
|
||||
# comment_url(@article, @comment)
|
||||
#
|
||||
# * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member
|
||||
# (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
|
||||
#
|
||||
# The <tt>:shallow</tt> option is inherited by any nested resource(s).
|
||||
#
|
||||
# For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
|
||||
#
|
||||
# map.resources :users, :shallow => true do |user|
|
||||
# user.resources :posts do |post|
|
||||
# post.resources :comments
|
||||
# end
|
||||
# end
|
||||
# # --> GET /users/1/posts (maps to the PostsController#index action as usual)
|
||||
# # also adds the usual named route called "user_posts"
|
||||
# # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
|
||||
# # also adds the named route called "post"
|
||||
# # --> GET /posts/2/comments (maps to the CommentsController#index action)
|
||||
# # also adds the named route called "post_comments"
|
||||
# # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
|
||||
# # also adds the named route called "comment"
|
||||
#
|
||||
# You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like:
|
||||
#
|
||||
# map.resources :users, :has_many => { :posts => :comments }, :shallow => true
|
||||
#
|
||||
# If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
|
||||
#
|
||||
# Examples:
|
||||
|
@ -345,28 +385,28 @@ module ActionController
|
|||
#
|
||||
# The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
|
||||
# HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
|
||||
# <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for resource routes.
|
||||
# <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes.
|
||||
def resources(*entities, &block)
|
||||
options = entities.extract_options!
|
||||
entities.each { |entity| map_resource(entity, options.dup, &block) }
|
||||
end
|
||||
|
||||
# Creates named routes for implementing verb-oriented controllers for a singleton resource.
|
||||
# A singleton resource is global to its current context. For unnested singleton resources,
|
||||
# the resource is global to the current user visiting the application, such as a user's
|
||||
# /account profile. For nested singleton resources, the resource is global to its parent
|
||||
# resource, such as a <tt>projects</tt> resource that <tt>has_one :project_manager</tt>.
|
||||
# The <tt>project_manager</tt> should be mapped as a singleton resource under <tt>projects</tt>:
|
||||
# Creates named routes for implementing verb-oriented controllers for a singleton \resource.
|
||||
# A singleton \resource is global to its current context. For unnested singleton \resources,
|
||||
# the \resource is global to the current user visiting the application, such as a user's
|
||||
# <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent
|
||||
# \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>.
|
||||
# The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>:
|
||||
#
|
||||
# map.resources :projects do |project|
|
||||
# project.resource :project_manager
|
||||
# end
|
||||
#
|
||||
# See map.resources for general conventions. These are the main differences:
|
||||
# * A singular name is given to map.resource. The default controller name is still taken from the plural name.
|
||||
# See +resources+ for general conventions. These are the main differences:
|
||||
# * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name.
|
||||
# * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option.
|
||||
# * No default index route is created for the singleton resource controller.
|
||||
# * When nesting singleton resources, only the singular name is used as the path prefix (example: 'account/messages/1')
|
||||
# * No default index route is created for the singleton \resource controller.
|
||||
# * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
|
@ -438,7 +478,7 @@ module ActionController
|
|||
map_associations(resource, options)
|
||||
|
||||
if block_given?
|
||||
with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
|
||||
with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -455,30 +495,45 @@ module ActionController
|
|||
map_associations(resource, options)
|
||||
|
||||
if block_given?
|
||||
with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
|
||||
with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def map_associations(resource, options)
|
||||
map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
|
||||
|
||||
path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
|
||||
name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
|
||||
|
||||
Array(options[:has_many]).each do |association|
|
||||
resources(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace])
|
||||
end
|
||||
|
||||
Array(options[:has_one]).each do |association|
|
||||
resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace])
|
||||
resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace], :shallow => options[:shallow])
|
||||
end
|
||||
end
|
||||
|
||||
def map_has_many_associations(resource, associations, options)
|
||||
case associations
|
||||
when Hash
|
||||
associations.each do |association,has_many|
|
||||
map_has_many_associations(resource, association, options.merge(:has_many => has_many))
|
||||
end
|
||||
when Array
|
||||
associations.each do |association|
|
||||
map_has_many_associations(resource, association, options)
|
||||
end
|
||||
when Symbol, String
|
||||
resources(associations, :path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], :has_many => options[:has_many])
|
||||
else
|
||||
end
|
||||
end
|
||||
|
||||
def map_collection_actions(map, resource)
|
||||
resource.collection_methods.each do |method, actions|
|
||||
actions.each do |action|
|
||||
action_options = action_options_for(action, resource, method)
|
||||
map.named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
|
||||
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}.:format", action_options)
|
||||
[method].flatten.each do |m|
|
||||
action_options = action_options_for(action, resource, m)
|
||||
map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -491,18 +546,15 @@ module ActionController
|
|||
index_route_name << "_index"
|
||||
end
|
||||
|
||||
map.named_route(index_route_name, resource.path, index_action_options)
|
||||
map.named_route("formatted_#{index_route_name}", "#{resource.path}.:format", index_action_options)
|
||||
map_named_routes(map, index_route_name, resource.path, index_action_options)
|
||||
|
||||
create_action_options = action_options_for("create", resource)
|
||||
map.connect(resource.path, create_action_options)
|
||||
map.connect("#{resource.path}.:format", create_action_options)
|
||||
map_unnamed_routes(map, resource.path, create_action_options)
|
||||
end
|
||||
|
||||
def map_default_singleton_actions(map, resource)
|
||||
create_action_options = action_options_for("create", resource)
|
||||
map.connect(resource.path, create_action_options)
|
||||
map.connect("#{resource.path}.:format", create_action_options)
|
||||
map_unnamed_routes(map, resource.path, create_action_options)
|
||||
end
|
||||
|
||||
def map_new_actions(map, resource)
|
||||
|
@ -510,11 +562,9 @@ module ActionController
|
|||
actions.each do |action|
|
||||
action_options = action_options_for(action, resource, method)
|
||||
if action == :new
|
||||
map.named_route("new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
|
||||
map.named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}.:format", action_options)
|
||||
map_named_routes(map, "new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
|
||||
else
|
||||
map.named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
|
||||
map.named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}.:format", action_options)
|
||||
map_named_routes(map, "#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -523,27 +573,35 @@ module ActionController
|
|||
def map_member_actions(map, resource)
|
||||
resource.member_methods.each do |method, actions|
|
||||
actions.each do |action|
|
||||
action_options = action_options_for(action, resource, method)
|
||||
[method].flatten.each do |m|
|
||||
action_options = action_options_for(action, resource, m)
|
||||
|
||||
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
|
||||
action_path ||= Base.resources_path_names[action] || action
|
||||
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
|
||||
action_path ||= Base.resources_path_names[action] || action
|
||||
|
||||
map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
|
||||
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}.:format",action_options)
|
||||
map_named_routes(map, "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
show_action_options = action_options_for("show", resource)
|
||||
map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
|
||||
map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options)
|
||||
map_named_routes(map, "#{resource.shallow_name_prefix}#{resource.singular}", resource.member_path, show_action_options)
|
||||
|
||||
update_action_options = action_options_for("update", resource)
|
||||
map.connect(resource.member_path, update_action_options)
|
||||
map.connect("#{resource.member_path}.:format", update_action_options)
|
||||
map_unnamed_routes(map, resource.member_path, update_action_options)
|
||||
|
||||
destroy_action_options = action_options_for("destroy", resource)
|
||||
map.connect(resource.member_path, destroy_action_options)
|
||||
map.connect("#{resource.member_path}.:format", destroy_action_options)
|
||||
map_unnamed_routes(map, resource.member_path, destroy_action_options)
|
||||
end
|
||||
|
||||
def map_unnamed_routes(map, path_without_format, options)
|
||||
map.connect(path_without_format, options)
|
||||
map.connect("#{path_without_format}.:format", options)
|
||||
end
|
||||
|
||||
def map_named_routes(map, name, path_without_format, options)
|
||||
map.named_route(name, path_without_format, options)
|
||||
map.named_route("formatted_#{name}", "#{path_without_format}.:format", options)
|
||||
end
|
||||
|
||||
def add_conditions_for(conditions, method)
|
||||
|
@ -555,6 +613,7 @@ module ActionController
|
|||
def action_options_for(action, resource, method = nil)
|
||||
default_options = { :action => action.to_s }
|
||||
require_id = !resource.kind_of?(SingletonResource)
|
||||
|
||||
case default_options[:action]
|
||||
when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
|
||||
when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
|
||||
|
|
|
@ -1,57 +1,169 @@
|
|||
require 'digest/md5'
|
||||
|
||||
module ActionController
|
||||
class AbstractResponse #:nodoc:
|
||||
module ActionController # :nodoc:
|
||||
# Represents an HTTP response generated by a controller action. One can use an
|
||||
# ActionController::AbstractResponse object to retrieve the current state of the
|
||||
# response, or customize the response. An AbstractResponse object can either
|
||||
# represent a "real" HTTP response (i.e. one that is meant to be sent back to the
|
||||
# web browser) or a test response (i.e. one that is generated from integration
|
||||
# tests). See CgiResponse and TestResponse, respectively.
|
||||
#
|
||||
# AbstractResponse is mostly a Ruby on Rails framework implement detail, and should
|
||||
# never be used directly in controllers. Controllers should use the methods defined
|
||||
# in ActionController::Base instead. For example, if you want to set the HTTP
|
||||
# response's content MIME type, then use ActionControllerBase#headers instead of
|
||||
# AbstractResponse#headers.
|
||||
#
|
||||
# Nevertheless, integration tests may want to inspect controller responses in more
|
||||
# detail, and that's when AbstractResponse can be useful for application developers.
|
||||
# Integration test methods such as ActionController::Integration::Session#get and
|
||||
# ActionController::Integration::Session#post return objects of type TestResponse
|
||||
# (which are of course also of type AbstractResponse).
|
||||
#
|
||||
# For example, the following demo integration "test" prints the body of the
|
||||
# controller response to the console:
|
||||
#
|
||||
# class DemoControllerTest < ActionController::IntegrationTest
|
||||
# def test_print_root_path_to_console
|
||||
# get('/')
|
||||
# puts @response.body
|
||||
# end
|
||||
# end
|
||||
class AbstractResponse
|
||||
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
|
||||
attr_accessor :request
|
||||
attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
|
||||
|
||||
# The body content (e.g. HTML) of the response, as a String.
|
||||
attr_accessor :body
|
||||
# The headers of the response, as a Hash. It maps header names to header values.
|
||||
attr_accessor :headers
|
||||
attr_accessor :session, :cookies, :assigns, :template, :layout
|
||||
attr_accessor :redirected_to, :redirected_to_method_params
|
||||
|
||||
delegate :default_charset, :to => 'ActionController::Base'
|
||||
|
||||
def initialize
|
||||
@body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
|
||||
end
|
||||
|
||||
def status; headers['Status'] end
|
||||
def status=(status) headers['Status'] = status end
|
||||
|
||||
def location; headers['Location'] end
|
||||
def location=(url) headers['Location'] = url end
|
||||
|
||||
|
||||
# Sets the HTTP response's content MIME type. For example, in the controller
|
||||
# you could write this:
|
||||
#
|
||||
# response.content_type = "text/plain"
|
||||
#
|
||||
# If a character set has been defined for this response (see charset=) then
|
||||
# the character set information will also be included in the content type
|
||||
# information.
|
||||
def content_type=(mime_type)
|
||||
self.headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type
|
||||
self.headers["Content-Type"] =
|
||||
if mime_type =~ /charset/ || (c = charset).nil?
|
||||
mime_type.to_s
|
||||
else
|
||||
"#{mime_type}; charset=#{c}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Returns the response's content MIME type, or nil if content type has been set.
|
||||
def content_type
|
||||
content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0]
|
||||
content_type.blank? ? nil : content_type
|
||||
end
|
||||
|
||||
def charset=(encoding)
|
||||
self.headers["Content-Type"] = "#{content_type || Mime::HTML}; charset=#{encoding}"
|
||||
|
||||
# Set the charset of the Content-Type header. Set to nil to remove it.
|
||||
# If no content type is set, it defaults to HTML.
|
||||
def charset=(charset)
|
||||
headers["Content-Type"] =
|
||||
if charset
|
||||
"#{content_type || Mime::HTML}; charset=#{charset}"
|
||||
else
|
||||
content_type || Mime::HTML.to_s
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def charset
|
||||
charset = String(headers["Content-Type"] || headers["type"]).split(";")[1]
|
||||
charset.blank? ? nil : charset.strip.split("=")[1]
|
||||
end
|
||||
|
||||
def redirect(to_url, response_status)
|
||||
self.headers["Status"] = response_status
|
||||
self.headers["Location"] = to_url.gsub(/[\r\n]/, '')
|
||||
def last_modified
|
||||
if last = headers['Last-Modified']
|
||||
Time.httpdate(last)
|
||||
end
|
||||
end
|
||||
|
||||
self.body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(to_url)}\">redirected</a>.</body></html>"
|
||||
def last_modified?
|
||||
headers.include?('Last-Modified')
|
||||
end
|
||||
|
||||
def last_modified=(utc_time)
|
||||
headers['Last-Modified'] = utc_time.httpdate
|
||||
end
|
||||
|
||||
def etag
|
||||
headers['ETag']
|
||||
end
|
||||
|
||||
def etag?
|
||||
headers.include?('ETag')
|
||||
end
|
||||
|
||||
def etag=(etag)
|
||||
headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
|
||||
end
|
||||
|
||||
def redirect(url, status)
|
||||
self.status = status
|
||||
self.location = url.gsub(/[\r\n]/, '')
|
||||
self.body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>"
|
||||
end
|
||||
|
||||
def sending_file?
|
||||
headers["Content-Transfer-Encoding"] == "binary"
|
||||
end
|
||||
|
||||
def assign_default_content_type_and_charset!
|
||||
self.content_type ||= Mime::HTML
|
||||
self.charset ||= default_charset unless sending_file?
|
||||
end
|
||||
|
||||
def prepare!
|
||||
assign_default_content_type_and_charset!
|
||||
handle_conditional_get!
|
||||
convert_content_type!
|
||||
set_content_length!
|
||||
convert_content_type!
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def handle_conditional_get!
|
||||
if body.is_a?(String) && (headers['Status'] ? headers['Status'][0..2] == '200' : true) && !body.empty?
|
||||
self.headers['ETag'] ||= %("#{Digest::MD5.hexdigest(body)}")
|
||||
self.headers['Cache-Control'] = 'private, max-age=0, must-revalidate' if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
|
||||
def handle_conditional_get!
|
||||
if etag? || last_modified?
|
||||
set_conditional_cache_control!
|
||||
elsif nonempty_ok_response?
|
||||
self.etag = body
|
||||
|
||||
if request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
|
||||
self.headers['Status'] = '304 Not Modified'
|
||||
self.body = ''
|
||||
end
|
||||
if request && request.etag_matches?(etag)
|
||||
self.status = '304 Not Modified'
|
||||
self.body = ''
|
||||
end
|
||||
|
||||
set_conditional_cache_control!
|
||||
end
|
||||
end
|
||||
|
||||
def nonempty_ok_response?
|
||||
ok = !status || status[0..2] == '200'
|
||||
ok && body.is_a?(String) && !body.empty?
|
||||
end
|
||||
|
||||
def set_conditional_cache_control!
|
||||
if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
|
||||
headers['Cache-Control'] = 'private, max-age=0, must-revalidate'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -70,7 +182,9 @@ module ActionController
|
|||
# Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice
|
||||
# for, say, a 2GB streaming file.
|
||||
def set_content_length!
|
||||
self.headers["Content-Length"] = body.size unless body.respond_to?(:call)
|
||||
unless body.respond_to?(:call) || (status && status[0..2] == '304')
|
||||
self.headers["Content-Length"] ||= body.size
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -201,7 +201,7 @@ module ActionController
|
|||
# With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>.
|
||||
#
|
||||
# * <tt>:method</tt> - Allows you to specify which method can access the route. Possible values are <tt>:post</tt>,
|
||||
# <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
|
||||
# <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
|
||||
# <tt>:any</tt> means that any method can access the route.
|
||||
#
|
||||
# Example:
|
||||
|
@ -213,7 +213,7 @@ module ActionController
|
|||
#
|
||||
# Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
|
||||
# URL will route to the <tt>show</tt> action.
|
||||
#
|
||||
#
|
||||
# == Reloading routes
|
||||
#
|
||||
# You can reload routes if you feel you must:
|
||||
|
@ -281,9 +281,9 @@ module ActionController
|
|||
end
|
||||
|
||||
class << self
|
||||
# Expects an array of controller names as the first argument.
|
||||
# Executes the passed block with only the named controllers named available.
|
||||
# This method is used in internal Rails testing.
|
||||
# Expects an array of controller names as the first argument.
|
||||
# Executes the passed block with only the named controllers named available.
|
||||
# This method is used in internal Rails testing.
|
||||
def with_controllers(names)
|
||||
prior_controllers = @possible_controllers
|
||||
use_controllers! names
|
||||
|
@ -292,10 +292,10 @@ module ActionController
|
|||
use_controllers! prior_controllers
|
||||
end
|
||||
|
||||
# Returns an array of paths, cleaned of double-slashes and relative path references.
|
||||
# * "\\\" and "//" become "\\" or "/".
|
||||
# * "/foo/bar/../config" becomes "/foo/config".
|
||||
# The returned array is sorted by length, descending.
|
||||
# Returns an array of paths, cleaned of double-slashes and relative path references.
|
||||
# * "\\\" and "//" become "\\" or "/".
|
||||
# * "/foo/bar/../config" becomes "/foo/config".
|
||||
# The returned array is sorted by length, descending.
|
||||
def normalize_paths(paths)
|
||||
# do the hokey-pokey of path normalization...
|
||||
paths = paths.collect do |path|
|
||||
|
@ -314,7 +314,7 @@ module ActionController
|
|||
paths = paths.uniq.sort_by { |path| - path.length }
|
||||
end
|
||||
|
||||
# Returns the array of controller names currently available to ActionController::Routing.
|
||||
# Returns the array of controller names currently available to ActionController::Routing.
|
||||
def possible_controllers
|
||||
unless @possible_controllers
|
||||
@possible_controllers = []
|
||||
|
@ -339,28 +339,27 @@ module ActionController
|
|||
@possible_controllers
|
||||
end
|
||||
|
||||
# Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
|
||||
# ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
|
||||
# Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
|
||||
# ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
|
||||
def use_controllers!(controller_names)
|
||||
@possible_controllers = controller_names
|
||||
end
|
||||
|
||||
# Returns a controller path for a new +controller+ based on a +previous+ controller path.
|
||||
# Handles 4 scenarios:
|
||||
#
|
||||
# * stay in the previous controller:
|
||||
# controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
|
||||
#
|
||||
# * stay in the previous namespace:
|
||||
# controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
|
||||
#
|
||||
# * forced move to the root namespace:
|
||||
# controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
|
||||
#
|
||||
# * previous namespace is root:
|
||||
# controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
|
||||
#
|
||||
|
||||
# Returns a controller path for a new +controller+ based on a +previous+ controller path.
|
||||
# Handles 4 scenarios:
|
||||
#
|
||||
# * stay in the previous controller:
|
||||
# controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
|
||||
#
|
||||
# * stay in the previous namespace:
|
||||
# controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
|
||||
#
|
||||
# * forced move to the root namespace:
|
||||
# controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
|
||||
#
|
||||
# * previous namespace is root:
|
||||
# controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
|
||||
#
|
||||
def controller_relative_to(controller, previous)
|
||||
if controller.nil? then previous
|
||||
elsif controller[0] == ?/ then controller[1..-1]
|
||||
|
@ -369,12 +368,11 @@ module ActionController
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Routes = RouteSet.new
|
||||
|
||||
ActiveSupport::Inflector.module_eval do
|
||||
# Ensures that routes are reloaded when Rails inflections are updated.
|
||||
# Ensures that routes are reloaded when Rails inflections are updated.
|
||||
def inflections_with_route_reloading(&block)
|
||||
returning(inflections_without_route_reloading(&block)) {
|
||||
ActionController::Routing::Routes.reload! if block_given?
|
||||
|
|
|
@ -48,14 +48,10 @@ module ActionController
|
|||
end
|
||||
when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true)
|
||||
when /\A\?(.*?)\?/
|
||||
returning segment = StaticSegment.new($1) do
|
||||
segment.is_optional = true
|
||||
end
|
||||
StaticSegment.new($1, :optional => true)
|
||||
when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1)
|
||||
when Regexp.new(separator_pattern) then
|
||||
returning segment = DividerSegment.new($&) do
|
||||
segment.is_optional = (optional_separators.include? $&)
|
||||
end
|
||||
DividerSegment.new($&, :optional => (optional_separators.include? $&))
|
||||
end
|
||||
[segment, $~.post_match]
|
||||
end
|
||||
|
@ -64,18 +60,18 @@ module ActionController
|
|||
# segments are passed alongside in order to distinguish between default values
|
||||
# and requirements.
|
||||
def divide_route_options(segments, options)
|
||||
options = options.dup
|
||||
options = options.except(:path_prefix, :name_prefix)
|
||||
|
||||
if options[:namespace]
|
||||
options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
|
||||
options.delete(:path_prefix)
|
||||
options.delete(:name_prefix)
|
||||
end
|
||||
|
||||
requirements = (options.delete(:requirements) || {}).dup
|
||||
defaults = (options.delete(:defaults) || {}).dup
|
||||
conditions = (options.delete(:conditions) || {}).dup
|
||||
|
||||
validate_route_conditions(conditions)
|
||||
|
||||
path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
|
||||
options.each do |key, value|
|
||||
hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
|
||||
|
@ -174,30 +170,32 @@ module ActionController
|
|||
defaults, requirements, conditions = divide_route_options(segments, options)
|
||||
requirements = assign_route_options(segments, defaults, requirements)
|
||||
|
||||
route = Route.new
|
||||
# TODO: Segments should be frozen on initialize
|
||||
segments.each { |segment| segment.freeze }
|
||||
|
||||
route.segments = segments
|
||||
route.requirements = requirements
|
||||
route.conditions = conditions
|
||||
|
||||
if !route.significant_keys.include?(:action) && !route.requirements[:action]
|
||||
route.requirements[:action] = "index"
|
||||
route.significant_keys << :action
|
||||
end
|
||||
|
||||
# Routes cannot use the current string interpolation method
|
||||
# if there are user-supplied <tt>:requirements</tt> as the interpolation
|
||||
# code won't raise RoutingErrors when generating
|
||||
if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
|
||||
route.optimise = false
|
||||
end
|
||||
route = Route.new(segments, requirements, conditions)
|
||||
|
||||
if !route.significant_keys.include?(:controller)
|
||||
raise ArgumentError, "Illegal route: the :controller must be specified!"
|
||||
end
|
||||
|
||||
route
|
||||
route.freeze
|
||||
end
|
||||
|
||||
private
|
||||
def validate_route_conditions(conditions)
|
||||
if method = conditions[:method]
|
||||
[method].flatten.each do |m|
|
||||
if m == :head
|
||||
raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
|
||||
end
|
||||
|
||||
unless HTTP_METHODS.include?(m.to_sym)
|
||||
raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
# Much of the slow performance from routes comes from the
|
||||
# Much of the slow performance from routes comes from the
|
||||
# complexity of expiry, <tt>:requirements</tt> matching, defaults providing
|
||||
# and figuring out which url pattern to use. With named routes
|
||||
# we can avoid the expense of finding the right route. So if
|
||||
# and figuring out which url pattern to use. With named routes
|
||||
# we can avoid the expense of finding the right route. So if
|
||||
# they've provided the right number of arguments, and have no
|
||||
# <tt>:requirements</tt>, we can just build up a string and return it.
|
||||
#
|
||||
# To support building optimisations for other common cases, the
|
||||
# generation code is separated into several classes
|
||||
#
|
||||
# To support building optimisations for other common cases, the
|
||||
# generation code is separated into several classes
|
||||
module Optimisation
|
||||
def generate_optimisation_block(route, kind)
|
||||
return "" unless route.optimise?
|
||||
|
@ -20,6 +20,7 @@ module ActionController
|
|||
|
||||
class Optimiser
|
||||
attr_reader :route, :kind
|
||||
|
||||
def initialize(route, kind)
|
||||
@route = route
|
||||
@kind = kind
|
||||
|
@ -53,12 +54,12 @@ module ActionController
|
|||
# map.person '/people/:id'
|
||||
#
|
||||
# If the user calls <tt>person_url(@person)</tt>, we can simply
|
||||
# return a string like "/people/#{@person.to_param}"
|
||||
# return a string like "/people/#{@person.to_param}"
|
||||
# rather than triggering the expensive logic in +url_for+.
|
||||
class PositionalArguments < Optimiser
|
||||
def guard_condition
|
||||
number_of_arguments = route.segment_keys.size
|
||||
# if they're using foo_url(:id=>2) it's one
|
||||
# if they're using foo_url(:id=>2) it's one
|
||||
# argument, but we don't want to generate /foos/id2
|
||||
if number_of_arguments == 1
|
||||
"(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)"
|
||||
|
@ -76,7 +77,7 @@ module ActionController
|
|||
elements << '#{request.host_with_port}'
|
||||
end
|
||||
|
||||
elements << '#{request.relative_url_root if request.relative_url_root}'
|
||||
elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}'
|
||||
|
||||
# The last entry in <tt>route.segments</tt> appears to *always* be a
|
||||
# 'divider segment' for '/' but we have assertions to ensure that
|
||||
|
@ -94,23 +95,24 @@ module ActionController
|
|||
end
|
||||
|
||||
# This case is mostly the same as the positional arguments case
|
||||
# above, but it supports additional query parameters as the last
|
||||
# above, but it supports additional query parameters as the last
|
||||
# argument
|
||||
class PositionalArgumentsWithAdditionalParams < PositionalArguments
|
||||
def guard_condition
|
||||
"(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == #{route.segment_keys.size + 1} && !args.last.has_key?(:anchor) && !args.last.has_key?(:port) && !args.last.has_key?(:host)"
|
||||
end
|
||||
|
||||
# This case uses almost the same code as positional arguments,
|
||||
# but add an args.last.to_query on the end
|
||||
# This case uses almost the same code as positional arguments,
|
||||
# but add a question mark and args.last.to_query on the end,
|
||||
# unless the last arg is empty
|
||||
def generation_code
|
||||
super.insert(-2, '?#{args.last.to_query}')
|
||||
super.insert(-2, '#{\'?\' + args.last.to_query unless args.last.empty?}')
|
||||
end
|
||||
|
||||
# To avoid generating "http://localhost/?host=foo.example.com" we
|
||||
# can't use this optimisation on routes without any segments
|
||||
def applicable?
|
||||
super && route.segment_keys.size > 0
|
||||
super && route.segment_keys.size > 0
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -51,7 +51,6 @@ module ActionController
|
|||
# 3) segm test for /users/:id
|
||||
# (jump to list index = 5)
|
||||
# 4) full test for /users/:id => here we are!
|
||||
|
||||
class RouteSet
|
||||
def recognize_path(path, environment={})
|
||||
result = recognize_optimized(path, environment) and return result
|
||||
|
@ -68,28 +67,6 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
def recognize_optimized(path, env)
|
||||
write_recognize_optimized
|
||||
recognize_optimized(path, env)
|
||||
end
|
||||
|
||||
def write_recognize_optimized
|
||||
tree = segment_tree(routes)
|
||||
body = generate_code(tree)
|
||||
instance_eval %{
|
||||
def recognize_optimized(path, env)
|
||||
segments = to_plain_segments(path)
|
||||
index = #{body}
|
||||
return nil unless index
|
||||
while index < routes.size
|
||||
result = routes[index].recognize(path, env) and return result
|
||||
index += 1
|
||||
end
|
||||
nil
|
||||
end
|
||||
}, __FILE__, __LINE__
|
||||
end
|
||||
|
||||
def segment_tree(routes)
|
||||
tree = [0]
|
||||
|
||||
|
@ -153,6 +130,45 @@ module ActionController
|
|||
segments
|
||||
end
|
||||
|
||||
private
|
||||
def write_recognize_optimized!
|
||||
tree = segment_tree(routes)
|
||||
body = generate_code(tree)
|
||||
|
||||
remove_recognize_optimized!
|
||||
|
||||
instance_eval %{
|
||||
def recognize_optimized(path, env)
|
||||
segments = to_plain_segments(path)
|
||||
index = #{body}
|
||||
return nil unless index
|
||||
while index < routes.size
|
||||
result = routes[index].recognize(path, env) and return result
|
||||
index += 1
|
||||
end
|
||||
nil
|
||||
end
|
||||
}, __FILE__, __LINE__
|
||||
end
|
||||
|
||||
def clear_recognize_optimized!
|
||||
remove_recognize_optimized!
|
||||
|
||||
class << self
|
||||
def recognize_optimized(path, environment)
|
||||
write_recognize_optimized!
|
||||
recognize_optimized(path, environment)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def remove_recognize_optimized!
|
||||
if respond_to?(:recognize_optimized)
|
||||
class << self
|
||||
remove_method :recognize_optimized
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,11 +3,25 @@ module ActionController
|
|||
class Route #:nodoc:
|
||||
attr_accessor :segments, :requirements, :conditions, :optimise
|
||||
|
||||
def initialize
|
||||
@segments = []
|
||||
@requirements = {}
|
||||
@conditions = {}
|
||||
@optimise = true
|
||||
def initialize(segments = [], requirements = {}, conditions = {})
|
||||
@segments = segments
|
||||
@requirements = requirements
|
||||
@conditions = conditions
|
||||
|
||||
if !significant_keys.include?(:action) && !requirements[:action]
|
||||
@requirements[:action] = "index"
|
||||
@significant_keys << :action
|
||||
end
|
||||
|
||||
# Routes cannot use the current string interpolation method
|
||||
# if there are user-supplied <tt>:requirements</tt> as the interpolation
|
||||
# code won't raise RoutingErrors when generating
|
||||
has_requirements = @segments.detect { |segment| segment.respond_to?(:regexp) && segment.regexp }
|
||||
if has_requirements || @requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
|
||||
@optimise = false
|
||||
else
|
||||
@optimise = true
|
||||
end
|
||||
end
|
||||
|
||||
# Indicates whether the routes should be optimised with the string interpolation
|
||||
|
@ -22,129 +36,6 @@ module ActionController
|
|||
end.compact
|
||||
end
|
||||
|
||||
# Write and compile a +generate+ method for this Route.
|
||||
def write_generation
|
||||
# Build the main body of the generation
|
||||
body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
|
||||
|
||||
# If we have conditions that must be tested first, nest the body inside an if
|
||||
body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
|
||||
args = "options, hash, expire_on = {}"
|
||||
|
||||
# Nest the body inside of a def block, and then compile it.
|
||||
raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
|
||||
# expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
|
||||
# are the same as the keys that were recalled from the previous request. Thus,
|
||||
# we can use the expire_on.keys to determine which keys ought to be used to build
|
||||
# the query string. (Never use keys from the recalled request when building the
|
||||
# query string.)
|
||||
|
||||
method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
|
||||
method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
raw_method
|
||||
end
|
||||
|
||||
# Build several lines of code that extract values from the options hash. If any
|
||||
# of the values are missing or rejected then a return will be executed.
|
||||
def generation_extraction
|
||||
segments.collect do |segment|
|
||||
segment.extraction_code
|
||||
end.compact * "\n"
|
||||
end
|
||||
|
||||
# Produce a condition expression that will check the requirements of this route
|
||||
# upon generation.
|
||||
def generation_requirements
|
||||
requirement_conditions = requirements.collect do |key, req|
|
||||
if req.is_a? Regexp
|
||||
value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
|
||||
"hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
|
||||
else
|
||||
"hash[:#{key}] == #{req.inspect}"
|
||||
end
|
||||
end
|
||||
requirement_conditions * ' && ' unless requirement_conditions.empty?
|
||||
end
|
||||
|
||||
def generation_structure
|
||||
segments.last.string_structure segments[0..-2]
|
||||
end
|
||||
|
||||
# Write and compile a +recognize+ method for this Route.
|
||||
def write_recognition
|
||||
# Create an if structure to extract the params from a match if it occurs.
|
||||
body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
|
||||
body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
|
||||
|
||||
# Build the method declaration and compile it
|
||||
method_decl = "def recognize(path, env={})\n#{body}\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
method_decl
|
||||
end
|
||||
|
||||
# Plugins may override this method to add other conditions, like checks on
|
||||
# host, subdomain, and so forth. Note that changes here only affect route
|
||||
# recognition, not generation.
|
||||
def recognition_conditions
|
||||
result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
|
||||
result << "conditions[:method] === env[:method]" if conditions[:method]
|
||||
result
|
||||
end
|
||||
|
||||
# Build the regular expression pattern that will match this route.
|
||||
def recognition_pattern(wrap = true)
|
||||
pattern = ''
|
||||
segments.reverse_each do |segment|
|
||||
pattern = segment.build_pattern pattern
|
||||
end
|
||||
wrap ? ("\\A" + pattern + "\\Z") : pattern
|
||||
end
|
||||
|
||||
# Write the code to extract the parameters from a matched route.
|
||||
def recognition_extraction
|
||||
next_capture = 1
|
||||
extraction = segments.collect do |segment|
|
||||
x = segment.match_extraction(next_capture)
|
||||
next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
|
||||
x
|
||||
end
|
||||
extraction.compact
|
||||
end
|
||||
|
||||
# Write the real generation implementation and then resend the message.
|
||||
def generate(options, hash, expire_on = {})
|
||||
write_generation
|
||||
generate options, hash, expire_on
|
||||
end
|
||||
|
||||
def generate_extras(options, hash, expire_on = {})
|
||||
write_generation
|
||||
generate_extras options, hash, expire_on
|
||||
end
|
||||
|
||||
# Generate the query string with any extra keys in the hash and append
|
||||
# it to the given path, returning the new path.
|
||||
def append_query_string(path, hash, query_keys=nil)
|
||||
return nil unless path
|
||||
query_keys ||= extra_keys(hash)
|
||||
"#{path}#{build_query_string(hash, query_keys)}"
|
||||
end
|
||||
|
||||
# Determine which keys in the given hash are "extra". Extra keys are
|
||||
# those that were not used to generate a particular route. The extra
|
||||
# keys also do not include those recalled from the prior request, nor
|
||||
# do they include any keys that were implied in the route (like a
|
||||
# <tt>:controller</tt> that is required, but not explicitly used in the
|
||||
# text of the route.)
|
||||
def extra_keys(hash, recall={})
|
||||
(hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
|
||||
end
|
||||
|
||||
# Build a query string from the keys of the given hash. If +only_keys+
|
||||
# is given (as an array), only the keys indicated will be used to build
|
||||
# the query string. The query string will correctly build array parameter
|
||||
|
@ -161,12 +52,6 @@ module ActionController
|
|||
elements.empty? ? '' : "?#{elements.sort * '&'}"
|
||||
end
|
||||
|
||||
# Write the real recognition implementation and then resend the message.
|
||||
def recognize(path, environment={})
|
||||
write_recognition
|
||||
recognize path, environment
|
||||
end
|
||||
|
||||
# A route's parameter shell contains parameter values that are not in the
|
||||
# route's path, but should be placed in the recognized hash.
|
||||
#
|
||||
|
@ -186,7 +71,7 @@ module ActionController
|
|||
# includes keys that appear inside the path, and keys that have requirements
|
||||
# placed upon them.
|
||||
def significant_keys
|
||||
@significant_keys ||= returning [] do |sk|
|
||||
@significant_keys ||= returning([]) do |sk|
|
||||
segments.each { |segment| sk << segment.key if segment.respond_to? :key }
|
||||
sk.concat requirements.keys
|
||||
sk.uniq!
|
||||
|
@ -209,12 +94,7 @@ module ActionController
|
|||
end
|
||||
|
||||
def matches_controller_and_action?(controller, action)
|
||||
unless defined? @matching_prepared
|
||||
@controller_requirement = requirement_for(:controller)
|
||||
@action_requirement = requirement_for(:action)
|
||||
@matching_prepared = true
|
||||
end
|
||||
|
||||
prepare_matching!
|
||||
(@controller_requirement.nil? || @controller_requirement === controller) &&
|
||||
(@action_requirement.nil? || @action_requirement === action)
|
||||
end
|
||||
|
@ -226,15 +106,150 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def requirement_for(key)
|
||||
return requirements[key] if requirements.key? key
|
||||
segments.each do |segment|
|
||||
return segment.regexp if segment.respond_to?(:key) && segment.key == key
|
||||
# TODO: Route should be prepared and frozen on initialize
|
||||
def freeze
|
||||
unless frozen?
|
||||
write_generation!
|
||||
write_recognition!
|
||||
prepare_matching!
|
||||
|
||||
parameter_shell
|
||||
significant_keys
|
||||
defaults
|
||||
to_s
|
||||
end
|
||||
nil
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
def requirement_for(key)
|
||||
return requirements[key] if requirements.key? key
|
||||
segments.each do |segment|
|
||||
return segment.regexp if segment.respond_to?(:key) && segment.key == key
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Write and compile a +generate+ method for this Route.
|
||||
def write_generation!
|
||||
# Build the main body of the generation
|
||||
body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
|
||||
|
||||
# If we have conditions that must be tested first, nest the body inside an if
|
||||
body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
|
||||
args = "options, hash, expire_on = {}"
|
||||
|
||||
# Nest the body inside of a def block, and then compile it.
|
||||
raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
|
||||
# expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
|
||||
# are the same as the keys that were recalled from the previous request. Thus,
|
||||
# we can use the expire_on.keys to determine which keys ought to be used to build
|
||||
# the query string. (Never use keys from the recalled request when building the
|
||||
# query string.)
|
||||
|
||||
method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
|
||||
method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
raw_method
|
||||
end
|
||||
|
||||
# Build several lines of code that extract values from the options hash. If any
|
||||
# of the values are missing or rejected then a return will be executed.
|
||||
def generation_extraction
|
||||
segments.collect do |segment|
|
||||
segment.extraction_code
|
||||
end.compact * "\n"
|
||||
end
|
||||
|
||||
# Produce a condition expression that will check the requirements of this route
|
||||
# upon generation.
|
||||
def generation_requirements
|
||||
requirement_conditions = requirements.collect do |key, req|
|
||||
if req.is_a? Regexp
|
||||
value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
|
||||
"hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
|
||||
else
|
||||
"hash[:#{key}] == #{req.inspect}"
|
||||
end
|
||||
end
|
||||
requirement_conditions * ' && ' unless requirement_conditions.empty?
|
||||
end
|
||||
|
||||
def generation_structure
|
||||
segments.last.string_structure segments[0..-2]
|
||||
end
|
||||
|
||||
# Write and compile a +recognize+ method for this Route.
|
||||
def write_recognition!
|
||||
# Create an if structure to extract the params from a match if it occurs.
|
||||
body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
|
||||
body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
|
||||
|
||||
# Build the method declaration and compile it
|
||||
method_decl = "def recognize(path, env = {})\n#{body}\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
method_decl
|
||||
end
|
||||
|
||||
# Plugins may override this method to add other conditions, like checks on
|
||||
# host, subdomain, and so forth. Note that changes here only affect route
|
||||
# recognition, not generation.
|
||||
def recognition_conditions
|
||||
result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
|
||||
result << "[conditions[:method]].flatten.include?(env[:method])" if conditions[:method]
|
||||
result
|
||||
end
|
||||
|
||||
# Build the regular expression pattern that will match this route.
|
||||
def recognition_pattern(wrap = true)
|
||||
pattern = ''
|
||||
segments.reverse_each do |segment|
|
||||
pattern = segment.build_pattern pattern
|
||||
end
|
||||
wrap ? ("\\A" + pattern + "\\Z") : pattern
|
||||
end
|
||||
|
||||
# Write the code to extract the parameters from a matched route.
|
||||
def recognition_extraction
|
||||
next_capture = 1
|
||||
extraction = segments.collect do |segment|
|
||||
x = segment.match_extraction(next_capture)
|
||||
next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
|
||||
x
|
||||
end
|
||||
extraction.compact
|
||||
end
|
||||
|
||||
# Generate the query string with any extra keys in the hash and append
|
||||
# it to the given path, returning the new path.
|
||||
def append_query_string(path, hash, query_keys = nil)
|
||||
return nil unless path
|
||||
query_keys ||= extra_keys(hash)
|
||||
"#{path}#{build_query_string(hash, query_keys)}"
|
||||
end
|
||||
|
||||
# Determine which keys in the given hash are "extra". Extra keys are
|
||||
# those that were not used to generate a particular route. The extra
|
||||
# keys also do not include those recalled from the prior request, nor
|
||||
# do they include any keys that were implied in the route (like a
|
||||
# <tt>:controller</tt> that is required, but not explicitly used in the
|
||||
# text of the route.)
|
||||
def extra_keys(hash, recall = {})
|
||||
(hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
|
||||
end
|
||||
|
||||
def prepare_matching!
|
||||
unless defined? @matching_prepared
|
||||
@controller_requirement = requirement_for(:controller)
|
||||
@action_requirement = requirement_for(:action)
|
||||
@matching_prepared = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
class RouteSet #:nodoc:
|
||||
class RouteSet #:nodoc:
|
||||
# Mapper instances are used to build routes. The object passed to the draw
|
||||
# block in config/routes.rb is a Mapper instance.
|
||||
#
|
||||
|
@ -115,7 +115,7 @@ module ActionController
|
|||
def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
|
||||
reset! if regenerate
|
||||
Array(destinations).each do |dest|
|
||||
dest.send! :include, @module
|
||||
dest.__send__(:include, @module)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -194,6 +194,8 @@ module ActionController
|
|||
def initialize
|
||||
self.routes = []
|
||||
self.named_routes = NamedRouteCollection.new
|
||||
|
||||
clear_recognize_optimized!
|
||||
end
|
||||
|
||||
# Subclasses and plugins may override this method to specify a different
|
||||
|
@ -215,7 +217,7 @@ module ActionController
|
|||
@routes_by_controller = nil
|
||||
# This will force routing/recognition_optimization.rb
|
||||
# to refresh optimisations.
|
||||
@compiled_recognize_optimized = nil
|
||||
clear_recognize_optimized!
|
||||
end
|
||||
|
||||
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
|
||||
|
@ -231,7 +233,6 @@ module ActionController
|
|||
Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
|
||||
clear!
|
||||
load_routes!
|
||||
install_helpers
|
||||
end
|
||||
|
||||
# reload! will always force a reload whereas load checks the timestamp first
|
||||
|
@ -352,7 +353,7 @@ module ActionController
|
|||
if generate_all
|
||||
# Used by caching to expire all paths for a resource
|
||||
return routes.collect do |route|
|
||||
route.send!(method, options, merged, expire_on)
|
||||
route.__send__(method, options, merged, expire_on)
|
||||
end.compact
|
||||
end
|
||||
|
||||
|
@ -360,7 +361,7 @@ module ActionController
|
|||
routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }]
|
||||
|
||||
routes.each do |route|
|
||||
results = route.send!(method, options, merged, expire_on)
|
||||
results = route.__send__(method, options, merged, expire_on)
|
||||
return results if results && (!results.is_a?(Array) || results.first)
|
||||
end
|
||||
end
|
||||
|
@ -432,4 +433,4 @@ module ActionController
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
class Object
|
||||
def to_param
|
||||
to_s
|
||||
|
|
|
@ -2,13 +2,15 @@ module ActionController
|
|||
module Routing
|
||||
class Segment #:nodoc:
|
||||
RESERVED_PCHAR = ':@&=+$,;'
|
||||
UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
|
||||
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
|
||||
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
|
||||
|
||||
# TODO: Convert :is_optional accessor to read only
|
||||
attr_accessor :is_optional
|
||||
alias_method :optional?, :is_optional
|
||||
|
||||
def initialize
|
||||
self.is_optional = false
|
||||
@is_optional = false
|
||||
end
|
||||
|
||||
def extraction_code
|
||||
|
@ -63,12 +65,14 @@ module ActionController
|
|||
end
|
||||
|
||||
class StaticSegment < Segment #:nodoc:
|
||||
attr_accessor :value, :raw
|
||||
attr_reader :value, :raw
|
||||
alias_method :raw?, :raw
|
||||
|
||||
def initialize(value = nil)
|
||||
def initialize(value = nil, options = {})
|
||||
super()
|
||||
self.value = value
|
||||
@value = value
|
||||
@raw = options[:raw] if options.key?(:raw)
|
||||
@is_optional = options[:optional] if options.key?(:optional)
|
||||
end
|
||||
|
||||
def interpolation_chunk
|
||||
|
@ -97,10 +101,8 @@ module ActionController
|
|||
end
|
||||
|
||||
class DividerSegment < StaticSegment #:nodoc:
|
||||
def initialize(value = nil)
|
||||
super(value)
|
||||
self.raw = true
|
||||
self.is_optional = true
|
||||
def initialize(value = nil, options = {})
|
||||
super(value, {:raw => true, :optional => true}.merge(options))
|
||||
end
|
||||
|
||||
def optionality_implied?
|
||||
|
@ -109,13 +111,17 @@ module ActionController
|
|||
end
|
||||
|
||||
class DynamicSegment < Segment #:nodoc:
|
||||
attr_accessor :key, :default, :regexp
|
||||
attr_reader :key
|
||||
|
||||
# TODO: Convert these accessors to read only
|
||||
attr_accessor :default, :regexp
|
||||
|
||||
def initialize(key = nil, options = {})
|
||||
super()
|
||||
self.key = key
|
||||
self.default = options[:default] if options.key? :default
|
||||
self.is_optional = true if options[:optional] || options.key?(:default)
|
||||
@key = key
|
||||
@default = options[:default] if options.key?(:default)
|
||||
@regexp = options[:regexp] if options.key?(:regexp)
|
||||
@is_optional = true if options[:optional] || options.key?(:default)
|
||||
end
|
||||
|
||||
def to_s
|
||||
|
@ -130,6 +136,7 @@ module ActionController
|
|||
def extract_value
|
||||
"#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
|
||||
end
|
||||
|
||||
def value_check
|
||||
if default # Then we know it won't be nil
|
||||
"#{value_regexp.inspect} =~ #{local_name}" if regexp
|
||||
|
@ -141,6 +148,7 @@ module ActionController
|
|||
"#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
|
||||
end
|
||||
end
|
||||
|
||||
def expiry_statement
|
||||
"expired, hash = true, options if !expired && expire_on[:#{key}]"
|
||||
end
|
||||
|
@ -152,7 +160,7 @@ module ActionController
|
|||
s << "\n#{expiry_statement}"
|
||||
end
|
||||
|
||||
def interpolation_chunk(value_code = "#{local_name}")
|
||||
def interpolation_chunk(value_code = local_name)
|
||||
"\#{CGI.escape(#{value_code}.to_s)}"
|
||||
end
|
||||
|
||||
|
@ -175,7 +183,7 @@ module ActionController
|
|||
end
|
||||
|
||||
def regexp_chunk
|
||||
if regexp
|
||||
if regexp
|
||||
if regexp_has_modifiers?
|
||||
"(#{regexp.to_s})"
|
||||
else
|
||||
|
@ -214,7 +222,6 @@ module ActionController
|
|||
def regexp_has_modifiers?
|
||||
regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ControllerSegment < DynamicSegment #:nodoc:
|
||||
|
@ -224,9 +231,10 @@ module ActionController
|
|||
end
|
||||
|
||||
# Don't URI.escape the controller name since it may contain slashes.
|
||||
def interpolation_chunk(value_code = "#{local_name}")
|
||||
def interpolation_chunk(value_code = local_name)
|
||||
"\#{#{value_code}.to_s}"
|
||||
end
|
||||
|
||||
# Make sure controller names like Admin/Content are correctly normalized to
|
||||
# admin/content
|
||||
def extract_value
|
||||
|
@ -243,12 +251,12 @@ module ActionController
|
|||
end
|
||||
|
||||
class PathSegment < DynamicSegment #:nodoc:
|
||||
def interpolation_chunk(value_code = "#{local_name}")
|
||||
def interpolation_chunk(value_code = local_name)
|
||||
"\#{#{value_code}}"
|
||||
end
|
||||
|
||||
def extract_value
|
||||
"#{local_name} = hash[:#{key}] && Array(hash[:#{key}]).collect { |path_component| CGI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}"
|
||||
"#{local_name} = hash[:#{key}] && Array(hash[:#{key}]).collect { |path_component| CGI.escape(path_component.to_param) }.to_param #{"|| #{default.inspect}" if default}"
|
||||
end
|
||||
|
||||
def default
|
||||
|
|
|
@ -70,7 +70,8 @@ class CGI::Session::CookieStore
|
|||
'path' => options['session_path'],
|
||||
'domain' => options['session_domain'],
|
||||
'expires' => options['session_expires'],
|
||||
'secure' => options['session_secure']
|
||||
'secure' => options['session_secure'],
|
||||
'http_only' => options['session_http_only']
|
||||
}
|
||||
|
||||
# Set no_hidden and no_cookies since the session id is unused and we
|
||||
|
@ -129,7 +130,7 @@ class CGI::Session::CookieStore
|
|||
private
|
||||
# Marshal a session hash into safe cookie data. Include an integrity hash.
|
||||
def marshal(session)
|
||||
data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop
|
||||
data = ActiveSupport::Base64.encode64s(Marshal.dump(session))
|
||||
"#{data}--#{generate_digest(data)}"
|
||||
end
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# This is a really simple session storage daemon, basically just a hash,
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# This is a really simple session storage daemon, basically just a hash,
|
||||
# which is enabled for DRb access.
|
||||
|
||||
|
||||
require 'drb'
|
||||
|
||||
session_hash = Hash.new
|
||||
|
@ -14,13 +14,13 @@ class <<session_hash
|
|||
super(key, value)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def [](key)
|
||||
@mutex.synchronize do
|
||||
super(key)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def delete(key)
|
||||
@mutex.synchronize do
|
||||
super(key)
|
||||
|
@ -29,4 +29,4 @@ class <<session_hash
|
|||
end
|
||||
|
||||
DRb.start_service('druby://127.0.0.1:9192', session_hash)
|
||||
DRb.thread.join
|
||||
DRb.thread.join
|
||||
|
|
|
@ -60,6 +60,10 @@ module ActionController #:nodoc:
|
|||
# # the session will only work over HTTPS, but only for the foo action
|
||||
# session :only => :foo, :session_secure => true
|
||||
#
|
||||
# # the session by default uses HttpOnly sessions for security reasons.
|
||||
# # this can be switched off.
|
||||
# session :only => :foo, :session_http_only => false
|
||||
#
|
||||
# # the session will only be disabled for 'foo', and only if it is
|
||||
# # requested as a web service
|
||||
# session :off, :only => :foo,
|
||||
|
@ -86,14 +90,14 @@ module ActionController #:nodoc:
|
|||
raise ArgumentError, "only one of either :only or :except are allowed"
|
||||
end
|
||||
|
||||
write_inheritable_array("session_options", [options])
|
||||
write_inheritable_array(:session_options, [options])
|
||||
end
|
||||
|
||||
# So we can declare session options in the Rails initializer.
|
||||
alias_method :session=, :session
|
||||
|
||||
def cached_session_options #:nodoc:
|
||||
@session_options ||= read_inheritable_attribute("session_options") || []
|
||||
@session_options ||= read_inheritable_attribute(:session_options) || []
|
||||
end
|
||||
|
||||
def session_options_for(request, action) #:nodoc:
|
||||
|
|
|
@ -12,19 +12,21 @@ module ActionController #:nodoc:
|
|||
X_SENDFILE_HEADER = 'X-Sendfile'.freeze
|
||||
|
||||
protected
|
||||
# Sends the file by streaming it 4096 bytes at a time. This way the
|
||||
# whole file doesn't need to be read into memory at once. This makes
|
||||
# it feasible to send even large files.
|
||||
# Sends the file, by default streaming it 4096 bytes at a time. This way the
|
||||
# whole file doesn't need to be read into memory at once. This makes it
|
||||
# feasible to send even large files. You can optionally turn off streaming
|
||||
# and send the whole file at once.
|
||||
#
|
||||
# Be careful to sanitize the path parameter if it coming from a web
|
||||
# Be careful to sanitize the path parameter if it is coming from a web
|
||||
# page. <tt>send_file(params[:path])</tt> allows a malicious user to
|
||||
# download any file on your server.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
||||
# Defaults to <tt>File.basename(path)</tt>.
|
||||
# * <tt>:type</tt> - specifies an HTTP content type.
|
||||
# Defaults to 'application/octet-stream'.
|
||||
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
|
||||
# * <tt>:length</tt> - used to manually override the length (in bytes) of the content that
|
||||
# is going to be sent to the client. Defaults to <tt>File.size(path)</tt>.
|
||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
||||
# Valid values are 'inline' and 'attachment' (default).
|
||||
# * <tt>:stream</tt> - whether to send the file to the user agent as it is read (+true+)
|
||||
|
@ -35,6 +37,12 @@ module ActionController #:nodoc:
|
|||
# * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
|
||||
# the URL, which is necessary for i18n filenames on certain browsers
|
||||
# (setting <tt>:filename</tt> overrides this option).
|
||||
# * <tt>:x_sendfile</tt> - uses X-Sendfile to send the file when set to +true+. This is currently
|
||||
# only available with Lighttpd/Apache2 and specific modules installed and activated. Since this
|
||||
# uses the web server to send the file, this may lower memory consumption on your server and
|
||||
# it will not block your application for further requests.
|
||||
# See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and
|
||||
# http://tn123.ath.cx/mod_xsendfile/ for details. Defaults to +false+.
|
||||
#
|
||||
# The default Content-Type and Content-Disposition headers are
|
||||
# set to download arbitrary binary files in as many browsers as
|
||||
|
@ -99,8 +107,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# Options:
|
||||
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
||||
# * <tt>:type</tt> - specifies an HTTP content type.
|
||||
# Defaults to 'application/octet-stream'.
|
||||
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
|
||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
||||
# Valid values are 'inline' and 'attachment' (default).
|
||||
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
</h1>
|
||||
<pre><%=h @exception.clean_message %></pre>
|
||||
|
||||
<%= render_file(@rescues_path + "/_trace.erb", false) %>
|
||||
<%= render(:file => @rescues_path + "/_trace.erb") %>
|
||||
|
||||
<%= render_file(@rescues_path + "/_request_and_response.erb", false) %>
|
||||
<%= render(:file => @rescues_path + "/_request_and_response.erb") %>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<% @real_exception = @exception
|
||||
@exception = @exception.original_exception || @exception %>
|
||||
<%= render_file(@rescues_path + "/_trace.erb", false) %>
|
||||
<%= render(:file => @rescues_path + "/_trace.erb") %>
|
||||
<% @exception = @real_exception %>
|
||||
|
||||
<%= render_file(@rescues_path + "/_request_and_response.erb", false) %>
|
||||
<%= render(:file => @rescues_path + "/_request_and_response.erb") %>
|
||||
|
|
|
@ -15,6 +15,65 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
# Superclass for ActionController functional tests. Functional tests allow you to
|
||||
# test a single controller action per test method. This should not be confused with
|
||||
# integration tests (see ActionController::IntegrationTest), which are more like
|
||||
# "stories" that can involve multiple controllers and mutliple actions (i.e. multiple
|
||||
# different HTTP requests).
|
||||
#
|
||||
# == Basic example
|
||||
#
|
||||
# Functional tests are written as follows:
|
||||
# 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
|
||||
# an HTTP request.
|
||||
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
|
||||
# the controller's HTTP response, the database contents, etc.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# class BooksControllerTest < ActionController::TestCase
|
||||
# def test_create
|
||||
# # Simulate a POST response with the given HTTP parameters.
|
||||
# post(:create, :book => { :title => "Love Hina" })
|
||||
#
|
||||
# # Assert that the controller tried to redirect us to
|
||||
# # the created book's URI.
|
||||
# assert_response :found
|
||||
#
|
||||
# # Assert that the controller really put the book in the database.
|
||||
# assert_not_nil Book.find_by_title("Love Hina")
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# == Special instance variables
|
||||
#
|
||||
# ActionController::TestCase will also automatically provide the following instance
|
||||
# variables for use in the tests:
|
||||
#
|
||||
# <b>@controller</b>::
|
||||
# The controller instance that will be tested.
|
||||
# <b>@request</b>::
|
||||
# An ActionController::TestRequest, representing the current HTTP
|
||||
# request. You can modify this object before sending the HTTP request. For example,
|
||||
# you might want to set some session properties before sending a GET request.
|
||||
# <b>@response</b>::
|
||||
# An ActionController::TestResponse object, representing the response
|
||||
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
|
||||
# after calling +post+. If the various assert methods are not sufficient, then you
|
||||
# may use this object to inspect the HTTP response in detail.
|
||||
#
|
||||
# (Earlier versions of Rails required each functional test to subclass
|
||||
# Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
|
||||
#
|
||||
# == Controller is automatically inferred
|
||||
#
|
||||
# ActionController::TestCase will automatically infer the controller under test
|
||||
# from the test class name. If the controller cannot be inferred from the test
|
||||
# class name, you can explicity set it with +tests+.
|
||||
#
|
||||
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
|
||||
# tests WidgetController
|
||||
# end
|
||||
class TestCase < ActiveSupport::TestCase
|
||||
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
|
||||
# (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
|
||||
|
@ -25,7 +84,7 @@ module ActionController
|
|||
module RaiseActionExceptions
|
||||
attr_accessor :exception
|
||||
|
||||
def rescue_action(e)
|
||||
def rescue_action_without_handler(e)
|
||||
self.exception = e
|
||||
|
||||
if request.remote_addr == "0.0.0.0"
|
||||
|
@ -41,6 +100,8 @@ module ActionController
|
|||
@@controller_class = nil
|
||||
|
||||
class << self
|
||||
# Sets the controller class name. Useful if the name can't be inferred from test class.
|
||||
# Expects +controller_class+ as a constant. Example: <tt>tests WidgetController</tt>.
|
||||
def tests(controller_class)
|
||||
self.controller_class = controller_class
|
||||
end
|
||||
|
@ -73,6 +134,9 @@ module ActionController
|
|||
@controller = self.class.controller_class.new
|
||||
@controller.request = @request = TestRequest.new
|
||||
@response = TestResponse.new
|
||||
|
||||
@controller.params = {}
|
||||
@controller.send(:initialize_current_url)
|
||||
end
|
||||
|
||||
# Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local
|
||||
|
@ -80,4 +144,4 @@ module ActionController
|
|||
@request.remote_addr = '208.77.188.166' # example.com
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,8 @@ require 'action_controller/test_case'
|
|||
|
||||
module ActionController #:nodoc:
|
||||
class Base
|
||||
attr_reader :assigns
|
||||
|
||||
# Process a test request called with a TestRequest object.
|
||||
def self.process_test(request)
|
||||
new.process_test(request)
|
||||
|
@ -14,7 +16,12 @@ module ActionController #:nodoc:
|
|||
|
||||
def process_with_test(*args)
|
||||
returning process_without_test(*args) do
|
||||
add_variables_to_assigns
|
||||
@assigns = {}
|
||||
(instance_variable_names - @@protected_instance_variables).each do |var|
|
||||
value = instance_variable_get(var)
|
||||
@assigns[var[1..-1]] = value
|
||||
response.template.assigns[var[1..-1]] = value if response
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -23,7 +30,7 @@ module ActionController #:nodoc:
|
|||
|
||||
class TestRequest < AbstractRequest #:nodoc:
|
||||
attr_accessor :cookies, :session_options
|
||||
attr_accessor :query_parameters, :request_parameters, :path, :session, :env
|
||||
attr_accessor :query_parameters, :request_parameters, :path, :session
|
||||
attr_accessor :host, :user_agent
|
||||
|
||||
def initialize(query_parameters = nil, request_parameters = nil, session = nil)
|
||||
|
@ -42,7 +49,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
# Wraps raw_post in a StringIO.
|
||||
def body
|
||||
def body_stream #:nodoc:
|
||||
StringIO.new(raw_post)
|
||||
end
|
||||
|
||||
|
@ -54,7 +61,7 @@ module ActionController #:nodoc:
|
|||
|
||||
def port=(number)
|
||||
@env["SERVER_PORT"] = number.to_i
|
||||
@port_as_int = nil
|
||||
port(true)
|
||||
end
|
||||
|
||||
def action=(action_name)
|
||||
|
@ -68,6 +75,8 @@ module ActionController #:nodoc:
|
|||
@env["REQUEST_URI"] = value
|
||||
@request_uri = nil
|
||||
@path = nil
|
||||
request_uri(true)
|
||||
path(true)
|
||||
end
|
||||
|
||||
def request_uri=(uri)
|
||||
|
@ -77,21 +86,26 @@ module ActionController #:nodoc:
|
|||
|
||||
def accept=(mime_types)
|
||||
@env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",")
|
||||
accepts(true)
|
||||
end
|
||||
|
||||
def if_modified_since=(last_modified)
|
||||
@env["HTTP_IF_MODIFIED_SINCE"] = last_modified
|
||||
end
|
||||
|
||||
def if_none_match=(etag)
|
||||
@env["HTTP_IF_NONE_MATCH"] = etag
|
||||
end
|
||||
|
||||
def remote_addr=(addr)
|
||||
@env['REMOTE_ADDR'] = addr
|
||||
end
|
||||
|
||||
def remote_addr
|
||||
@env['REMOTE_ADDR']
|
||||
end
|
||||
|
||||
def request_uri
|
||||
def request_uri(*args)
|
||||
@request_uri || super
|
||||
end
|
||||
|
||||
def path
|
||||
def path(*args)
|
||||
@path || super
|
||||
end
|
||||
|
||||
|
@ -113,17 +127,13 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
@parameters = nil # reset TestRequest#parameters to use the new path_parameters
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def recycle!
|
||||
self.request_parameters = {}
|
||||
self.query_parameters = {}
|
||||
self.path_parameters = {}
|
||||
@request_method, @accepts, @content_type = nil, nil, nil
|
||||
end
|
||||
|
||||
def referer
|
||||
@env["HTTP_REFERER"]
|
||||
unmemoize_all
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -135,7 +145,7 @@ module ActionController #:nodoc:
|
|||
@host = "test.host"
|
||||
@request_uri = "/"
|
||||
@user_agent = "Rails Testing"
|
||||
self.remote_addr = "0.0.0.0"
|
||||
self.remote_addr = "0.0.0.0"
|
||||
@env["SERVER_PORT"] = 80
|
||||
@env['REQUEST_METHOD'] = "GET"
|
||||
end
|
||||
|
@ -157,16 +167,16 @@ module ActionController #:nodoc:
|
|||
module TestResponseBehavior #:nodoc:
|
||||
# The response code of the request
|
||||
def response_code
|
||||
headers['Status'][0,3].to_i rescue 0
|
||||
status[0,3].to_i rescue 0
|
||||
end
|
||||
|
||||
|
||||
# Returns a String to ensure compatibility with Net::HTTPResponse
|
||||
def code
|
||||
headers['Status'].to_s.split(' ')[0]
|
||||
status.to_s.split(' ')[0]
|
||||
end
|
||||
|
||||
def message
|
||||
headers['Status'].to_s.split(' ',2)[1]
|
||||
status.to_s.split(' ',2)[1]
|
||||
end
|
||||
|
||||
# Was the response successful?
|
||||
|
@ -205,24 +215,13 @@ module ActionController #:nodoc:
|
|||
p.match(redirect_url) != nil
|
||||
end
|
||||
|
||||
# Returns the template path of the file which was used to
|
||||
# render this response (or nil)
|
||||
def rendered_file(with_controller=false)
|
||||
unless template.first_render.nil?
|
||||
unless with_controller
|
||||
template.first_render
|
||||
else
|
||||
template.first_render.split('/').last || template.first_render
|
||||
end
|
||||
end
|
||||
# Returns the template of the file which was used to
|
||||
# render this response (or nil)
|
||||
def rendered_template
|
||||
template.send(:_first_render)
|
||||
end
|
||||
|
||||
# Was this template rendered by a file?
|
||||
def rendered_with_file?
|
||||
!rendered_file.nil?
|
||||
end
|
||||
|
||||
# A shortcut to the flash. Returns an empyt hash if no session flash exists.
|
||||
# A shortcut to the flash. Returns an empty hash if no session flash exists.
|
||||
def flash
|
||||
session['flash'] || {}
|
||||
end
|
||||
|
@ -254,11 +253,11 @@ module ActionController #:nodoc:
|
|||
|
||||
# Does the specified template object exist?
|
||||
def has_template_object?(name=nil)
|
||||
!template_objects[name].nil?
|
||||
!template_objects[name].nil?
|
||||
end
|
||||
|
||||
# Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs
|
||||
#
|
||||
#
|
||||
# assert_equal ['AuthorOfNewPage'], r.cookies['author'].value
|
||||
def cookies
|
||||
headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash }
|
||||
|
@ -277,8 +276,19 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
class TestResponse < AbstractResponse #:nodoc:
|
||||
# Integration test methods such as ActionController::Integration::Session#get
|
||||
# and ActionController::Integration::Session#post return objects of class
|
||||
# TestResponse, which represent the HTTP response results of the requested
|
||||
# controller actions.
|
||||
#
|
||||
# See AbstractResponse for more information on controller response objects.
|
||||
class TestResponse < AbstractResponse
|
||||
include TestResponseBehavior
|
||||
|
||||
def recycle!
|
||||
headers.delete('ETag')
|
||||
headers.delete('Last-Modified')
|
||||
end
|
||||
end
|
||||
|
||||
class TestSession #:nodoc:
|
||||
|
@ -324,7 +334,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# Usage example, within a functional test:
|
||||
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png')
|
||||
#
|
||||
#
|
||||
# Pass a true third parameter to ensure the uploaded file is opened in binary mode (only required for Windows):
|
||||
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png', :binary)
|
||||
require 'tempfile'
|
||||
|
@ -352,13 +362,14 @@ module ActionController #:nodoc:
|
|||
alias local_path path
|
||||
|
||||
def method_missing(method_name, *args, &block) #:nodoc:
|
||||
@tempfile.send!(method_name, *args, &block)
|
||||
@tempfile.__send__(method_name, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
module TestProcess
|
||||
def self.included(base)
|
||||
# execute the request simulating a specific HTTP method and set/volley the response
|
||||
# TODO: this should be un-DRY'ed for the sake of API documentation.
|
||||
%w( get post put delete head ).each do |method|
|
||||
base.class_eval <<-EOV, __FILE__, __LINE__
|
||||
def #{method}(action, parameters = nil, session = nil, flash = nil)
|
||||
|
@ -380,6 +391,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
@request.recycle!
|
||||
@response.recycle!
|
||||
|
||||
@html_document = nil
|
||||
@request.env['REQUEST_METHOD'] ||= "GET"
|
||||
|
@ -397,32 +409,21 @@ module ActionController #:nodoc:
|
|||
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
|
||||
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
|
||||
@request.env['HTTP_ACCEPT'] = 'text/javascript, text/html, application/xml, text/xml, */*'
|
||||
returning send!(request_method, action, parameters, session, flash) do
|
||||
returning __send__(request_method, action, parameters, session, flash) do
|
||||
@request.env.delete 'HTTP_X_REQUESTED_WITH'
|
||||
@request.env.delete 'HTTP_ACCEPT'
|
||||
end
|
||||
end
|
||||
alias xhr :xml_http_request
|
||||
|
||||
def follow_redirect
|
||||
redirected_controller = @response.redirected_to[:controller]
|
||||
if redirected_controller && redirected_controller != @controller.controller_name
|
||||
raise "Can't follow redirects outside of current controller (from #{@controller.controller_name} to #{redirected_controller})"
|
||||
def assigns(key = nil)
|
||||
if key.nil?
|
||||
@response.template.assigns
|
||||
else
|
||||
@response.template.assigns[key.to_s]
|
||||
end
|
||||
|
||||
get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys)
|
||||
end
|
||||
|
||||
deprecate :follow_redirect => "If you wish to follow redirects, you should use integration tests"
|
||||
|
||||
def assigns(key = nil)
|
||||
if key.nil?
|
||||
@response.template.assigns
|
||||
else
|
||||
@response.template.assigns[key.to_s]
|
||||
end
|
||||
end
|
||||
|
||||
def session
|
||||
@response.session
|
||||
end
|
||||
|
@ -441,7 +442,7 @@ module ActionController #:nodoc:
|
|||
|
||||
def build_request_uri(action, parameters)
|
||||
unless @request.env['REQUEST_URI']
|
||||
options = @controller.send!(:rewrite_options, parameters)
|
||||
options = @controller.__send__(:rewrite_options, parameters)
|
||||
options.update(:only_path => true, :action => action)
|
||||
|
||||
url = ActionController::UrlRewriter.new(@request, parameters)
|
||||
|
@ -463,10 +464,13 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def method_missing(selector, *args)
|
||||
return @controller.send!(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
|
||||
return super
|
||||
if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
|
||||
@controller.send(selector, *args)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Shortcut for <tt>ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type)</tt>:
|
||||
#
|
||||
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
|
||||
|
@ -477,7 +481,7 @@ module ActionController #:nodoc:
|
|||
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
|
||||
def fixture_file_upload(path, mime_type = nil, binary = false)
|
||||
ActionController::TestUploadedFile.new(
|
||||
Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path,
|
||||
Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path,
|
||||
mime_type,
|
||||
binary
|
||||
)
|
||||
|
@ -485,7 +489,7 @@ module ActionController #:nodoc:
|
|||
|
||||
# A helper to make it easier to test different route configurations.
|
||||
# This method temporarily replaces ActionController::Routing::Routes
|
||||
# with a new RouteSet instance.
|
||||
# with a new RouteSet instance.
|
||||
#
|
||||
# The new instance is yielded to the passed block. Typically the block
|
||||
# will create some routes using <tt>map.draw { map.connect ... }</tt>:
|
||||
|
|
13
vendor/rails/actionpack/lib/action_controller/translation.rb
vendored
Normal file
13
vendor/rails/actionpack/lib/action_controller/translation.rb
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
module ActionController
|
||||
module Translation
|
||||
def translate(*args)
|
||||
I18n.translate *args
|
||||
end
|
||||
alias :t :translate
|
||||
|
||||
def localize(*args)
|
||||
I18n.localize *args
|
||||
end
|
||||
alias :l :localize
|
||||
end
|
||||
end
|
|
@ -1,19 +1,96 @@
|
|||
module ActionController
|
||||
# Write URLs from arbitrary places in your codebase, such as your mailers.
|
||||
# In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
|
||||
# is also possible: an URL can be generated from one of your routing definitions.
|
||||
# URL generation functionality is centralized in this module.
|
||||
#
|
||||
# Example:
|
||||
# See ActionController::Routing and ActionController::Resources for general
|
||||
# information about routing and routes.rb.
|
||||
#
|
||||
# class MyMailer
|
||||
# include ActionController::UrlWriter
|
||||
# default_url_options[:host] = 'www.basecamphq.com'
|
||||
# <b>Tip:</b> If you need to generate URLs from your models or some other place,
|
||||
# then ActionController::UrlWriter is what you're looking for. Read on for
|
||||
# an introduction.
|
||||
#
|
||||
# def signup_url(token)
|
||||
# url_for(:controller => 'signup', action => 'index', :token => token)
|
||||
# == URL generation from parameters
|
||||
#
|
||||
# As you may know, some functions - such as ActionController::Base#url_for
|
||||
# and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
|
||||
# of parameters. For example, you've probably had the chance to write code
|
||||
# like this in one of your views:
|
||||
#
|
||||
# <%= link_to('Click here', :controller => 'users',
|
||||
# :action => 'new', :message => 'Welcome!') %>
|
||||
#
|
||||
# #=> Generates a link to: /users/new?message=Welcome%21
|
||||
#
|
||||
# link_to, and all other functions that require URL generation functionality,
|
||||
# actually use ActionController::UrlWriter under the hood. And in particular,
|
||||
# they use the ActionController::UrlWriter#url_for method. One can generate
|
||||
# the same path as the above example by using the following code:
|
||||
#
|
||||
# include UrlWriter
|
||||
# url_for(:controller => 'users',
|
||||
# :action => 'new',
|
||||
# :message => 'Welcome!',
|
||||
# :only_path => true)
|
||||
# # => "/users/new?message=Welcome%21"
|
||||
#
|
||||
# Notice the <tt>:only_path => true</tt> part. This is because UrlWriter has no
|
||||
# information about the website hostname that your Rails app is serving. So if you
|
||||
# want to include the hostname as well, then you must also pass the <tt>:host</tt>
|
||||
# argument:
|
||||
#
|
||||
# include UrlWriter
|
||||
# url_for(:controller => 'users',
|
||||
# :action => 'new',
|
||||
# :message => 'Welcome!',
|
||||
# :host => 'www.example.com') # Changed this.
|
||||
# # => "http://www.example.com/users/new?message=Welcome%21"
|
||||
#
|
||||
# By default, all controllers and views have access to a special version of url_for,
|
||||
# that already knows what the current hostname is. So if you use url_for in your
|
||||
# controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
|
||||
# argument.
|
||||
#
|
||||
# For convenience reasons, mailers provide a shortcut for ActionController::UrlWriter#url_for.
|
||||
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#url_for'
|
||||
# in full. However, mailers don't have hostname information, and what's why you'll still
|
||||
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
|
||||
#
|
||||
#
|
||||
# == URL generation for named routes
|
||||
#
|
||||
# UrlWriter also allows one to access methods that have been auto-generated from
|
||||
# named routes. For example, suppose that you have a 'users' resource in your
|
||||
# <b>routes.rb</b>:
|
||||
#
|
||||
# map.resources :users
|
||||
#
|
||||
# This generates, among other things, the method <tt>users_path</tt>. By default,
|
||||
# this method is accessible from your controllers, views and mailers. If you need
|
||||
# to access this auto-generated method from other places (such as a model), then
|
||||
# you can do that in two ways.
|
||||
#
|
||||
# The first way is to include ActionController::UrlWriter in your class:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# include ActionController::UrlWriter # !!!
|
||||
#
|
||||
# def name=(value)
|
||||
# write_attribute('name', value)
|
||||
# write_attribute('base_uri', users_path) # !!!
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# In addition to providing +url_for+, named routes are also accessible after
|
||||
# including UrlWriter.
|
||||
# The second way is to access them through ActionController::UrlWriter.
|
||||
# The autogenerated named routes methods are available as class methods:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# def name=(value)
|
||||
# write_attribute('name', value)
|
||||
# path = ActionController::UrlWriter.users_path # !!!
|
||||
# write_attribute('base_uri', path) # !!!
|
||||
# end
|
||||
# end
|
||||
module UrlWriter
|
||||
# The default options for urls written by this writer. Typically a <tt>:host</tt>
|
||||
# pair is provided.
|
||||
|
@ -37,7 +114,7 @@ module ActionController
|
|||
# * <tt>:port</tt> - Optionally specify the port to connect to.
|
||||
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
|
||||
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
|
||||
# +relative_url_root+ set in ActionController::AbstractRequest.relative_url_root.
|
||||
# +relative_url_root+ set in ActionController::Base.relative_url_root.
|
||||
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
|
||||
#
|
||||
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
|
||||
|
@ -67,7 +144,7 @@ module ActionController
|
|||
[:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
|
||||
end
|
||||
trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
|
||||
url << ActionController::AbstractRequest.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor]
|
||||
generated = Routing::Routes.generate(options, {})
|
||||
url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
|
||||
|
@ -108,7 +185,7 @@ module ActionController
|
|||
end
|
||||
|
||||
path = rewrite_path(options)
|
||||
rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
|
||||
rewritten_url << "##{options[:anchor]}" if options[:anchor]
|
||||
|
||||
|
|
|
@ -150,7 +150,14 @@ module HTML #:nodoc:
|
|||
end
|
||||
|
||||
if scanner.skip(/!\[CDATA\[/)
|
||||
scanner.scan_until(/\]\]>/)
|
||||
unless scanner.skip_until(/\]\]>/)
|
||||
if strict
|
||||
raise "expected ]]> (got #{scanner.rest.inspect} for #{content})"
|
||||
else
|
||||
scanner.skip_until(/\Z/)
|
||||
end
|
||||
end
|
||||
|
||||
return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, ''))
|
||||
end
|
||||
|
||||
|
@ -265,7 +272,7 @@ module HTML #:nodoc:
|
|||
# itself.
|
||||
class CDATA < Text #:nodoc:
|
||||
def to_s
|
||||
"<![CDATA[#{super}]>"
|
||||
"<![CDATA[#{super}]]>"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ module HTML
|
|||
#
|
||||
# When using a combination of the above, the element name comes first
|
||||
# followed by identifier, class names, attributes, pseudo classes and
|
||||
# negation in any order. Do not seprate these parts with spaces!
|
||||
# negation in any order. Do not separate these parts with spaces!
|
||||
# Space separation is used for descendant selectors.
|
||||
#
|
||||
# For example:
|
||||
|
@ -158,7 +158,7 @@ module HTML
|
|||
# * <tt>:not(selector)</tt> -- Match the element only if the element does not
|
||||
# match the simple selector.
|
||||
#
|
||||
# As you can see, <tt>:nth-child<tt> pseudo class and its varient can get quite
|
||||
# As you can see, <tt>:nth-child<tt> pseudo class and its variant can get quite
|
||||
# tricky and the CSS specification doesn't do a much better job explaining it.
|
||||
# But after reading the examples and trying a few combinations, it's easy to
|
||||
# figure out.
|
||||
|
|
|
@ -80,7 +80,7 @@ module ActionController #:nodoc:
|
|||
# array (may also be a single value).
|
||||
def verify(options={})
|
||||
before_filter :only => options[:only], :except => options[:except] do |c|
|
||||
c.send! :verify_action, options
|
||||
c.__send__ :verify_action, options
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -116,7 +116,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def apply_redirect_to(redirect_to_option) # :nodoc:
|
||||
(redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.send!(redirect_to_option) : redirect_to_option
|
||||
(redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.__send__(redirect_to_option) : redirect_to_option
|
||||
end
|
||||
|
||||
def apply_remaining_actions(options) # :nodoc:
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
module ActionPack #:nodoc:
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 1
|
||||
TINY = 1
|
||||
MINOR = 2
|
||||
TINY = 0
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
|
30
vendor/rails/actionpack/lib/action_view.rb
vendored
30
vendor/rails/actionpack/lib/action_view.rb
vendored
|
@ -21,25 +21,33 @@
|
|||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#++
|
||||
|
||||
require 'action_view/template_handler'
|
||||
require 'action_view/template_handlers/compilable'
|
||||
require 'action_view/template_handlers/builder'
|
||||
require 'action_view/template_handlers/erb'
|
||||
require 'action_view/template_handlers/rjs'
|
||||
begin
|
||||
require 'active_support'
|
||||
rescue LoadError
|
||||
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
|
||||
if File.directory?(activesupport_path)
|
||||
$:.unshift activesupport_path
|
||||
require 'active_support'
|
||||
end
|
||||
end
|
||||
|
||||
require 'action_view/template_handlers'
|
||||
require 'action_view/renderable'
|
||||
require 'action_view/renderable_partial'
|
||||
|
||||
require 'action_view/template_finder'
|
||||
require 'action_view/template'
|
||||
require 'action_view/partial_template'
|
||||
require 'action_view/inline_template'
|
||||
require 'action_view/paths'
|
||||
|
||||
require 'action_view/base'
|
||||
require 'action_view/partials'
|
||||
require 'action_view/template_error'
|
||||
|
||||
I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en-US.yml"
|
||||
|
||||
require 'action_view/helpers'
|
||||
|
||||
ActionView::Base.class_eval do
|
||||
include ActionView::Partials
|
||||
|
||||
ActionView::Base.helper_modules.each do |helper_module|
|
||||
include helper_module
|
||||
end
|
||||
include ActionView::Helpers
|
||||
end
|
||||
|
|
321
vendor/rails/actionpack/lib/action_view/base.rb
vendored
321
vendor/rails/actionpack/lib/action_view/base.rb
vendored
|
@ -1,17 +1,23 @@
|
|||
module ActionView #:nodoc:
|
||||
class ActionViewError < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
|
||||
class MissingTemplate < ActionViewError #:nodoc:
|
||||
def initialize(paths, path, template_format = nil)
|
||||
full_template_path = path.include?('.') ? path : "#{path}.erb"
|
||||
display_paths = paths.join(':')
|
||||
template_type = (path =~ /layouts/i) ? 'layout' : 'template'
|
||||
super("Missing #{template_type} #{full_template_path} in view path #{display_paths}")
|
||||
end
|
||||
end
|
||||
|
||||
# Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
|
||||
# (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> (or <tt>.rxml</tt>) extension then Jim Weirich's Builder::XmlMarkup library is used.
|
||||
# Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
|
||||
# (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> (or <tt>.rxml</tt>) extension then Jim Weirich's Builder::XmlMarkup library is used.
|
||||
# If the template file has a <tt>.rjs</tt> extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.
|
||||
#
|
||||
#
|
||||
# = ERb
|
||||
#
|
||||
# You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
|
||||
#
|
||||
# You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
|
||||
# following loop for names:
|
||||
#
|
||||
# <b>Names of all the people</b>
|
||||
|
@ -51,7 +57,7 @@ module ActionView #:nodoc:
|
|||
# <title><%= @page_title %></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 } %>
|
||||
|
@ -77,8 +83,8 @@ module ActionView #:nodoc:
|
|||
#
|
||||
# == 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 <tt>.builder</tt> extension.
|
||||
# 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 <tt>.builder</tt> extension.
|
||||
#
|
||||
# Here are some basic examples:
|
||||
#
|
||||
|
@ -87,7 +93,7 @@ module ActionView #:nodoc:
|
|||
# xml.a("A Link", "href"=>"http://onestepback.org") # => <a href="http://onestepback.org">A Link</a>
|
||||
# xml.target("name"=>"compile", "option"=>"fast") # => <target option="fast" name="compile"\>
|
||||
# # 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 {
|
||||
|
@ -111,7 +117,7 @@ module ActionView #:nodoc:
|
|||
# xml.description "Basecamp: Recent items"
|
||||
# xml.language "en-us"
|
||||
# xml.ttl "40"
|
||||
#
|
||||
#
|
||||
# for item in @recent_items
|
||||
# xml.item do
|
||||
# xml.title(item_title(item))
|
||||
|
@ -119,7 +125,7 @@ module ActionView #:nodoc:
|
|||
# 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
|
||||
|
@ -130,12 +136,12 @@ module ActionView #:nodoc:
|
|||
#
|
||||
# == JavaScriptGenerator
|
||||
#
|
||||
# JavaScriptGenerator templates end in <tt>.rjs</tt>. Unlike conventional templates which are used to
|
||||
# render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to
|
||||
# modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax
|
||||
# JavaScriptGenerator templates end in <tt>.rjs</tt>. Unlike conventional templates which are used to
|
||||
# render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to
|
||||
# modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax
|
||||
# and make updates to the page where the request originated from.
|
||||
#
|
||||
# An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block.
|
||||
#
|
||||
# An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block.
|
||||
#
|
||||
# When an <tt>.rjs</tt> action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example:
|
||||
#
|
||||
|
@ -145,203 +151,220 @@ module ActionView #:nodoc:
|
|||
#
|
||||
# page.replace_html 'sidebar', :partial => 'sidebar'
|
||||
# page.remove "person-#{@person.id}"
|
||||
# page.visual_effect :highlight, 'user-list'
|
||||
# page.visual_effect :highlight, 'user-list'
|
||||
#
|
||||
# This refreshes the sidebar, removes a person element and highlights the user list.
|
||||
#
|
||||
#
|
||||
# See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details.
|
||||
class Base
|
||||
include ERB::Util
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
attr_reader :finder
|
||||
attr_accessor :base_path, :assigns, :template_extension, :first_render
|
||||
attr_accessor :base_path, :assigns, :template_extension
|
||||
attr_accessor :controller
|
||||
|
||||
|
||||
attr_writer :template_format
|
||||
attr_accessor :current_render_extension
|
||||
|
||||
# Specify trim mode for the ERB compiler. Defaults to '-'.
|
||||
# See ERb documentation for suitable values.
|
||||
@@erb_trim_mode = '-'
|
||||
cattr_accessor :erb_trim_mode
|
||||
attr_accessor :output_buffer
|
||||
|
||||
# Specify whether file modification times should be checked to see if a template needs recompilation
|
||||
@@cache_template_loading = false
|
||||
cattr_accessor :cache_template_loading
|
||||
|
||||
def self.cache_template_extensions=(*args)
|
||||
ActiveSupport::Deprecation.warn("config.action_view.cache_template_extensions option has been deprecated and has no affect. " <<
|
||||
"Please remove it from your config files.", caller)
|
||||
class << self
|
||||
delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB'
|
||||
delegate :logger, :to => 'ActionController::Base'
|
||||
end
|
||||
|
||||
# Templates that are exempt from layouts
|
||||
@@exempt_from_layout = Set.new([/\.rjs$/])
|
||||
|
||||
# Don't render layouts for templates with the given extensions.
|
||||
def self.exempt_from_layout(*extensions)
|
||||
regexps = extensions.collect do |extension|
|
||||
extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/
|
||||
end
|
||||
@@exempt_from_layout.merge(regexps)
|
||||
end
|
||||
|
||||
# Specify whether RJS responses should be wrapped in a try/catch block
|
||||
# that alert()s the caught exception (and then re-raises it).
|
||||
# that alert()s the caught exception (and then re-raises it).
|
||||
@@debug_rjs = false
|
||||
cattr_accessor :debug_rjs
|
||||
|
||||
@@erb_variable = '_erbout'
|
||||
cattr_accessor :erb_variable
|
||||
class << self
|
||||
deprecate :erb_variable= => 'The erb variable will no longer be configurable. Use the concat helper method instead of appending to it directly.'
|
||||
end
|
||||
# A warning will be displayed whenever an action results in a cache miss on your view paths.
|
||||
@@warn_cache_misses = false
|
||||
cattr_accessor :warn_cache_misses
|
||||
|
||||
attr_internal :request
|
||||
|
||||
delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
|
||||
:flash, :logger, :action_name, :to => :controller
|
||||
|
||||
:flash, :logger, :action_name, :controller_name, :to => :controller
|
||||
|
||||
module CompiledTemplates #:nodoc:
|
||||
# holds compiled template code
|
||||
end
|
||||
include CompiledTemplates
|
||||
|
||||
# Maps inline templates to their method names
|
||||
cattr_accessor :method_names
|
||||
@@method_names = {}
|
||||
# Map method names to the names passed in local assigns so far
|
||||
@@template_args = {}
|
||||
|
||||
# Cache public asset paths
|
||||
cattr_reader :computed_public_paths
|
||||
@@computed_public_paths = {}
|
||||
|
||||
class ObjectWrapper < Struct.new(:value) #:nodoc:
|
||||
def self.process_view_paths(value)
|
||||
ActionView::PathSet.new(Array(value))
|
||||
end
|
||||
|
||||
def self.helper_modules #:nodoc:
|
||||
helpers = []
|
||||
Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file|
|
||||
next unless file =~ /^([a-z][a-z_]*_helper).rb$/
|
||||
require "action_view/helpers/#{$1}"
|
||||
helper_module_name = $1.camelize
|
||||
if Helpers.const_defined?(helper_module_name)
|
||||
helpers << Helpers.const_get(helper_module_name)
|
||||
end
|
||||
attr_reader :helpers
|
||||
|
||||
class ProxyModule < Module
|
||||
def initialize(receiver)
|
||||
@receiver = receiver
|
||||
end
|
||||
|
||||
def include(*args)
|
||||
super(*args)
|
||||
@receiver.extend(*args)
|
||||
end
|
||||
return helpers
|
||||
end
|
||||
|
||||
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc:
|
||||
@assigns = assigns_for_first_render
|
||||
@assigns_added = nil
|
||||
@controller = controller
|
||||
@finder = TemplateFinder.new(self, view_paths)
|
||||
@helpers = ProxyModule.new(self)
|
||||
self.view_paths = view_paths
|
||||
end
|
||||
|
||||
# Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true,
|
||||
# it's relative to the view_paths array, otherwise it's absolute. The hash in <tt>local_assigns</tt>
|
||||
# is made available as local variables.
|
||||
def render_file(template_path, use_full_path = true, local_assigns = {}) #:nodoc:
|
||||
if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/")
|
||||
raise ActionViewError, <<-END_ERROR
|
||||
Due to changes in ActionMailer, you need to provide the mailer_name along with the template name.
|
||||
attr_reader :view_paths
|
||||
|
||||
render "user_mailer/signup"
|
||||
render :file => "user_mailer/signup"
|
||||
|
||||
If you are rendering a subtemplate, you must now use controller-like partial syntax:
|
||||
|
||||
render :partial => 'signup' # no mailer_name necessary
|
||||
END_ERROR
|
||||
end
|
||||
|
||||
Template.new(self, template_path, use_full_path, local_assigns).render_template
|
||||
def view_paths=(paths)
|
||||
@view_paths = self.class.process_view_paths(paths)
|
||||
end
|
||||
|
||||
# Renders the template present at <tt>template_path</tt> (relative to the view_paths array).
|
||||
|
||||
# Renders the template present at <tt>template_path</tt> (relative to the view_paths array).
|
||||
# The hash in <tt>local_assigns</tt> is made available as local variables.
|
||||
def render(options = {}, local_assigns = {}, &block) #:nodoc:
|
||||
local_assigns ||= {}
|
||||
|
||||
if options.is_a?(String)
|
||||
render_file(options, true, local_assigns)
|
||||
render(:file => options, :locals => local_assigns)
|
||||
elsif options == :update
|
||||
update_page(&block)
|
||||
elsif options.is_a?(Hash)
|
||||
use_full_path = options[:use_full_path]
|
||||
options = options.reverse_merge(:locals => {}, :use_full_path => true)
|
||||
|
||||
if partial_layout = options.delete(:layout)
|
||||
if block_given?
|
||||
wrap_content_for_layout capture(&block) do
|
||||
concat(render(options.merge(:partial => partial_layout)), block.binding)
|
||||
end
|
||||
else
|
||||
wrap_content_for_layout render(options) do
|
||||
render(options.merge(:partial => partial_layout))
|
||||
end
|
||||
end
|
||||
options = options.reverse_merge(:locals => {})
|
||||
if options[:layout]
|
||||
_render_with_layout(options, local_assigns, &block)
|
||||
elsif options[:file]
|
||||
render_file(options[:file], use_full_path || false, options[:locals])
|
||||
elsif options[:partial] && options[:collection]
|
||||
render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals])
|
||||
_pick_template(options[:file]).render_template(self, options[:locals])
|
||||
elsif options[:partial]
|
||||
render_partial(options[:partial], ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals])
|
||||
render_partial(options)
|
||||
elsif options[:inline]
|
||||
template = InlineTemplate.new(self, options[:inline], options[:locals], options[:type])
|
||||
render_template(template)
|
||||
InlineTemplate.new(options[:inline], options[:type]).render(self, options[:locals])
|
||||
elsif options[:text]
|
||||
options[:text]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def render_template(template) #:nodoc:
|
||||
template.render_template
|
||||
end
|
||||
|
||||
# Returns true is the file may be rendered implicitly.
|
||||
def file_public?(template_path)#:nodoc:
|
||||
template_path.split('/').last[0,1] != '_'
|
||||
end
|
||||
|
||||
# Returns a symbolized version of the <tt>:format</tt> parameter of the request,
|
||||
# or <tt>:html</tt> by default.
|
||||
#
|
||||
# EXCEPTION: If the <tt>:format</tt> parameter is not set, the Accept header will be examined for
|
||||
# whether it contains the JavaScript mime type as its first priority. If that's the case,
|
||||
# it will be used. This ensures that Ajax applications can use the same URL to support both
|
||||
# JavaScript and non-JavaScript users.
|
||||
# The format to be used when choosing between multiple templates with
|
||||
# the same name but differing formats. See +Request#template_format+
|
||||
# for more details.
|
||||
def template_format
|
||||
return @template_format if @template_format
|
||||
|
||||
if controller && controller.respond_to?(:request)
|
||||
parameter_format = controller.request.parameters[:format]
|
||||
accept_format = controller.request.accepts.first
|
||||
|
||||
case
|
||||
when parameter_format.blank? && accept_format != :js
|
||||
@template_format = :html
|
||||
when parameter_format.blank? && accept_format == :js
|
||||
@template_format = :js
|
||||
else
|
||||
@template_format = parameter_format.to_sym
|
||||
end
|
||||
if defined? @template_format
|
||||
@template_format
|
||||
elsif controller && controller.respond_to?(:request)
|
||||
@template_format = controller.request.template_format
|
||||
else
|
||||
@template_format = :html
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def wrap_content_for_layout(content)
|
||||
original_content_for_layout = @content_for_layout
|
||||
@content_for_layout = content
|
||||
returning(yield) { @content_for_layout = original_content_for_layout }
|
||||
end
|
||||
attr_accessor :_first_render, :_last_render
|
||||
|
||||
# Evaluate the local assigns and pushes them to the view.
|
||||
def evaluate_assigns
|
||||
# Evaluates the local assigns and controller ivars, pushes them to the view.
|
||||
def _evaluate_assigns_and_ivars #:nodoc:
|
||||
unless @assigns_added
|
||||
assign_variables_from_controller
|
||||
@assigns.each { |key, value| instance_variable_set("@#{key}", value) }
|
||||
_copy_ivars_from_controller
|
||||
@assigns_added = true
|
||||
end
|
||||
end
|
||||
|
||||
# Assigns instance variables from the controller to the view.
|
||||
def assign_variables_from_controller
|
||||
@assigns.each { |key, value| instance_variable_set("@#{key}", value) }
|
||||
def _copy_ivars_from_controller #:nodoc:
|
||||
if @controller
|
||||
variables = @controller.instance_variable_names
|
||||
variables -= @controller.protected_instance_variables if @controller.respond_to?(:protected_instance_variables)
|
||||
variables.each { |name| instance_variable_set(name, @controller.instance_variable_get(name)) }
|
||||
end
|
||||
end
|
||||
|
||||
def execute(template)
|
||||
send(template.method, template.locals) do |*names|
|
||||
instance_variable_get "@content_for_#{names.first || 'layout'}"
|
||||
end
|
||||
|
||||
def _set_controller_content_type(content_type) #:nodoc:
|
||||
if controller.respond_to?(:response)
|
||||
controller.response.content_type ||= content_type
|
||||
end
|
||||
end
|
||||
|
||||
def _pick_template(template_path)
|
||||
return template_path if template_path.respond_to?(:render)
|
||||
|
||||
path = template_path.sub(/^\//, '')
|
||||
if m = path.match(/(.*)\.(\w+)$/)
|
||||
template_file_name, template_file_extension = m[1], m[2]
|
||||
else
|
||||
template_file_name = path
|
||||
end
|
||||
|
||||
# OPTIMIZE: Checks to lookup template in view path
|
||||
if template = self.view_paths["#{template_file_name}.#{template_format}"]
|
||||
template
|
||||
elsif template = self.view_paths[template_file_name]
|
||||
template
|
||||
elsif _first_render && template = self.view_paths["#{template_file_name}.#{_first_render.format_and_extension}"]
|
||||
template
|
||||
elsif template_format == :js && template = self.view_paths["#{template_file_name}.html"]
|
||||
@template_format = :html
|
||||
template
|
||||
else
|
||||
template = Template.new(template_path, view_paths)
|
||||
|
||||
if self.class.warn_cache_misses && logger
|
||||
logger.debug "[PERFORMANCE] Rendering a template that was " +
|
||||
"not found in view path. Templates outside the view path are " +
|
||||
"not cached and result in expensive disk operations. Move this " +
|
||||
"file into #{view_paths.join(':')} or add the folder to your " +
|
||||
"view path list"
|
||||
end
|
||||
|
||||
template
|
||||
end
|
||||
end
|
||||
memoize :_pick_template
|
||||
|
||||
def _exempt_from_layout?(template_path) #:nodoc:
|
||||
template = _pick_template(template_path).to_s
|
||||
@@exempt_from_layout.any? { |ext| template =~ ext }
|
||||
rescue ActionView::MissingTemplate
|
||||
return false
|
||||
end
|
||||
|
||||
def _render_with_layout(options, local_assigns, &block) #:nodoc:
|
||||
partial_layout = options.delete(:layout)
|
||||
|
||||
if block_given?
|
||||
begin
|
||||
@_proc_for_layout = block
|
||||
concat(render(options.merge(:partial => partial_layout)))
|
||||
ensure
|
||||
@_proc_for_layout = nil
|
||||
end
|
||||
else
|
||||
begin
|
||||
original_content_for_layout = @content_for_layout if defined?(@content_for_layout)
|
||||
@content_for_layout = render(options)
|
||||
|
||||
if (options[:inline] || options[:file] || options[:text])
|
||||
@cached_content_for_layout = @content_for_layout
|
||||
render(:file => partial_layout, :locals => local_assigns)
|
||||
else
|
||||
render(options.merge(:partial => partial_layout))
|
||||
end
|
||||
ensure
|
||||
@content_for_layout = original_content_for_layout
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
38
vendor/rails/actionpack/lib/action_view/helpers.rb
vendored
Normal file
38
vendor/rails/actionpack/lib/action_view/helpers.rb
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file|
|
||||
next unless file =~ /^([a-z][a-z_]*_helper).rb$/
|
||||
require "action_view/helpers/#{$1}"
|
||||
end
|
||||
|
||||
module ActionView #:nodoc:
|
||||
module Helpers #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
include SanitizeHelper::ClassMethods
|
||||
end
|
||||
|
||||
include ActiveRecordHelper
|
||||
include AssetTagHelper
|
||||
include AtomFeedHelper
|
||||
include BenchmarkHelper
|
||||
include CacheHelper
|
||||
include CaptureHelper
|
||||
include DateHelper
|
||||
include DebugHelper
|
||||
include FormHelper
|
||||
include FormOptionsHelper
|
||||
include FormTagHelper
|
||||
include NumberHelper
|
||||
include PrototypeHelper
|
||||
include RecordIdentificationHelper
|
||||
include RecordTagHelper
|
||||
include SanitizeHelper
|
||||
include ScriptaculousHelper
|
||||
include TagHelper
|
||||
include TextHelper
|
||||
include TranslationHelper
|
||||
include UrlHelper
|
||||
end
|
||||
end
|
|
@ -25,7 +25,7 @@ module ActionView
|
|||
# Returns an entire form with all needed input tags for a specified Active Record object. For example, if <tt>@post</tt>
|
||||
# has attributes named +title+ of type +VARCHAR+ and +body+ of type +TEXT+ then
|
||||
#
|
||||
# form("post")
|
||||
# form("post")
|
||||
#
|
||||
# would yield a form like the following (modulus formatting):
|
||||
#
|
||||
|
@ -90,23 +90,41 @@ module ActionView
|
|||
end
|
||||
|
||||
# Returns a string containing the error message attached to the +method+ on the +object+ if one exists.
|
||||
# This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a +prepend_text+ and/or +append_text+
|
||||
# (to properly explain the error), and a +css_class+ to style it accordingly. +object+ should either be the name of an instance variable or
|
||||
# the actual object. As an example, let's say you have a model <tt>@post</tt> that has an error message on the +title+ attribute:
|
||||
# This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a <tt>:prepend_text</tt>
|
||||
# and/or <tt>:append_text</tt> (to properly explain the error), and a <tt>:css_class</tt> to style it
|
||||
# accordingly. +object+ should either be the name of an instance variable or the actual object. The method can be
|
||||
# passed in either as a string or a symbol.
|
||||
# As an example, let's say you have a model <tt>@post</tt> that has an error message on the +title+ attribute:
|
||||
#
|
||||
# <%= error_message_on "post", "title" %>
|
||||
# # => <div class="formError">can't be empty</div>
|
||||
#
|
||||
# <%= error_message_on @post, "title" %>
|
||||
# <%= error_message_on @post, :title %>
|
||||
# # => <div class="formError">can't be empty</div>
|
||||
#
|
||||
# <%= error_message_on "post", "title", "Title simply ", " (or it won't work).", "inputError" %>
|
||||
# # => <div class="inputError">Title simply can't be empty (or it won't work).</div>
|
||||
def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError")
|
||||
# <%= error_message_on "post", "title",
|
||||
# :prepend_text => "Title simply ",
|
||||
# :append_text => " (or it won't work).",
|
||||
# :css_class => "inputError" %>
|
||||
def error_message_on(object, method, *args)
|
||||
options = args.extract_options!
|
||||
unless args.empty?
|
||||
ActiveSupport::Deprecation.warn('error_message_on takes an option hash instead of separate ' +
|
||||
'prepend_text, append_text, and css_class arguments', caller)
|
||||
|
||||
options[:prepend_text] = args[0] || ''
|
||||
options[:append_text] = args[1] || ''
|
||||
options[:css_class] = args[2] || 'formError'
|
||||
end
|
||||
options.reverse_merge!(:prepend_text => '', :append_text => '', :css_class => 'formError')
|
||||
|
||||
if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
|
||||
(errors = obj.errors.on(method))
|
||||
content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class)
|
||||
else
|
||||
content_tag("div",
|
||||
"#{options[:prepend_text]}#{errors.is_a?(Array) ? errors.first : errors}#{options[:append_text]}",
|
||||
:class => options[:css_class]
|
||||
)
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
@ -133,7 +151,7 @@ module ActionView
|
|||
#
|
||||
# To specify the display for one object, you simply provide its name as a parameter.
|
||||
# For example, for the <tt>@user</tt> model:
|
||||
#
|
||||
#
|
||||
# error_messages_for 'user'
|
||||
#
|
||||
# To specify more than one object, you simply list them; optionally, you can add an extra <tt>:object_name</tt> parameter, which
|
||||
|
@ -141,7 +159,7 @@ module ActionView
|
|||
#
|
||||
# error_messages_for 'user_common', 'user', :object_name => 'user'
|
||||
#
|
||||
# If the objects cannot be located as instance variables, you can add an extra <tt>:object</tt> paremeter which gives the actual
|
||||
# If the objects cannot be located as instance variables, you can add an extra <tt>:object</tt> parameter which gives the actual
|
||||
# object (or array of objects to use):
|
||||
#
|
||||
# error_messages_for 'user', :object => @question.user
|
||||
|
@ -151,12 +169,14 @@ module ActionView
|
|||
# instance yourself and set it up. View the source of this method to see how easy it is.
|
||||
def error_messages_for(*params)
|
||||
options = params.extract_options!.symbolize_keys
|
||||
|
||||
if object = options.delete(:object)
|
||||
objects = [object].flatten
|
||||
else
|
||||
objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
|
||||
end
|
||||
count = objects.inject(0) {|sum, object| sum + object.errors.count }
|
||||
|
||||
count = objects.inject(0) {|sum, object| sum + object.errors.count }
|
||||
unless count.zero?
|
||||
html = {}
|
||||
[:id, :class].each do |key|
|
||||
|
@ -168,16 +188,25 @@ module ActionView
|
|||
end
|
||||
end
|
||||
options[:object_name] ||= params.first
|
||||
options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message)
|
||||
options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message)
|
||||
error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join
|
||||
|
||||
contents = ''
|
||||
contents << content_tag(options[:header_tag] || :h2, options[:header_message]) unless options[:header_message].blank?
|
||||
contents << content_tag(:p, options[:message]) unless options[:message].blank?
|
||||
contents << content_tag(:ul, error_messages)
|
||||
I18n.with_options :locale => options[:locale], :scope => [:activerecord, :errors, :template] do |locale|
|
||||
header_message = if options.include?(:header_message)
|
||||
options[:header_message]
|
||||
else
|
||||
object_name = options[:object_name].to_s.gsub('_', ' ')
|
||||
object_name = I18n.t(object_name, :default => object_name, :scope => [:activerecord, :models], :count => 1)
|
||||
locale.t :header, :count => count, :model => object_name
|
||||
end
|
||||
message = options.include?(:message) ? options[:message] : locale.t(:body)
|
||||
error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join
|
||||
|
||||
content_tag(:div, contents, html)
|
||||
contents = ''
|
||||
contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
|
||||
contents << content_tag(:p, message) unless message.blank?
|
||||
contents << content_tag(:ul, error_messages)
|
||||
|
||||
content_tag(:div, contents, html)
|
||||
end
|
||||
else
|
||||
''
|
||||
end
|
||||
|
@ -217,7 +246,7 @@ module ActionView
|
|||
|
||||
alias_method :tag_without_error_wrapping, :tag
|
||||
def tag(name, options)
|
||||
if object.respond_to?("errors") && object.errors.respond_to?("on")
|
||||
if object.respond_to?(:errors) && object.errors.respond_to?(:on)
|
||||
error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name))
|
||||
else
|
||||
tag_without_error_wrapping(name, options)
|
||||
|
@ -226,7 +255,7 @@ module ActionView
|
|||
|
||||
alias_method :content_tag_without_error_wrapping, :content_tag
|
||||
def content_tag(name, value, options)
|
||||
if object.respond_to?("errors") && object.errors.respond_to?("on")
|
||||
if object.respond_to?(:errors) && object.errors.respond_to?(:on)
|
||||
error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name))
|
||||
else
|
||||
content_tag_without_error_wrapping(name, value, options)
|
||||
|
@ -235,7 +264,7 @@ module ActionView
|
|||
|
||||
alias_method :to_date_select_tag_without_error_wrapping, :to_date_select_tag
|
||||
def to_date_select_tag(options = {}, html_options = {})
|
||||
if object.respond_to?("errors") && object.errors.respond_to?("on")
|
||||
if object.respond_to?(:errors) && object.errors.respond_to?(:on)
|
||||
error_wrapping(to_date_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name))
|
||||
else
|
||||
to_date_select_tag_without_error_wrapping(options, html_options)
|
||||
|
@ -244,7 +273,7 @@ module ActionView
|
|||
|
||||
alias_method :to_datetime_select_tag_without_error_wrapping, :to_datetime_select_tag
|
||||
def to_datetime_select_tag(options = {}, html_options = {})
|
||||
if object.respond_to?("errors") && object.errors.respond_to?("on")
|
||||
if object.respond_to?(:errors) && object.errors.respond_to?(:on)
|
||||
error_wrapping(to_datetime_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name))
|
||||
else
|
||||
to_datetime_select_tag_without_error_wrapping(options, html_options)
|
||||
|
@ -253,7 +282,7 @@ module ActionView
|
|||
|
||||
alias_method :to_time_select_tag_without_error_wrapping, :to_time_select_tag
|
||||
def to_time_select_tag(options = {}, html_options = {})
|
||||
if object.respond_to?("errors") && object.errors.respond_to?("on")
|
||||
if object.respond_to?(:errors) && object.errors.respond_to?(:on)
|
||||
error_wrapping(to_time_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name))
|
||||
else
|
||||
to_time_select_tag_without_error_wrapping(options, html_options)
|
||||
|
@ -269,7 +298,7 @@ module ActionView
|
|||
end
|
||||
|
||||
def column_type
|
||||
object.send("column_for_attribute", @method_name).type
|
||||
object.send(:column_for_attribute, @method_name).type
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,12 +5,12 @@ require 'action_view/helpers/tag_helper'
|
|||
module ActionView
|
||||
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.
|
||||
# as images, javascripts, stylesheets, and feeds. These methods do not verify
|
||||
# the assets exist before linking to them.
|
||||
#
|
||||
# === 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 assets server by
|
||||
# folder, but you can direct Rails to link to assets from a dedicated assets server by
|
||||
# setting ActionController::Base.asset_host in your <tt>config/environment.rb</tt>. For example,
|
||||
# let's say your asset host is <tt>assets.example.com</tt>.
|
||||
#
|
||||
|
@ -22,16 +22,16 @@ module ActionView
|
|||
#
|
||||
# This is useful since browsers typically open at most two connections to a single host,
|
||||
# which means your assets often wait in single file for their turn to load. You can
|
||||
# alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
|
||||
# alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
|
||||
# to automatically distribute asset requests among four hosts (e.g., "assets0.example.com" through "assets3.example.com")
|
||||
# so browsers will open eight connections rather than two.
|
||||
# so browsers will open eight connections rather than two.
|
||||
#
|
||||
# image_tag("rails.png")
|
||||
# => <img src="http://assets0.example.com/images/rails.png" alt="Rails" />
|
||||
# stylesheet_link_tag("application")
|
||||
# => <link href="http://assets3.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME
|
||||
# To do this, you can either setup 4 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.
|
||||
#
|
||||
|
@ -86,7 +86,7 @@ module ActionView
|
|||
# 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
|
||||
# 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
|
||||
|
@ -95,16 +95,17 @@ module ActionView
|
|||
# ExpiresDefault "access plus 1 year"
|
||||
# </FilesMatch>
|
||||
#
|
||||
# Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
|
||||
# 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 drift out of sync, you'll see different timestamps at random and the cache won't
|
||||
# work. Which means that 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 (and that the assets are not being
|
||||
# something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
|
||||
# requested over and over).
|
||||
module AssetTagHelper
|
||||
ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public"
|
||||
JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts"
|
||||
STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets"
|
||||
|
||||
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
|
||||
|
||||
# Returns a link tag that browsers and news readers can use to auto-detect
|
||||
# an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
|
||||
# <tt>:atom</tt>. Control the link options in url_for format using the
|
||||
|
@ -150,14 +151,10 @@ module ActionView
|
|||
# javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr.js
|
||||
# javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
|
||||
def javascript_path(source)
|
||||
compute_public_path(source, 'javascripts', 'js')
|
||||
JavaScriptTag.create(self, @controller, source).public_path
|
||||
end
|
||||
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
|
||||
|
||||
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
|
||||
@@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
|
||||
@@stylesheet_expansions = {}
|
||||
|
||||
# Returns an html script tag for each of the +sources+ provided. You
|
||||
# can pass in the filename (.js extension is optional) of javascript files
|
||||
# that exist in your public/javascripts directory for inclusion into the
|
||||
|
@ -193,7 +190,7 @@ module ActionView
|
|||
#
|
||||
# * = The application.js file is only referenced if it exists
|
||||
#
|
||||
# Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
|
||||
# Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
|
||||
# (e.g., you're going to be using a certain .js file in every action), then take a look at the register_javascript_include_default method.
|
||||
#
|
||||
# You can also include all javascripts in the javascripts directory using <tt>:all</tt> as the source:
|
||||
|
@ -209,12 +206,16 @@ module ActionView
|
|||
# Note that the default javascript files will be included first. So Prototype and Scriptaculous are available to
|
||||
# all subsequently included files.
|
||||
#
|
||||
# If you want Rails to search in all the subdirectories under javascripts, you should explicitly set <tt>:recursive</tt>:
|
||||
#
|
||||
# javascript_include_tag :all, :recursive => true
|
||||
#
|
||||
# == Caching multiple javascripts into one
|
||||
#
|
||||
# You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
|
||||
# compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
|
||||
# is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development
|
||||
# environment).
|
||||
# environment).
|
||||
#
|
||||
# ==== Examples
|
||||
# javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
|
||||
|
@ -235,18 +236,27 @@ module ActionView
|
|||
#
|
||||
# javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is true =>
|
||||
# <script type="text/javascript" src="/javascripts/shop.js"></script>
|
||||
#
|
||||
# The <tt>:recursive</tt> option is also available for caching:
|
||||
#
|
||||
# javascript_include_tag :all, :cache => true, :recursive => true
|
||||
def javascript_include_tag(*sources)
|
||||
options = sources.extract_options!.stringify_keys
|
||||
cache = options.delete("cache")
|
||||
recursive = options.delete("recursive")
|
||||
|
||||
if ActionController::Base.perform_caching && cache
|
||||
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
|
||||
joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
|
||||
|
||||
write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources))
|
||||
unless File.exists?(joined_javascript_path)
|
||||
JavaScriptSources.create(self, @controller, sources, recursive).write_asset_file_contents(joined_javascript_path)
|
||||
end
|
||||
javascript_src_tag(joined_javascript_name, options)
|
||||
else
|
||||
expand_javascript_sources(sources).collect { |source| javascript_src_tag(source, options) }.join("\n")
|
||||
JavaScriptSources.create(self, @controller, sources, recursive).expand_sources.collect { |source|
|
||||
javascript_src_tag(source, options)
|
||||
}.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -262,7 +272,7 @@ module ActionView
|
|||
# <script type="text/javascript" src="/javascripts/body.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/tail.js"></script>
|
||||
def self.register_javascript_expansion(expansions)
|
||||
@@javascript_expansions.merge!(expansions)
|
||||
JavaScriptSources.expansions.merge!(expansions)
|
||||
end
|
||||
|
||||
# Register one or more stylesheet files to be included when <tt>symbol</tt>
|
||||
|
@ -277,7 +287,7 @@ module ActionView
|
|||
# <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
def self.register_stylesheet_expansion(expansions)
|
||||
@@stylesheet_expansions.merge!(expansions)
|
||||
StylesheetSources.expansions.merge!(expansions)
|
||||
end
|
||||
|
||||
# Register one or more additional JavaScript files to be included when
|
||||
|
@ -285,11 +295,11 @@ module ActionView
|
|||
# typically intended to be called from plugin initialization to register additional
|
||||
# .js files that the plugin installed in <tt>public/javascripts</tt>.
|
||||
def self.register_javascript_include_default(*sources)
|
||||
@@javascript_expansions[:defaults].concat(sources)
|
||||
JavaScriptSources.expansions[:defaults].concat(sources)
|
||||
end
|
||||
|
||||
def self.reset_javascript_include_default #:nodoc:
|
||||
@@javascript_expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
|
||||
JavaScriptSources.expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
|
||||
end
|
||||
|
||||
# Computes the path to a stylesheet asset in the public stylesheets directory.
|
||||
|
@ -304,7 +314,7 @@ module ActionView
|
|||
# stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style.css
|
||||
# stylesheet_path "http://www.railsapplication.com/css/style.js" # => http://www.railsapplication.com/css/style.css
|
||||
def stylesheet_path(source)
|
||||
compute_public_path(source, 'stylesheets', 'css')
|
||||
StylesheetTag.create(self, @controller, source).public_path
|
||||
end
|
||||
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
|
||||
|
||||
|
@ -332,13 +342,17 @@ module ActionView
|
|||
# <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# You can also include all styles in the stylesheet directory using <tt>:all</tt> as the source:
|
||||
# You can also include all styles in the stylesheets directory using <tt>:all</tt> as the source:
|
||||
#
|
||||
# stylesheet_link_tag :all # =>
|
||||
# <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# If you want Rails to search in all the subdirectories under stylesheets, you should explicitly set <tt>:recursive</tt>:
|
||||
#
|
||||
# stylesheet_link_tag :all, :recursive => true
|
||||
#
|
||||
# == Caching multiple stylesheets into one
|
||||
#
|
||||
# You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
|
||||
|
@ -362,18 +376,27 @@ module ActionView
|
|||
#
|
||||
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is true =>
|
||||
# <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# The <tt>:recursive</tt> option is also available for caching:
|
||||
#
|
||||
# stylesheet_link_tag :all, :cache => true, :recursive => true
|
||||
def stylesheet_link_tag(*sources)
|
||||
options = sources.extract_options!.stringify_keys
|
||||
cache = options.delete("cache")
|
||||
recursive = options.delete("recursive")
|
||||
|
||||
if ActionController::Base.perform_caching && cache
|
||||
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
|
||||
joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
|
||||
|
||||
write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources))
|
||||
unless File.exists?(joined_stylesheet_path)
|
||||
StylesheetSources.create(self, @controller, sources, recursive).write_asset_file_contents(joined_stylesheet_path)
|
||||
end
|
||||
stylesheet_tag(joined_stylesheet_name, options)
|
||||
else
|
||||
expand_stylesheet_sources(sources).collect { |source| stylesheet_tag(source, options) }.join("\n")
|
||||
StylesheetSources.create(self, @controller, sources, recursive).expand_sources.collect { |source|
|
||||
stylesheet_tag(source, options)
|
||||
}.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -388,7 +411,7 @@ module ActionView
|
|||
# image_path("/icons/edit.png") # => /icons/edit.png
|
||||
# image_path("http://www.railsapplication.com/img/edit.png") # => http://www.railsapplication.com/img/edit.png
|
||||
def image_path(source)
|
||||
compute_public_path(source, 'images')
|
||||
ImageTag.create(self, @controller, source).public_path
|
||||
end
|
||||
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
|
||||
|
||||
|
@ -421,9 +444,9 @@ module ActionView
|
|||
# <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
|
||||
# image_tag("/icons/icon.gif", :class => "menu_icon") # =>
|
||||
# <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
|
||||
# image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
|
||||
# image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
|
||||
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
|
||||
# image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
|
||||
# image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
|
||||
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
|
||||
def image_tag(source, options = {})
|
||||
options.symbolize_keys!
|
||||
|
@ -436,121 +459,14 @@ module ActionView
|
|||
end
|
||||
|
||||
if mouseover = options.delete(:mouseover)
|
||||
options[:onmouseover] = "this.src='#{image_path(mouseover)}'"
|
||||
options[:onmouseout] = "this.src='#{image_path(options[:src])}'"
|
||||
options[:onmouseover] = "this.src='#{image_path(mouseover)}'"
|
||||
options[:onmouseout] = "this.src='#{image_path(options[:src])}'"
|
||||
end
|
||||
|
||||
tag("img", options)
|
||||
end
|
||||
|
||||
private
|
||||
def file_exist?(path)
|
||||
@@file_exist_cache ||= {}
|
||||
if !(@@file_exist_cache[path] ||= File.exist?(path))
|
||||
@@file_exist_cache[path] = true
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
|
||||
# Prefix with <tt>/dir/</tt> 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.
|
||||
def compute_public_path(source, dir, ext = nil, include_host = true)
|
||||
has_request = @controller.respond_to?(:request)
|
||||
|
||||
cache_key =
|
||||
if has_request
|
||||
[ @controller.request.protocol,
|
||||
ActionController::Base.asset_host.to_s,
|
||||
@controller.request.relative_url_root,
|
||||
dir, source, ext, include_host ].join
|
||||
else
|
||||
[ ActionController::Base.asset_host.to_s,
|
||||
dir, source, ext, include_host ].join
|
||||
end
|
||||
|
||||
ActionView::Base.computed_public_paths[cache_key] ||=
|
||||
begin
|
||||
source += ".#{ext}" if ext && File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}"))
|
||||
|
||||
if source =~ %r{^[-a-z]+://}
|
||||
source
|
||||
else
|
||||
source = "/#{dir}/#{source}" unless source[0] == ?/
|
||||
if has_request
|
||||
unless source =~ %r{^#{@controller.request.relative_url_root}/}
|
||||
source = "#{@controller.request.relative_url_root}#{source}"
|
||||
end
|
||||
end
|
||||
|
||||
rewrite_asset_path(source)
|
||||
end
|
||||
end
|
||||
|
||||
source = ActionView::Base.computed_public_paths[cache_key]
|
||||
|
||||
if include_host && source !~ %r{^[-a-z]+://}
|
||||
host = compute_asset_host(source)
|
||||
|
||||
if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
|
||||
host = "#{@controller.request.protocol}#{host}"
|
||||
end
|
||||
|
||||
"#{host}#{source}"
|
||||
else
|
||||
source
|
||||
end
|
||||
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 <tt>%d</tt> (the number is the source hash mod 4),
|
||||
# or the value returned from invoking the proc if it's a proc.
|
||||
def compute_asset_host(source)
|
||||
if host = ActionController::Base.asset_host
|
||||
if host.is_a?(Proc)
|
||||
case host.arity
|
||||
when 2
|
||||
host.call(source, @controller.request)
|
||||
else
|
||||
host.call(source)
|
||||
end
|
||||
else
|
||||
(host =~ /%d/) ? host % (source.hash % 4) : host
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# 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
|
||||
path = File.join(ASSETS_DIR, source)
|
||||
|
||||
if File.exist?(path)
|
||||
File.mtime(path).to_i.to_s
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
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)
|
||||
asset_id = rails_asset_id(source)
|
||||
if asset_id.blank?
|
||||
source
|
||||
else
|
||||
source + "?#{asset_id}"
|
||||
end
|
||||
end
|
||||
|
||||
def javascript_src_tag(source, options)
|
||||
content_tag("script", "", { "type" => Mime::JS, "src" => path_to_javascript(source) }.merge(options))
|
||||
end
|
||||
|
@ -559,55 +475,336 @@ module ActionView
|
|||
tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
|
||||
end
|
||||
|
||||
def compute_javascript_paths(sources)
|
||||
expand_javascript_sources(sources).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
|
||||
end
|
||||
module ImageAsset
|
||||
DIRECTORY = 'images'.freeze
|
||||
|
||||
def compute_stylesheet_paths(sources)
|
||||
expand_stylesheet_sources(sources).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
|
||||
end
|
||||
def directory
|
||||
DIRECTORY
|
||||
end
|
||||
|
||||
def expand_javascript_sources(sources)
|
||||
if sources.include?(:all)
|
||||
all_javascript_files = Dir[File.join(JAVASCRIPTS_DIR, '*.js')].collect { |file| File.basename(file).gsub(/\.\w+$/, '') }.sort
|
||||
@@all_javascript_sources ||= ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
|
||||
else
|
||||
expanded_sources = sources.collect do |source|
|
||||
determine_source(source, @@javascript_expansions)
|
||||
end.flatten
|
||||
expanded_sources << "application" if sources.include?(:defaults) && file_exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
|
||||
expanded_sources
|
||||
def extension
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def expand_stylesheet_sources(sources)
|
||||
if sources.first == :all
|
||||
@@all_stylesheet_sources ||= Dir[File.join(STYLESHEETS_DIR, '*.css')].collect { |file| File.basename(file).gsub(/\.\w+$/, '') }.sort
|
||||
else
|
||||
sources.collect do |source|
|
||||
determine_source(source, @@stylesheet_expansions)
|
||||
end.flatten
|
||||
module JavaScriptAsset
|
||||
DIRECTORY = 'javascripts'.freeze
|
||||
EXTENSION = 'js'.freeze
|
||||
|
||||
def public_directory
|
||||
JAVASCRIPTS_DIR
|
||||
end
|
||||
|
||||
def directory
|
||||
DIRECTORY
|
||||
end
|
||||
|
||||
def extension
|
||||
EXTENSION
|
||||
end
|
||||
end
|
||||
|
||||
def determine_source(source, collection)
|
||||
case source
|
||||
when Symbol
|
||||
collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
|
||||
else
|
||||
source
|
||||
module StylesheetAsset
|
||||
DIRECTORY = 'stylesheets'.freeze
|
||||
EXTENSION = 'css'.freeze
|
||||
|
||||
def public_directory
|
||||
STYLESHEETS_DIR
|
||||
end
|
||||
|
||||
def directory
|
||||
DIRECTORY
|
||||
end
|
||||
|
||||
def extension
|
||||
EXTENSION
|
||||
end
|
||||
end
|
||||
|
||||
def join_asset_file_contents(paths)
|
||||
paths.collect { |path| File.read(File.join(ASSETS_DIR, path.split("?").first)) }.join("\n\n")
|
||||
class AssetTag
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
Cache = {}
|
||||
CacheGuard = Mutex.new
|
||||
|
||||
def self.create(template, controller, source, include_host = true)
|
||||
CacheGuard.synchronize do
|
||||
key = if controller.respond_to?(:request)
|
||||
[self, controller.request.protocol,
|
||||
ActionController::Base.asset_host,
|
||||
ActionController::Base.relative_url_root,
|
||||
source, include_host]
|
||||
else
|
||||
[self, ActionController::Base.asset_host, source, include_host]
|
||||
end
|
||||
Cache[key] ||= new(template, controller, source, include_host).freeze
|
||||
end
|
||||
end
|
||||
|
||||
ProtocolRegexp = %r{^[-a-z]+://}.freeze
|
||||
|
||||
def initialize(template, controller, source, include_host = true)
|
||||
# NOTE: The template arg is temporarily needed for a legacy plugin
|
||||
# hook that is expected to call rewrite_asset_path on the
|
||||
# template. This should eventually be removed.
|
||||
@template = template
|
||||
@controller = controller
|
||||
@source = source
|
||||
@include_host = include_host
|
||||
end
|
||||
|
||||
def public_path
|
||||
compute_public_path(@source)
|
||||
end
|
||||
memoize :public_path
|
||||
|
||||
def asset_file_path
|
||||
File.join(ASSETS_DIR, public_path.split('?').first)
|
||||
end
|
||||
memoize :asset_file_path
|
||||
|
||||
def contents
|
||||
File.read(asset_file_path)
|
||||
end
|
||||
|
||||
def mtime
|
||||
File.mtime(asset_file_path)
|
||||
end
|
||||
|
||||
private
|
||||
def request
|
||||
@controller.request
|
||||
end
|
||||
|
||||
def request?
|
||||
@controller.respond_to?(:request)
|
||||
end
|
||||
|
||||
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
|
||||
# Prefix with <tt>/dir/</tt> 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.
|
||||
def compute_public_path(source)
|
||||
source += ".#{extension}" if missing_extension?(source)
|
||||
unless source =~ ProtocolRegexp
|
||||
source = "/#{directory}/#{source}" unless source[0] == ?/
|
||||
source = prepend_relative_url_root(source)
|
||||
source = rewrite_asset_path(source)
|
||||
end
|
||||
source = prepend_asset_host(source)
|
||||
source
|
||||
end
|
||||
|
||||
def missing_extension?(source)
|
||||
extension && (File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, directory, "#{source}.#{extension}")))
|
||||
end
|
||||
|
||||
def prepend_relative_url_root(source)
|
||||
relative_url_root = ActionController::Base.relative_url_root
|
||||
if request? && @include_host && source !~ %r{^#{relative_url_root}/}
|
||||
"#{relative_url_root}#{source}"
|
||||
else
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
def prepend_asset_host(source)
|
||||
if @include_host && source !~ ProtocolRegexp
|
||||
host = compute_asset_host(source)
|
||||
if request? && !host.blank? && host !~ ProtocolRegexp
|
||||
host = "#{request.protocol}#{host}"
|
||||
end
|
||||
"#{host}#{source}"
|
||||
else
|
||||
source
|
||||
end
|
||||
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 <tt>%d</tt> (the number is the source hash mod 4),
|
||||
# or the value returned from invoking the proc if it's a proc.
|
||||
def compute_asset_host(source)
|
||||
if host = ActionController::Base.asset_host
|
||||
if host.is_a?(Proc)
|
||||
case host.arity
|
||||
when 2
|
||||
host.call(source, request)
|
||||
else
|
||||
host.call(source)
|
||||
end
|
||||
else
|
||||
(host =~ /%d/) ? host % (source.hash % 4) : host
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# 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
|
||||
path = File.join(ASSETS_DIR, source)
|
||||
|
||||
if File.exist?(path)
|
||||
File.mtime(path).to_i.to_s
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
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)
|
||||
if @template.respond_to?(:rewrite_asset_path)
|
||||
# DEPRECATE: This way to override rewrite_asset_path
|
||||
@template.send(:rewrite_asset_path, source)
|
||||
else
|
||||
asset_id = rails_asset_id(source)
|
||||
if asset_id.blank?
|
||||
source
|
||||
else
|
||||
"#{source}?#{asset_id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def write_asset_file_contents(joined_asset_path, asset_paths)
|
||||
unless file_exist?(joined_asset_path)
|
||||
class ImageTag < AssetTag
|
||||
include ImageAsset
|
||||
end
|
||||
|
||||
class JavaScriptTag < AssetTag
|
||||
include JavaScriptAsset
|
||||
end
|
||||
|
||||
class StylesheetTag < AssetTag
|
||||
include StylesheetAsset
|
||||
end
|
||||
|
||||
class AssetCollection
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
Cache = {}
|
||||
CacheGuard = Mutex.new
|
||||
|
||||
def self.create(template, controller, sources, recursive)
|
||||
CacheGuard.synchronize do
|
||||
key = [self, sources, recursive]
|
||||
Cache[key] ||= new(template, controller, sources, recursive).freeze
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(template, controller, sources, recursive)
|
||||
# NOTE: The template arg is temporarily needed for a legacy plugin
|
||||
# hook. See NOTE under AssetTag#initialize for more details
|
||||
@template = template
|
||||
@controller = controller
|
||||
@sources = sources
|
||||
@recursive = recursive
|
||||
end
|
||||
|
||||
def write_asset_file_contents(joined_asset_path)
|
||||
FileUtils.mkdir_p(File.dirname(joined_asset_path))
|
||||
File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
|
||||
File.open(joined_asset_path, "w+") { |cache| cache.write(joined_contents) }
|
||||
mt = latest_mtime
|
||||
File.utime(mt, mt, joined_asset_path)
|
||||
end
|
||||
|
||||
private
|
||||
def determine_source(source, collection)
|
||||
case source
|
||||
when Symbol
|
||||
collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
|
||||
else
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
def validate_sources!
|
||||
@sources.collect { |source| determine_source(source, self.class.expansions) }.flatten
|
||||
end
|
||||
|
||||
def all_asset_files
|
||||
path = [public_directory, ('**' if @recursive), "*.#{extension}"].compact
|
||||
Dir[File.join(*path)].collect { |file|
|
||||
file[-(file.size - public_directory.size - 1)..-1].sub(/\.\w+$/, '')
|
||||
}.sort
|
||||
end
|
||||
|
||||
def tag_sources
|
||||
expand_sources.collect { |source| tag_class.create(@template, @controller, source, false) }
|
||||
end
|
||||
|
||||
def joined_contents
|
||||
tag_sources.collect { |source| source.contents }.join("\n\n")
|
||||
end
|
||||
|
||||
# Set mtime to the latest of the combined files to allow for
|
||||
# consistent ETag without a shared filesystem.
|
||||
def latest_mtime
|
||||
tag_sources.map { |source| source.mtime }.max
|
||||
end
|
||||
end
|
||||
|
||||
class JavaScriptSources < AssetCollection
|
||||
include JavaScriptAsset
|
||||
|
||||
EXPANSIONS = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
|
||||
|
||||
def self.expansions
|
||||
EXPANSIONS
|
||||
end
|
||||
|
||||
APPLICATION_JS = "application".freeze
|
||||
APPLICATION_FILE = "application.js".freeze
|
||||
|
||||
def expand_sources
|
||||
if @sources.include?(:all)
|
||||
assets = all_asset_files
|
||||
((defaults.dup & assets) + assets).uniq!
|
||||
else
|
||||
expanded_sources = validate_sources!
|
||||
expanded_sources << APPLICATION_JS if include_application?
|
||||
expanded_sources
|
||||
end
|
||||
end
|
||||
memoize :expand_sources
|
||||
|
||||
private
|
||||
def tag_class
|
||||
JavaScriptTag
|
||||
end
|
||||
|
||||
def defaults
|
||||
determine_source(:defaults, self.class.expansions)
|
||||
end
|
||||
|
||||
def include_application?
|
||||
@sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, APPLICATION_FILE))
|
||||
end
|
||||
end
|
||||
|
||||
class StylesheetSources < AssetCollection
|
||||
include StylesheetAsset
|
||||
|
||||
EXPANSIONS = {}
|
||||
|
||||
def self.expansions
|
||||
EXPANSIONS
|
||||
end
|
||||
|
||||
def expand_sources
|
||||
@sources.first == :all ? all_asset_files : validate_sources!
|
||||
end
|
||||
memoize :expand_sources
|
||||
|
||||
private
|
||||
def tag_class
|
||||
StylesheetTag
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,7 +17,7 @@ module ActionView
|
|||
# # GET /posts.atom
|
||||
# def index
|
||||
# @posts = Post.find(:all)
|
||||
#
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.html
|
||||
# format.atom
|
||||
|
@ -29,12 +29,12 @@ module ActionView
|
|||
# atom_feed do |feed|
|
||||
# feed.title("My great blog!")
|
||||
# feed.updated((@posts.first.created_at))
|
||||
#
|
||||
#
|
||||
# for post in @posts
|
||||
# feed.entry(post) do |entry|
|
||||
# entry.title(post.title)
|
||||
# entry.content(post.body, :type => 'html')
|
||||
#
|
||||
#
|
||||
# entry.author do |author|
|
||||
# author.name("DHH")
|
||||
# end
|
||||
|
@ -47,9 +47,11 @@ module ActionView
|
|||
# * <tt>:language</tt>: Defaults to "en-US".
|
||||
# * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
|
||||
# * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
|
||||
# * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
|
||||
# created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
|
||||
# * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}"
|
||||
# * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
|
||||
# created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
|
||||
# 2005 is used (as an "I don't care" value).
|
||||
# * <tt>:instruct</tt>: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]}
|
||||
#
|
||||
# Other namespaces can be added to the root element:
|
||||
#
|
||||
|
@ -73,36 +75,87 @@ module ActionView
|
|||
# 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:
|
||||
#
|
||||
# atom_feed yields an AtomFeedBuilder instance.
|
||||
# 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[: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("tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}")
|
||||
xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}")
|
||||
xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port))
|
||||
xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url)
|
||||
|
||||
|
||||
yield AtomFeedBuilder.new(xml, self, options)
|
||||
end
|
||||
end
|
||||
|
||||
class AtomBuilder
|
||||
def initialize(xml)
|
||||
@xml = xml
|
||||
end
|
||||
|
||||
class AtomFeedBuilder
|
||||
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)
|
||||
%w( content rights title subtitle summary ).include?(method.to_s) &&
|
||||
arguments.last.respond_to?(:[]) &&
|
||||
arguments.last[:type].to_s == 'xhtml'
|
||||
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)
|
||||
|
@ -115,9 +168,10 @@ module ActionView
|
|||
# * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
|
||||
# * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
|
||||
# * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
|
||||
# * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
|
||||
def entry(record, options = {})
|
||||
@xml.entry do
|
||||
@xml.id("tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
|
||||
@xml.entry do
|
||||
@xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
|
||||
|
||||
if options[:published] || (record.respond_to?(:created_at) && record.created_at)
|
||||
@xml.published((options[:published] || record.created_at).xmlschema)
|
||||
|
@ -129,15 +183,11 @@ module ActionView
|
|||
|
||||
@xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:url] || @view.polymorphic_url(record))
|
||||
|
||||
yield @xml
|
||||
yield AtomBuilder.new(@xml)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def method_missing(method, *arguments, &block)
|
||||
@xml.__send__(method, *arguments, &block)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,15 +15,15 @@ module ActionView
|
|||
# <%= expensive_files_operation %>
|
||||
# <% end %>
|
||||
#
|
||||
# That would add something like "Process data files (0.34523)" to the log,
|
||||
# That would add something like "Process data files (345.2ms)" to the log,
|
||||
# which you can then use to compare timings when optimizing your code.
|
||||
#
|
||||
# You may give an optional logger level as the second argument
|
||||
# (:debug, :info, :warn, :error); the default value is :info.
|
||||
def benchmark(message = "Benchmarking", level = :info)
|
||||
if controller.logger
|
||||
real = Benchmark.realtime { yield }
|
||||
controller.logger.send(level, "#{message} (#{'%.5f' % real})")
|
||||
seconds = Benchmark.realtime { yield }
|
||||
controller.logger.send(level, "#{message} (#{'%.1f' % (seconds * 1000)}ms)")
|
||||
else
|
||||
yield
|
||||
end
|
||||
|
|
|
@ -32,8 +32,7 @@ module ActionView
|
|||
# <i>Topics listed alphabetically</i>
|
||||
# <% end %>
|
||||
def cache(name = {}, options = nil, &block)
|
||||
handler = Template.handler_class_for_extension(current_render_extension.to_sym)
|
||||
handler.new(@controller).cache_fragment(block, name, options)
|
||||
@controller.fragment_for(output_buffer, name, options, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,20 +31,15 @@ module ActionView
|
|||
# </body></html>
|
||||
#
|
||||
def capture(*args, &block)
|
||||
# execute the block
|
||||
begin
|
||||
buffer = eval(ActionView::Base.erb_variable, block.binding)
|
||||
rescue
|
||||
buffer = nil
|
||||
end
|
||||
|
||||
if buffer.nil?
|
||||
capture_block(*args, &block).to_s
|
||||
# Return captured buffer in erb.
|
||||
if block_called_from_erb?(block)
|
||||
with_output_buffer { block.call(*args) }
|
||||
else
|
||||
capture_erb_with_buffer(buffer, *args, &block).to_s
|
||||
# Return block result otherwise, but protect buffer also.
|
||||
with_output_buffer { return block.call(*args) }
|
||||
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 or the layout
|
||||
# by passing the identifier as an argument to <tt>yield</tt>.
|
||||
|
@ -121,41 +116,21 @@ module ActionView
|
|||
# named <tt>@content_for_#{name_of_the_content_block}</tt>. The preferred usage is now
|
||||
# <tt><%= yield :footer %></tt>.
|
||||
def content_for(name, content = nil, &block)
|
||||
existing_content_for = instance_variable_get("@content_for_#{name}").to_s
|
||||
new_content_for = existing_content_for + (block_given? ? capture(&block) : content)
|
||||
instance_variable_set("@content_for_#{name}", new_content_for)
|
||||
ivar = "@content_for_#{name}"
|
||||
content = capture(&block) if block_given?
|
||||
instance_variable_set(ivar, "#{instance_variable_get(ivar)}#{content}")
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
def capture_block(*args, &block)
|
||||
block.call(*args)
|
||||
end
|
||||
|
||||
def capture_erb(*args, &block)
|
||||
buffer = eval(ActionView::Base.erb_variable, block.binding)
|
||||
capture_erb_with_buffer(buffer, *args, &block)
|
||||
end
|
||||
|
||||
def capture_erb_with_buffer(buffer, *args, &block)
|
||||
pos = buffer.length
|
||||
block.call(*args)
|
||||
|
||||
# extract the block
|
||||
data = buffer[pos..-1]
|
||||
|
||||
# replace it in the original with empty string
|
||||
buffer[pos..-1] = ''
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
def erb_content_for(name, &block)
|
||||
eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_erb(&block)"
|
||||
end
|
||||
|
||||
def block_content_for(name, &block)
|
||||
eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_block(&block)"
|
||||
end
|
||||
# Use an alternate output buffer for the duration of the block.
|
||||
# Defaults to a new empty string.
|
||||
def with_output_buffer(buf = '') #:nodoc:
|
||||
self.output_buffer, old_buffer = buf, output_buffer
|
||||
yield
|
||||
output_buffer
|
||||
ensure
|
||||
self.output_buffer = old_buffer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,21 +2,28 @@ module ActionView
|
|||
module Helpers
|
||||
# Provides a set of methods for making it easier to debug Rails objects.
|
||||
module DebugHelper
|
||||
# Returns a <pre>-tag that has +object+ dumped by YAML. This creates a very
|
||||
# readable way to inspect an object.
|
||||
# Returns a YAML representation of +object+ wrapped with <pre> and </pre>.
|
||||
# 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
|
||||
# my_hash = {'first' => 1, 'second' => 'two', 'third' => [1,2,3]}
|
||||
# debug(my_hash)
|
||||
#
|
||||
# => <pre class='debug_dump'>---
|
||||
# first: 1
|
||||
# second: two
|
||||
# third:
|
||||
# - 1
|
||||
# - 2
|
||||
# - 3
|
||||
# </pre>
|
||||
# @user = User.new({ :username => 'testing', :password => 'xyz', :age => 42}) %>
|
||||
# debug(@user)
|
||||
# # =>
|
||||
# <pre class='debug_dump'>--- !ruby/object:User
|
||||
# attributes:
|
||||
# updated_at:
|
||||
# username: testing
|
||||
#
|
||||
# age: 42
|
||||
# password: xyz
|
||||
# created_at:
|
||||
# attributes_cache: {}
|
||||
#
|
||||
# new_record: true
|
||||
# </pre>
|
||||
|
||||
def debug(object)
|
||||
begin
|
||||
Marshal::dump(object)
|
||||
|
@ -28,4 +35,4 @@ module ActionView
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -76,7 +76,7 @@ module ActionView
|
|||
# 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 succint resource-oriented form generation with +form_for+
|
||||
# Rails provides succinct resource-oriented form generation with +form_for+
|
||||
# like this:
|
||||
#
|
||||
# <% form_for @offer do |f| %>
|
||||
|
@ -249,9 +249,9 @@ module ActionView
|
|||
args.unshift object
|
||||
end
|
||||
|
||||
concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding)
|
||||
concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}))
|
||||
fields_for(object_name, *(args << options), &proc)
|
||||
concat('</form>', proc.binding)
|
||||
concat('</form>')
|
||||
end
|
||||
|
||||
def apply_form_for_options!(object_or_array, options) #:nodoc:
|
||||
|
@ -304,10 +304,6 @@ module ActionView
|
|||
when String, Symbol
|
||||
object_name = record_or_name_or_array
|
||||
object = args.first
|
||||
when Array
|
||||
object = record_or_name_or_array.last
|
||||
object_name = ActionController::RecordIdentifier.singular_class_name(object)
|
||||
apply_form_for_options!(record_or_name_or_array, options)
|
||||
else
|
||||
object = record_or_name_or_array
|
||||
object_name = ActionController::RecordIdentifier.singular_class_name(object)
|
||||
|
@ -333,7 +329,7 @@ module ActionView
|
|||
# # => <label for="post_title" class="title_label">A short title</label>
|
||||
#
|
||||
def label(object_name, method, text = nil, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_label_tag(text, options)
|
||||
InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options)
|
||||
end
|
||||
|
||||
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
|
||||
|
@ -355,7 +351,7 @@ module ActionView
|
|||
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
|
||||
#
|
||||
def text_field(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("text", options)
|
||||
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
|
||||
end
|
||||
|
||||
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
|
||||
|
@ -377,7 +373,7 @@ module ActionView
|
|||
# # => <input type="text" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
|
||||
#
|
||||
def password_field(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("password", options)
|
||||
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", options)
|
||||
end
|
||||
|
||||
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
|
||||
|
@ -395,7 +391,7 @@ module ActionView
|
|||
# hidden_field(:user, :token)
|
||||
# # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
|
||||
def hidden_field(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("hidden", options)
|
||||
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
|
||||
end
|
||||
|
||||
# Returns an file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
|
||||
|
@ -414,7 +410,7 @@ module ActionView
|
|||
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
|
||||
#
|
||||
def file_field(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("file", options)
|
||||
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options)
|
||||
end
|
||||
|
||||
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
|
||||
|
@ -442,15 +438,44 @@ module ActionView
|
|||
# # #{@entry.body}
|
||||
# # </textarea>
|
||||
def text_area(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_text_area_tag(options)
|
||||
InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
|
||||
end
|
||||
|
||||
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
|
||||
# assigned to the template (identified by +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. Since HTTP standards say that unchecked checkboxes don't post anything,
|
||||
# we add a hidden value with the same name as the checkbox as a work around.
|
||||
# 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 a hidden field with the same name as
|
||||
# the checkbox after the very check box. So, 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 Rails parameters extraction always
|
||||
# gets the first occurrence of any given key, that works in 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.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Let's say that @post.validated? is 1:
|
||||
|
@ -468,7 +493,7 @@ module ActionView
|
|||
# # <input name="eula[accepted]" type="hidden" value="no" />
|
||||
#
|
||||
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
|
||||
InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
|
||||
end
|
||||
|
||||
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
|
||||
|
@ -488,7 +513,7 @@ module ActionView
|
|||
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
|
||||
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
|
||||
def radio_button(object_name, method, tag_value, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_radio_button_tag(tag_value, options)
|
||||
InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -501,12 +526,12 @@ module ActionView
|
|||
DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
|
||||
DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
|
||||
|
||||
def initialize(object_name, method_name, template_object, local_binding = nil, object = nil)
|
||||
def initialize(object_name, method_name, template_object, object = nil)
|
||||
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
|
||||
@template_object, @local_binding = template_object, local_binding
|
||||
@template_object = template_object
|
||||
@object = object
|
||||
if @object_name.sub!(/\[\]$/,"")
|
||||
if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
|
||||
if @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
|
||||
if (object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}")) && 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}"
|
||||
|
@ -683,7 +708,7 @@ module ActionView
|
|||
end
|
||||
|
||||
def sanitized_object_name
|
||||
@sanitized_object_name ||= @object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
||||
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
||||
end
|
||||
|
||||
def sanitized_method_name
|
||||
|
@ -701,6 +726,13 @@ module ActionView
|
|||
def initialize(object_name, object, template, options, proc)
|
||||
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
|
||||
@default_options = @options ? @options.slice(:index) : {}
|
||||
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
|
||||
end
|
||||
|
||||
(field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
|
||||
|
@ -713,16 +745,25 @@ module ActionView
|
|||
end
|
||||
|
||||
def fields_for(record_or_name_or_array, *args, &block)
|
||||
if options.has_key?(:index)
|
||||
index = "[#{options[:index]}]"
|
||||
elsif defined?(@auto_index)
|
||||
self.object_name = @object_name.to_s.sub(/\[\]$/,"")
|
||||
index = "[#{@auto_index}]"
|
||||
else
|
||||
index = ""
|
||||
end
|
||||
|
||||
case record_or_name_or_array
|
||||
when String, Symbol
|
||||
name = "#{object_name}[#{record_or_name_or_array}]"
|
||||
name = "#{object_name}#{index}[#{record_or_name_or_array}]"
|
||||
when Array
|
||||
object = record_or_name_or_array.last
|
||||
name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
|
||||
name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
|
||||
args.unshift(object)
|
||||
else
|
||||
object = record_or_name_or_array
|
||||
name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
|
||||
name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
|
||||
args.unshift(object)
|
||||
end
|
||||
|
||||
|
@ -741,8 +782,8 @@ module ActionView
|
|||
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
|
||||
end
|
||||
|
||||
def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError")
|
||||
@template.error_message_on(@object, method, prepend_text, append_text, css_class)
|
||||
def error_message_on(method, *args)
|
||||
@template.error_message_on(@object, method, *args)
|
||||
end
|
||||
|
||||
def error_messages(options = {})
|
||||
|
|
|
@ -96,7 +96,7 @@ module ActionView
|
|||
# By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
|
||||
# or <tt>:selected => nil</tt> to leave all options unselected.
|
||||
def select(object, method, choices, options = {}, html_options = {})
|
||||
InstanceTag.new(object, method, self, nil, options.delete(:object)).to_select_tag(choices, options, html_options)
|
||||
InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
|
||||
end
|
||||
|
||||
# Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
|
||||
|
@ -130,12 +130,7 @@ module ActionView
|
|||
# <option value="3">M. Clark</option>
|
||||
# </select>
|
||||
def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
|
||||
InstanceTag.new(object, method, self, nil, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
|
||||
end
|
||||
|
||||
# Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
|
||||
def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
|
||||
InstanceTag.new(object, method, self, nil, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
|
||||
InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
|
||||
end
|
||||
|
||||
# Return select and option tags for the given object and method, using
|
||||
|
@ -150,7 +145,8 @@ module ActionView
|
|||
# You can also supply an array of TimeZone objects
|
||||
# as +priority_zones+, so that they will be listed above the rest of the
|
||||
# (long) list. (You can use TimeZone.us_zones as a convenience for
|
||||
# obtaining a list of the US time zones.)
|
||||
# obtaining a list of the US time zones, or a Regexp to select the zones
|
||||
# of your choice)
|
||||
#
|
||||
# Finally, this method supports a <tt>:default</tt> option, which selects
|
||||
# a default TimeZone if the object's time zone is +nil+.
|
||||
|
@ -164,9 +160,11 @@ module ActionView
|
|||
#
|
||||
# time_zone_select( "user", 'time_zone', [ TimeZone['Alaska'], TimeZone['Hawaii'] ])
|
||||
#
|
||||
# time_zone_select( "user", 'time_zone', /Australia/)
|
||||
#
|
||||
# time_zone_select( "user", "time_zone", TZInfo::Timezone.all.sort, :model => TZInfo::Timezone)
|
||||
def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
|
||||
InstanceTag.new(object, method, self, nil, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
|
||||
InstanceTag.new(object, method, self, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
|
||||
end
|
||||
|
||||
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
|
||||
|
@ -271,28 +269,13 @@ module ActionView
|
|||
end
|
||||
end
|
||||
|
||||
# Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to
|
||||
# have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so
|
||||
# that they will be listed above the rest of the (long) list.
|
||||
#
|
||||
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
|
||||
def country_options_for_select(selected = nil, priority_countries = nil)
|
||||
country_options = ""
|
||||
|
||||
if priority_countries
|
||||
country_options += options_for_select(priority_countries, selected)
|
||||
country_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
|
||||
end
|
||||
|
||||
return country_options + options_for_select(COUNTRIES, selected)
|
||||
end
|
||||
|
||||
# Returns a string of option tags for pretty much any time zone in the
|
||||
# world. Supply a TimeZone name as +selected+ to have it marked as the
|
||||
# selected option tag. You can also supply an array of TimeZone objects
|
||||
# as +priority_zones+, so that they will be listed above the rest of the
|
||||
# (long) list. (You can use TimeZone.us_zones as a convenience for
|
||||
# obtaining a list of the US time zones.)
|
||||
# obtaining a list of the US time zones, or a Regexp to select the zones
|
||||
# of your choice)
|
||||
#
|
||||
# The +selected+ parameter must be either +nil+, or a string that names
|
||||
# a TimeZone.
|
||||
|
@ -311,6 +294,9 @@ module ActionView
|
|||
convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
|
||||
|
||||
if priority_zones
|
||||
if priority_zones.is_a?(Regexp)
|
||||
priority_zones = model.all.find_all {|z| z =~ priority_zones}
|
||||
end
|
||||
zone_options += options_for_select(convert_zones[priority_zones], selected)
|
||||
zone_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
|
||||
|
||||
|
@ -338,45 +324,6 @@ module ActionView
|
|||
value == selected
|
||||
end
|
||||
end
|
||||
|
||||
# All the countries included in the country_options output.
|
||||
COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
|
||||
"Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
|
||||
"Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
|
||||
"Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
|
||||
"British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
|
||||
"Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
|
||||
"Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
|
||||
"Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
|
||||
"Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
|
||||
"El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
|
||||
"Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
|
||||
"French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
|
||||
"Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
|
||||
"Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
|
||||
"Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
|
||||
"Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
|
||||
"Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
|
||||
"Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
|
||||
"Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
|
||||
"Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
|
||||
"Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
|
||||
"Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
|
||||
"Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
|
||||
"Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
|
||||
"Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
|
||||
"Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
|
||||
"Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
|
||||
"Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
|
||||
"Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
|
||||
"South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
|
||||
"Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
|
||||
"Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
|
||||
"Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
|
||||
"Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
|
||||
"United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
|
||||
"Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
|
||||
"Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES")
|
||||
end
|
||||
|
||||
class InstanceTag #:nodoc:
|
||||
|
@ -399,18 +346,6 @@ module ActionView
|
|||
)
|
||||
end
|
||||
|
||||
def to_country_select_tag(priority_countries, options, html_options)
|
||||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
value = value(object)
|
||||
content_tag("select",
|
||||
add_options(
|
||||
country_options_for_select(value, priority_countries),
|
||||
options, value
|
||||
), html_options
|
||||
)
|
||||
end
|
||||
|
||||
def to_time_zone_select_tag(priority_zones, options, html_options)
|
||||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
|
@ -438,19 +373,15 @@ module ActionView
|
|||
|
||||
class FormBuilder
|
||||
def select(method, choices, options = {}, html_options = {})
|
||||
@template.select(@object_name, method, choices, options.merge(:object => @object), html_options)
|
||||
@template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
|
||||
end
|
||||
|
||||
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
|
||||
@template.collection_select(@object_name, method, collection, value_method, text_method, options.merge(:object => @object), html_options)
|
||||
end
|
||||
|
||||
def country_select(method, priority_countries = nil, options = {}, html_options = {})
|
||||
@template.country_select(@object_name, method, priority_countries, options.merge(:object => @object), html_options)
|
||||
@template.collection_select(@object_name, method, collection, value_method, text_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, options.merge(:object => @object), html_options)
|
||||
@template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ module ActionView
|
|||
# Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like
|
||||
# FormHelper does. Instead, you provide the names and values manually.
|
||||
#
|
||||
# NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
|
||||
# NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
|
||||
# <tt>:disabled => true</tt> will give <tt>disabled="disabled"</tt>.
|
||||
module FormTagHelper
|
||||
# Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like
|
||||
|
@ -20,15 +20,15 @@ module ActionView
|
|||
# * A list of parameters to feed to the URL the form will be posted to.
|
||||
#
|
||||
# ==== Examples
|
||||
# form_tag('/posts')
|
||||
# form_tag('/posts')
|
||||
# # => <form action="/posts" method="post">
|
||||
#
|
||||
# form_tag('/posts/1', :method => :put)
|
||||
# form_tag('/posts/1', :method => :put)
|
||||
# # => <form action="/posts/1" method="put">
|
||||
#
|
||||
# form_tag('/upload', :multipart => true)
|
||||
# form_tag('/upload', :multipart => true)
|
||||
# # => <form action="/upload" method="post" enctype="multipart/form-data">
|
||||
#
|
||||
#
|
||||
# <% form_tag '/posts' do -%>
|
||||
# <div><%= submit_tag 'Save' %></div>
|
||||
# <% end -%>
|
||||
|
@ -62,7 +62,7 @@ module ActionView
|
|||
# # <option>3</option><option>4</option></select>
|
||||
#
|
||||
# select_tag "colors", "<option>Red</option><option>Green</option><option>Blue</option>", :multiple => true
|
||||
# # => <select id="colors" multiple="multiple" name="colors"><option>Red</option>
|
||||
# # => <select id="colors" multiple="multiple" name="colors[]"><option>Red</option>
|
||||
# # <option>Green</option><option>Blue</option></select>
|
||||
#
|
||||
# select_tag "locations", "<option>Home</option><option selected="selected">Work</option><option>Out</option>"
|
||||
|
@ -70,14 +70,15 @@ module ActionView
|
|||
# # <option>Out</option></select>
|
||||
#
|
||||
# select_tag "access", "<option>Read</option><option>Write</option>", :multiple => true, :class => 'form_input'
|
||||
# # => <select class="form_input" id="access" multiple="multiple" name="access"><option>Read</option>
|
||||
# # => <select class="form_input" id="access" multiple="multiple" name="access[]"><option>Read</option>
|
||||
# # <option>Write</option></select>
|
||||
#
|
||||
# select_tag "destination", "<option>NYC</option><option>Paris</option><option>Rome</option>", :disabled => true
|
||||
# # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option>
|
||||
# # <option>Paris</option><option>Rome</option></select>
|
||||
def select_tag(name, option_tags = nil, options = {})
|
||||
content_tag :select, option_tags, { "name" => name, "id" => name }.update(options.stringify_keys)
|
||||
html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
|
||||
content_tag :select, option_tags, { "name" => html_name, "id" => name }.update(options.stringify_keys)
|
||||
end
|
||||
|
||||
# Creates a standard text field; use these text fields to input smaller chunks of text like a username
|
||||
|
@ -88,7 +89,7 @@ module ActionView
|
|||
# * <tt>:size</tt> - The number of visible characters that will fit in the input.
|
||||
# * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
|
||||
# * Any other key creates standard HTML attributes for the tag.
|
||||
#
|
||||
#
|
||||
# ==== Examples
|
||||
# text_field_tag 'name'
|
||||
# # => <input id="name" name="name" type="text" />
|
||||
|
@ -116,7 +117,7 @@ module ActionView
|
|||
|
||||
# Creates a label field
|
||||
#
|
||||
# ==== Options
|
||||
# ==== Options
|
||||
# * Creates standard HTML attributes for the tag.
|
||||
#
|
||||
# ==== Examples
|
||||
|
@ -146,21 +147,21 @@ module ActionView
|
|||
# # => <input id="token" name="token" type="hidden" value="VUBJKB23UIVI1UU1VOBVI@" />
|
||||
#
|
||||
# hidden_field_tag 'collected_input', '', :onchange => "alert('Input collected!')"
|
||||
# # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
|
||||
# # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
|
||||
# # type="hidden" value="" />
|
||||
def hidden_field_tag(name, value = nil, options = {})
|
||||
text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
|
||||
end
|
||||
|
||||
# Creates a file upload field. If you are using file uploads then you will also need
|
||||
# Creates a file upload field. If you are using file uploads then you will also need
|
||||
# to set the multipart option for the form tag:
|
||||
#
|
||||
# <%= form_tag { :action => "post" }, { :multipart => true } %>
|
||||
# <% form_tag '/upload', :multipart => true do %>
|
||||
# <label for="file">File to Upload</label> <%= file_field_tag "file" %>
|
||||
# <%= submit_tag %>
|
||||
# <%= end_form_tag %>
|
||||
# <% end %>
|
||||
#
|
||||
# The specified URL will then be passed a File object containing the selected file, or if the field
|
||||
# The specified URL will then be passed a File object containing the selected file, or if the field
|
||||
# was left blank, a StringIO object.
|
||||
#
|
||||
# ==== Options
|
||||
|
@ -181,7 +182,7 @@ module ActionView
|
|||
# # => <input id="resume" name="resume" type="file" value="~/resume.doc" />
|
||||
#
|
||||
# file_field_tag 'user_pic', :accept => 'image/png,image/gif,image/jpeg'
|
||||
# # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" />
|
||||
# # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" />
|
||||
#
|
||||
# file_field_tag 'file', :accept => 'text/html', :class => 'upload', :value => 'index.html'
|
||||
# # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" />
|
||||
|
@ -286,7 +287,7 @@ module ActionView
|
|||
tag :input, html_options
|
||||
end
|
||||
|
||||
# Creates a radio button; use groups of radio buttons named the same to allow users to
|
||||
# Creates a radio button; use groups of radio buttons named the same to allow users to
|
||||
# select from a group of options.
|
||||
#
|
||||
# ==== Options
|
||||
|
@ -313,14 +314,14 @@ module ActionView
|
|||
tag :input, html_options
|
||||
end
|
||||
|
||||
# Creates a submit button with the text <tt>value</tt> as the caption.
|
||||
# Creates a submit button with the text <tt>value</tt> as the caption.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:confirm => 'question?'</tt> - This will add a JavaScript confirm
|
||||
# prompt with the question specified. If the user accepts, the form is
|
||||
# processed normally, otherwise no action is taken.
|
||||
# * <tt>:disabled</tt> - If true, the user will not be able to use this input.
|
||||
# * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a disabled version
|
||||
# * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a disabled version
|
||||
# of the submit button when the form is submitted.
|
||||
# * Any other key creates standard HTML options for the tag.
|
||||
#
|
||||
|
@ -335,7 +336,7 @@ module ActionView
|
|||
# # => <input disabled="disabled" name="commit" type="submit" value="Save edits" />
|
||||
#
|
||||
# submit_tag "Complete sale", :disable_with => "Please wait..."
|
||||
# # => <input name="commit" onclick="this.disabled=true;this.value='Please wait...';this.form.submit();"
|
||||
# # => <input name="commit" onclick="this.disabled=true;this.value='Please wait...';this.form.submit();"
|
||||
# # type="submit" value="Complete sale" />
|
||||
#
|
||||
# submit_tag nil, :class => "form_submit"
|
||||
|
@ -346,34 +347,34 @@ module ActionView
|
|||
# # name="commit" type="submit" value="Edit" />
|
||||
def submit_tag(value = "Save changes", options = {})
|
||||
options.stringify_keys!
|
||||
|
||||
|
||||
if disable_with = options.delete("disable_with")
|
||||
disable_with = "this.value='#{disable_with}'"
|
||||
disable_with << ";#{options.delete('onclick')}" if options['onclick']
|
||||
|
||||
options["onclick"] = [
|
||||
"this.setAttribute('originalValue', this.value)",
|
||||
"this.disabled=true",
|
||||
disable_with,
|
||||
"result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit())",
|
||||
"if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false }",
|
||||
"return result;",
|
||||
].join(";")
|
||||
options["onclick"] = "if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }"
|
||||
options["onclick"] << "else { hiddenCommit = this.cloneNode(false);hiddenCommit.setAttribute('type', 'hidden');this.form.appendChild(hiddenCommit); }"
|
||||
options["onclick"] << "this.setAttribute('originalValue', this.value);this.disabled = true;#{disable_with};"
|
||||
options["onclick"] << "result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());"
|
||||
options["onclick"] << "if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;"
|
||||
end
|
||||
|
||||
|
||||
if confirm = options.delete("confirm")
|
||||
options["onclick"] ||= ''
|
||||
options["onclick"] += "return #{confirm_javascript_function(confirm)};"
|
||||
options["onclick"] << "return #{confirm_javascript_function(confirm)};"
|
||||
end
|
||||
|
||||
|
||||
tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
|
||||
end
|
||||
|
||||
|
||||
# Displays an image which when clicked will submit the form.
|
||||
#
|
||||
# <tt>source</tt> is passed to AssetTagHelper#image_path
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:confirm => 'question?'</tt> - This will add a JavaScript confirm
|
||||
# prompt with the question specified. If the user accepts, the form is
|
||||
# processed normally, otherwise no action is taken.
|
||||
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
||||
# * Any other key creates standard HTML options for the tag.
|
||||
#
|
||||
|
@ -390,12 +391,20 @@ module ActionView
|
|||
# image_submit_tag("agree.png", :disabled => true, :class => "agree-disagree-button")
|
||||
# # => <input class="agree-disagree-button" disabled="disabled" src="/images/agree.png" type="image" />
|
||||
def image_submit_tag(source, options = {})
|
||||
options.stringify_keys!
|
||||
|
||||
if confirm = options.delete("confirm")
|
||||
options["onclick"] ||= ''
|
||||
options["onclick"] += "return #{confirm_javascript_function(confirm)};"
|
||||
end
|
||||
|
||||
tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options.stringify_keys)
|
||||
end
|
||||
|
||||
# Creates a field set for grouping HTML form elements.
|
||||
#
|
||||
# <tt>legend</tt> will become the fieldset's title (optional as per W3C).
|
||||
# <tt>options</tt> accept the same values as tag.
|
||||
#
|
||||
# === Examples
|
||||
# <% field_set_tag do %>
|
||||
|
@ -407,14 +416,19 @@ module ActionView
|
|||
# <p><%= text_field_tag 'name' %></p>
|
||||
# <% end %>
|
||||
# # => <fieldset><legend>Your details</legend><p><input id="name" name="name" type="text" /></p></fieldset>
|
||||
def field_set_tag(legend = nil, &block)
|
||||
#
|
||||
# <% field_set_tag nil, :class => 'format' do %>
|
||||
# <p><%= text_field_tag 'name' %></p>
|
||||
# <% end %>
|
||||
# # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
|
||||
def field_set_tag(legend = nil, options = nil, &block)
|
||||
content = capture(&block)
|
||||
concat(tag(:fieldset, {}, true), block.binding)
|
||||
concat(content_tag(:legend, legend), block.binding) unless legend.blank?
|
||||
concat(content, block.binding)
|
||||
concat("</fieldset>", block.binding)
|
||||
concat(tag(:fieldset, options, true))
|
||||
concat(content_tag(:legend, legend)) unless legend.blank?
|
||||
concat(content)
|
||||
concat("</fieldset>")
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def html_options_for_form(url_for_options, options, *parameters_for_url)
|
||||
returning options.stringify_keys do |html_options|
|
||||
|
@ -422,7 +436,7 @@ module ActionView
|
|||
html_options["action"] = url_for(url_for_options, *parameters_for_url)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def extra_tags_for_form(html_options)
|
||||
case method = html_options.delete("method").to_s
|
||||
when /^get$/i # must be case-insentive, but can't use downcase as might be nil
|
||||
|
@ -436,17 +450,17 @@ module ActionView
|
|||
content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag, :style => 'margin:0;padding:0')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def form_tag_html(html_options)
|
||||
extra_tags = extra_tags_for_form(html_options)
|
||||
tag(:form, html_options, true) + extra_tags
|
||||
end
|
||||
|
||||
|
||||
def form_tag_in_block(html_options, &block)
|
||||
content = capture(&block)
|
||||
concat(form_tag_html(html_options), block.binding)
|
||||
concat(content, block.binding)
|
||||
concat("</form>", block.binding)
|
||||
concat(form_tag_html(html_options))
|
||||
concat(content)
|
||||
concat("</form>")
|
||||
end
|
||||
|
||||
def token_tag
|
||||
|
|
|
@ -4,10 +4,10 @@ require 'action_view/helpers/prototype_helper'
|
|||
module ActionView
|
||||
module Helpers
|
||||
# Provides functionality for working with JavaScript in your views.
|
||||
#
|
||||
#
|
||||
# == Ajax, controls and visual effects
|
||||
#
|
||||
# * For information on using Ajax, see
|
||||
#
|
||||
# * For information on using Ajax, see
|
||||
# ActionView::Helpers::PrototypeHelper.
|
||||
# * For information on using controls and visual effects, see
|
||||
# ActionView::Helpers::ScriptaculousHelper.
|
||||
|
@ -20,22 +20,22 @@ module ActionView
|
|||
# and ActionView::Helpers::ScriptaculousHelper), you must do one of the
|
||||
# following:
|
||||
#
|
||||
# * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD
|
||||
# section of your page (recommended): This function will return
|
||||
# * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD
|
||||
# section of your page (recommended): This function will return
|
||||
# references to the JavaScript files created by the +rails+ command in
|
||||
# your <tt>public/javascripts</tt> directory. Using it is recommended as
|
||||
# the browser can then cache the libraries instead of fetching all the
|
||||
# the browser can then cache the libraries instead of fetching all the
|
||||
# functions anew on every request.
|
||||
# * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but
|
||||
# * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but
|
||||
# will only include the Prototype core library, which means you are able
|
||||
# to use all basic AJAX functionality. For the Scriptaculous-based
|
||||
# JavaScript helpers, like visual effects, autocompletion, drag and drop
|
||||
# to use all basic AJAX functionality. For the Scriptaculous-based
|
||||
# JavaScript helpers, like visual effects, autocompletion, drag and drop
|
||||
# and so on, you should use the method described above.
|
||||
# * Use <tt><%= define_javascript_functions %></tt>: this will copy all the
|
||||
# JavaScript support functions within a single script block. Not
|
||||
# recommended.
|
||||
#
|
||||
# For documentation on +javascript_include_tag+ see
|
||||
# For documentation on +javascript_include_tag+ see
|
||||
# ActionView::Helpers::AssetTagHelper.
|
||||
module JavaScriptHelper
|
||||
unless const_defined? :JAVASCRIPT_PATH
|
||||
|
@ -43,13 +43,22 @@ module ActionView
|
|||
end
|
||||
|
||||
include PrototypeHelper
|
||||
|
||||
# Returns a link that will trigger a JavaScript +function+ using the
|
||||
|
||||
# Returns a link of the given +name+ that will trigger a JavaScript +function+ using the
|
||||
# onclick handler and return false after the fact.
|
||||
#
|
||||
# The first argument +name+ is used as the link text.
|
||||
#
|
||||
# The next arguments are optional and may include the javascript function definition and a hash of html_options.
|
||||
#
|
||||
# The +function+ argument can be omitted in favor of an +update_page+
|
||||
# block, which evaluates to a string when the template is rendered
|
||||
# (instead of making an Ajax request first).
|
||||
# (instead of making an Ajax request first).
|
||||
#
|
||||
# The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
|
||||
#
|
||||
# Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
|
||||
#
|
||||
#
|
||||
# Examples:
|
||||
# link_to_function "Greeting", "alert('Hello world!')"
|
||||
|
@ -70,35 +79,39 @@ module ActionView
|
|||
# <a href="#" id="more_link" onclick="try {
|
||||
# $("details").visualEffect("toggle_blind");
|
||||
# $("more_link").update("Show me less");
|
||||
# }
|
||||
# catch (e) {
|
||||
# alert('RJS error:\n\n' + e.toString());
|
||||
# }
|
||||
# catch (e) {
|
||||
# alert('RJS error:\n\n' + e.toString());
|
||||
# alert('$(\"details\").visualEffect(\"toggle_blind\");
|
||||
# \n$(\"more_link\").update(\"Show me less\");');
|
||||
# throw e
|
||||
# throw e
|
||||
# };
|
||||
# return false;">Show me more</a>
|
||||
#
|
||||
def link_to_function(name, *args, &block)
|
||||
html_options = args.extract_options!.symbolize_keys
|
||||
function = args[0] || ''
|
||||
|
||||
function = update_page(&block) if block_given?
|
||||
content_tag(
|
||||
"a", name,
|
||||
html_options.merge({
|
||||
:href => html_options[:href] || "#",
|
||||
:onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function}; return false;"
|
||||
})
|
||||
)
|
||||
function = block_given? ? update_page(&block) : args[0] || ''
|
||||
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
|
||||
href = html_options[:href] || '#'
|
||||
|
||||
content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
|
||||
end
|
||||
|
||||
# Returns a button that'll trigger a JavaScript +function+ using the
|
||||
|
||||
# Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
|
||||
# onclick handler.
|
||||
#
|
||||
# The first argument +name+ is used as the button's value or display text.
|
||||
#
|
||||
# The next arguments are optional and may include the javascript function definition and a hash of html_options.
|
||||
#
|
||||
# The +function+ argument can be omitted in favor of an +update_page+
|
||||
# block, which evaluates to a string when the template is rendered
|
||||
# (instead of making an Ajax request first).
|
||||
# (instead of making an Ajax request first).
|
||||
#
|
||||
# The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
|
||||
#
|
||||
# Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
|
||||
#
|
||||
# Examples:
|
||||
# button_to_function "Greeting", "alert('Hello world!')"
|
||||
|
@ -111,45 +124,29 @@ module ActionView
|
|||
# end
|
||||
def button_to_function(name, *args, &block)
|
||||
html_options = args.extract_options!.symbolize_keys
|
||||
function = args[0] || ''
|
||||
|
||||
function = update_page(&block) if block_given?
|
||||
tag(:input, html_options.merge({
|
||||
:type => "button", :value => name,
|
||||
:onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
|
||||
}))
|
||||
function = block_given? ? update_page(&block) : args[0] || ''
|
||||
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
|
||||
|
||||
tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
|
||||
end
|
||||
|
||||
# Includes the Action Pack JavaScript libraries inside a single <script>
|
||||
# tag. The function first includes prototype.js and then its core extensions,
|
||||
# (determined by filenames starting with "prototype").
|
||||
# Afterwards, any additional scripts will be included in undefined order.
|
||||
#
|
||||
# Note: The recommended approach is to copy the contents of
|
||||
# lib/action_view/helpers/javascripts/ into your application's
|
||||
# public/javascripts/ directory, and use +javascript_include_tag+ to
|
||||
# create remote <script> links.
|
||||
def define_javascript_functions
|
||||
javascript = "<script type=\"#{Mime::JS}\">"
|
||||
|
||||
# load prototype.js and its extensions first
|
||||
prototype_libs = Dir.glob(File.join(JAVASCRIPT_PATH, 'prototype*')).sort.reverse
|
||||
prototype_libs.each do |filename|
|
||||
javascript << "\n" << IO.read(filename)
|
||||
end
|
||||
|
||||
# load other libraries
|
||||
(Dir.glob(File.join(JAVASCRIPT_PATH, '*')) - prototype_libs).each do |filename|
|
||||
javascript << "\n" << IO.read(filename)
|
||||
end
|
||||
javascript << '</script>'
|
||||
end
|
||||
|
||||
deprecate :define_javascript_functions=>"use javascript_include_tag instead"
|
||||
JS_ESCAPE_MAP = {
|
||||
'\\' => '\\\\',
|
||||
'</' => '<\/',
|
||||
"\r\n" => '\n',
|
||||
"\n" => '\n',
|
||||
"\r" => '\n',
|
||||
'"' => '\\"',
|
||||
"'" => "\\'" }
|
||||
|
||||
# Escape carrier returns and single and double quotes for JavaScript segments.
|
||||
def escape_javascript(javascript)
|
||||
(javascript || '').gsub('\\','\0\0').gsub('</','<\/').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }
|
||||
if javascript
|
||||
javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] }
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a JavaScript tag with the +content+ inside. Example:
|
||||
|
@ -163,7 +160,7 @@ module ActionView
|
|||
# </script>
|
||||
#
|
||||
# +html_options+ may be a hash of attributes for the <script> tag. Example:
|
||||
# javascript_tag "alert('All is good')", :defer => 'defer'
|
||||
# javascript_tag "alert('All is good')", :defer => 'defer'
|
||||
# # => <script defer="defer" type="text/javascript">alert('All is good')</script>
|
||||
#
|
||||
# Instead of passing the content as an argument, you can also use a block
|
||||
|
@ -172,46 +169,43 @@ module ActionView
|
|||
# alert('All is good')
|
||||
# <% end -%>
|
||||
def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
|
||||
if block_given?
|
||||
html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
|
||||
content = capture(&block)
|
||||
else
|
||||
content = content_or_options_with_block
|
||||
end
|
||||
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
|
||||
|
||||
javascript_tag = content_tag("script", javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
|
||||
|
||||
if block_given? && block_is_within_action_view?(block)
|
||||
concat(javascript_tag, block.binding)
|
||||
tag = content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
|
||||
|
||||
if block_called_from_erb?(block)
|
||||
concat(tag)
|
||||
else
|
||||
javascript_tag
|
||||
tag
|
||||
end
|
||||
end
|
||||
|
||||
def javascript_cdata_section(content) #:nodoc:
|
||||
"\n//#{cdata_section("\n#{content}\n//")}\n"
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
def options_for_javascript(options)
|
||||
'{' + options.map {|k, v| "#{k}:#{v}"}.sort.join(', ') + '}'
|
||||
if options.empty?
|
||||
'{}'
|
||||
else
|
||||
"{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def array_or_string_for_javascript(option)
|
||||
js_option = if option.kind_of?(Array)
|
||||
if option.kind_of?(Array)
|
||||
"['#{option.join('\',\'')}']"
|
||||
elsif !option.nil?
|
||||
"'#{option}'"
|
||||
end
|
||||
js_option
|
||||
end
|
||||
|
||||
private
|
||||
def block_is_within_action_view?(block)
|
||||
eval("defined? _erbout", block.binding)
|
||||
end
|
||||
end
|
||||
|
||||
JavascriptHelper = JavaScriptHelper unless const_defined? :JavascriptHelper
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* Prototype JavaScript framework, version 1.6.0.1
|
||||
* (c) 2005-2007 Sam Stephenson
|
||||
/* Prototype JavaScript framework, version 1.6.0.2
|
||||
* (c) 2005-2008 Sam Stephenson
|
||||
*
|
||||
* Prototype is freely distributable under the terms of an MIT-style license.
|
||||
* For details, see the Prototype web site: http://www.prototypejs.org/
|
||||
|
@ -7,7 +7,7 @@
|
|||
*--------------------------------------------------------------------------*/
|
||||
|
||||
var Prototype = {
|
||||
Version: '1.6.0.1',
|
||||
Version: '1.6.0.2',
|
||||
|
||||
Browser: {
|
||||
IE: !!(window.attachEvent && !window.opera),
|
||||
|
@ -110,7 +110,7 @@ Object.extend(Object, {
|
|||
try {
|
||||
if (Object.isUndefined(object)) return 'undefined';
|
||||
if (object === null) return 'null';
|
||||
return object.inspect ? object.inspect() : object.toString();
|
||||
return object.inspect ? object.inspect() : String(object);
|
||||
} catch (e) {
|
||||
if (e instanceof RangeError) return '...';
|
||||
throw e;
|
||||
|
@ -171,7 +171,8 @@ Object.extend(Object, {
|
|||
},
|
||||
|
||||
isArray: function(object) {
|
||||
return object && object.constructor === Array;
|
||||
return object != null && typeof object == "object" &&
|
||||
'splice' in object && 'join' in object;
|
||||
},
|
||||
|
||||
isHash: function(object) {
|
||||
|
@ -578,7 +579,7 @@ var Template = Class.create({
|
|||
}
|
||||
|
||||
return before + String.interpret(ctx);
|
||||
}.bind(this));
|
||||
});
|
||||
}
|
||||
});
|
||||
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
|
||||
|
@ -806,20 +807,20 @@ Object.extend(Enumerable, {
|
|||
function $A(iterable) {
|
||||
if (!iterable) return [];
|
||||
if (iterable.toArray) return iterable.toArray();
|
||||
var length = iterable.length, results = new Array(length);
|
||||
var length = iterable.length || 0, results = new Array(length);
|
||||
while (length--) results[length] = iterable[length];
|
||||
return results;
|
||||
}
|
||||
|
||||
if (Prototype.Browser.WebKit) {
|
||||
function $A(iterable) {
|
||||
$A = function(iterable) {
|
||||
if (!iterable) return [];
|
||||
if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
|
||||
iterable.toArray) return iterable.toArray();
|
||||
var length = iterable.length, results = new Array(length);
|
||||
var length = iterable.length || 0, results = new Array(length);
|
||||
while (length--) results[length] = iterable[length];
|
||||
return results;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Array.from = $A;
|
||||
|
@ -1298,7 +1299,7 @@ Ajax.Request = Class.create(Ajax.Base, {
|
|||
|
||||
var contentType = response.getHeader('Content-type');
|
||||
if (this.options.evalJS == 'force'
|
||||
|| (this.options.evalJS && contentType
|
||||
|| (this.options.evalJS && this.isSameOrigin() && contentType
|
||||
&& contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
|
||||
this.evalResponse();
|
||||
}
|
||||
|
@ -1316,9 +1317,18 @@ Ajax.Request = Class.create(Ajax.Base, {
|
|||
}
|
||||
},
|
||||
|
||||
isSameOrigin: function() {
|
||||
var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
|
||||
return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
|
||||
protocol: location.protocol,
|
||||
domain: document.domain,
|
||||
port: location.port ? ':' + location.port : ''
|
||||
}));
|
||||
},
|
||||
|
||||
getHeader: function(name) {
|
||||
try {
|
||||
return this.transport.getResponseHeader(name);
|
||||
return this.transport.getResponseHeader(name) || null;
|
||||
} catch (e) { return null }
|
||||
},
|
||||
|
||||
|
@ -1391,7 +1401,8 @@ Ajax.Response = Class.create({
|
|||
if (!json) return null;
|
||||
json = decodeURIComponent(escape(json));
|
||||
try {
|
||||
return json.evalJSON(this.request.options.sanitizeJSON);
|
||||
return json.evalJSON(this.request.options.sanitizeJSON ||
|
||||
!this.request.isSameOrigin());
|
||||
} catch (e) {
|
||||
this.request.dispatchException(e);
|
||||
}
|
||||
|
@ -1404,7 +1415,8 @@ Ajax.Response = Class.create({
|
|||
this.responseText.blank())
|
||||
return null;
|
||||
try {
|
||||
return this.responseText.evalJSON(options.sanitizeJSON);
|
||||
return this.responseText.evalJSON(options.sanitizeJSON ||
|
||||
!this.request.isSameOrigin());
|
||||
} catch (e) {
|
||||
this.request.dispatchException(e);
|
||||
}
|
||||
|
@ -1608,24 +1620,28 @@ Element.Methods = {
|
|||
Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
|
||||
insertions = {bottom:insertions};
|
||||
|
||||
var content, t, range;
|
||||
var content, insert, tagName, childNodes;
|
||||
|
||||
for (position in insertions) {
|
||||
for (var position in insertions) {
|
||||
content = insertions[position];
|
||||
position = position.toLowerCase();
|
||||
t = Element._insertionTranslations[position];
|
||||
insert = Element._insertionTranslations[position];
|
||||
|
||||
if (content && content.toElement) content = content.toElement();
|
||||
if (Object.isElement(content)) {
|
||||
t.insert(element, content);
|
||||
insert(element, content);
|
||||
continue;
|
||||
}
|
||||
|
||||
content = Object.toHTML(content);
|
||||
|
||||
range = element.ownerDocument.createRange();
|
||||
t.initializeRange(element, range);
|
||||
t.insert(element, range.createContextualFragment(content.stripScripts()));
|
||||
tagName = ((position == 'before' || position == 'after')
|
||||
? element.parentNode : element).tagName.toUpperCase();
|
||||
|
||||
childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
|
||||
|
||||
if (position == 'top' || position == 'after') childNodes.reverse();
|
||||
childNodes.each(insert.curry(element));
|
||||
|
||||
content.evalScripts.bind(content).defer();
|
||||
}
|
||||
|
@ -1670,7 +1686,7 @@ Element.Methods = {
|
|||
},
|
||||
|
||||
descendants: function(element) {
|
||||
return $(element).getElementsBySelector("*");
|
||||
return $(element).select("*");
|
||||
},
|
||||
|
||||
firstDescendant: function(element) {
|
||||
|
@ -1709,32 +1725,31 @@ Element.Methods = {
|
|||
element = $(element);
|
||||
if (arguments.length == 1) return $(element.parentNode);
|
||||
var ancestors = element.ancestors();
|
||||
return expression ? Selector.findElement(ancestors, expression, index) :
|
||||
ancestors[index || 0];
|
||||
return Object.isNumber(expression) ? ancestors[expression] :
|
||||
Selector.findElement(ancestors, expression, index);
|
||||
},
|
||||
|
||||
down: function(element, expression, index) {
|
||||
element = $(element);
|
||||
if (arguments.length == 1) return element.firstDescendant();
|
||||
var descendants = element.descendants();
|
||||
return expression ? Selector.findElement(descendants, expression, index) :
|
||||
descendants[index || 0];
|
||||
return Object.isNumber(expression) ? element.descendants()[expression] :
|
||||
element.select(expression)[index || 0];
|
||||
},
|
||||
|
||||
previous: function(element, expression, index) {
|
||||
element = $(element);
|
||||
if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
|
||||
var previousSiblings = element.previousSiblings();
|
||||
return expression ? Selector.findElement(previousSiblings, expression, index) :
|
||||
previousSiblings[index || 0];
|
||||
return Object.isNumber(expression) ? previousSiblings[expression] :
|
||||
Selector.findElement(previousSiblings, expression, index);
|
||||
},
|
||||
|
||||
next: function(element, expression, index) {
|
||||
element = $(element);
|
||||
if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
|
||||
var nextSiblings = element.nextSiblings();
|
||||
return expression ? Selector.findElement(nextSiblings, expression, index) :
|
||||
nextSiblings[index || 0];
|
||||
return Object.isNumber(expression) ? nextSiblings[expression] :
|
||||
Selector.findElement(nextSiblings, expression, index);
|
||||
},
|
||||
|
||||
select: function() {
|
||||
|
@ -1860,7 +1875,8 @@ Element.Methods = {
|
|||
do { ancestor = ancestor.parentNode; }
|
||||
while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
|
||||
}
|
||||
if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
|
||||
if (nextAncestor && nextAncestor.sourceIndex)
|
||||
return (e > a && e < nextAncestor.sourceIndex);
|
||||
}
|
||||
|
||||
while (element = element.parentNode)
|
||||
|
@ -2004,7 +2020,7 @@ Element.Methods = {
|
|||
if (element) {
|
||||
if (element.tagName == 'BODY') break;
|
||||
var p = Element.getStyle(element, 'position');
|
||||
if (p == 'relative' || p == 'absolute') break;
|
||||
if (p !== 'static') break;
|
||||
}
|
||||
} while (element);
|
||||
return Element._returnOffset(valueL, valueT);
|
||||
|
@ -2153,46 +2169,6 @@ Element._attributeTranslations = {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
if (!document.createRange || Prototype.Browser.Opera) {
|
||||
Element.Methods.insert = function(element, insertions) {
|
||||
element = $(element);
|
||||
|
||||
if (Object.isString(insertions) || Object.isNumber(insertions) ||
|
||||
Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
|
||||
insertions = { bottom: insertions };
|
||||
|
||||
var t = Element._insertionTranslations, content, position, pos, tagName;
|
||||
|
||||
for (position in insertions) {
|
||||
content = insertions[position];
|
||||
position = position.toLowerCase();
|
||||
pos = t[position];
|
||||
|
||||
if (content && content.toElement) content = content.toElement();
|
||||
if (Object.isElement(content)) {
|
||||
pos.insert(element, content);
|
||||
continue;
|
||||
}
|
||||
|
||||
content = Object.toHTML(content);
|
||||
tagName = ((position == 'before' || position == 'after')
|
||||
? element.parentNode : element).tagName.toUpperCase();
|
||||
|
||||
if (t.tags[tagName]) {
|
||||
var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
|
||||
if (position == 'top' || position == 'after') fragments.reverse();
|
||||
fragments.each(pos.insert.curry(element));
|
||||
}
|
||||
else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());
|
||||
|
||||
content.evalScripts.bind(content).defer();
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
||||
}
|
||||
|
||||
if (Prototype.Browser.Opera) {
|
||||
Element.Methods.getStyle = Element.Methods.getStyle.wrap(
|
||||
function(proceed, element, style) {
|
||||
|
@ -2237,12 +2213,31 @@ if (Prototype.Browser.Opera) {
|
|||
}
|
||||
|
||||
else if (Prototype.Browser.IE) {
|
||||
$w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
|
||||
// IE doesn't report offsets correctly for static elements, so we change them
|
||||
// to "relative" to get the values, then change them back.
|
||||
Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
|
||||
function(proceed, element) {
|
||||
element = $(element);
|
||||
var position = element.getStyle('position');
|
||||
if (position !== 'static') return proceed(element);
|
||||
element.setStyle({ position: 'relative' });
|
||||
var value = proceed(element);
|
||||
element.setStyle({ position: position });
|
||||
return value;
|
||||
}
|
||||
);
|
||||
|
||||
$w('positionedOffset viewportOffset').each(function(method) {
|
||||
Element.Methods[method] = Element.Methods[method].wrap(
|
||||
function(proceed, element) {
|
||||
element = $(element);
|
||||
var position = element.getStyle('position');
|
||||
if (position != 'static') return proceed(element);
|
||||
if (position !== 'static') return proceed(element);
|
||||
// Trigger hasLayout on the offset parent so that IE6 reports
|
||||
// accurate offsetTop and offsetLeft values for position: fixed.
|
||||
var offsetParent = element.getOffsetParent();
|
||||
if (offsetParent && offsetParent.getStyle('position') === 'fixed')
|
||||
offsetParent.setStyle({ zoom: 1 });
|
||||
element.setStyle({ position: 'relative' });
|
||||
var value = proceed(element);
|
||||
element.setStyle({ position: position });
|
||||
|
@ -2324,7 +2319,10 @@ else if (Prototype.Browser.IE) {
|
|||
};
|
||||
|
||||
Element._attributeTranslations.write = {
|
||||
names: Object.clone(Element._attributeTranslations.read.names),
|
||||
names: Object.extend({
|
||||
cellpadding: 'cellPadding',
|
||||
cellspacing: 'cellSpacing'
|
||||
}, Element._attributeTranslations.read.names),
|
||||
values: {
|
||||
checked: function(element, value) {
|
||||
element.checked = !!value;
|
||||
|
@ -2444,7 +2442,7 @@ if (Prototype.Browser.IE || Prototype.Browser.Opera) {
|
|||
};
|
||||
}
|
||||
|
||||
if (document.createElement('div').outerHTML) {
|
||||
if ('outerHTML' in document.createElement('div')) {
|
||||
Element.Methods.replace = function(element, content) {
|
||||
element = $(element);
|
||||
|
||||
|
@ -2482,45 +2480,25 @@ Element._returnOffset = function(l, t) {
|
|||
|
||||
Element._getContentFromAnonymousElement = function(tagName, html) {
|
||||
var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
|
||||
div.innerHTML = t[0] + html + t[1];
|
||||
t[2].times(function() { div = div.firstChild });
|
||||
if (t) {
|
||||
div.innerHTML = t[0] + html + t[1];
|
||||
t[2].times(function() { div = div.firstChild });
|
||||
} else div.innerHTML = html;
|
||||
return $A(div.childNodes);
|
||||
};
|
||||
|
||||
Element._insertionTranslations = {
|
||||
before: {
|
||||
adjacency: 'beforeBegin',
|
||||
insert: function(element, node) {
|
||||
element.parentNode.insertBefore(node, element);
|
||||
},
|
||||
initializeRange: function(element, range) {
|
||||
range.setStartBefore(element);
|
||||
}
|
||||
before: function(element, node) {
|
||||
element.parentNode.insertBefore(node, element);
|
||||
},
|
||||
top: {
|
||||
adjacency: 'afterBegin',
|
||||
insert: function(element, node) {
|
||||
element.insertBefore(node, element.firstChild);
|
||||
},
|
||||
initializeRange: function(element, range) {
|
||||
range.selectNodeContents(element);
|
||||
range.collapse(true);
|
||||
}
|
||||
top: function(element, node) {
|
||||
element.insertBefore(node, element.firstChild);
|
||||
},
|
||||
bottom: {
|
||||
adjacency: 'beforeEnd',
|
||||
insert: function(element, node) {
|
||||
element.appendChild(node);
|
||||
}
|
||||
bottom: function(element, node) {
|
||||
element.appendChild(node);
|
||||
},
|
||||
after: {
|
||||
adjacency: 'afterEnd',
|
||||
insert: function(element, node) {
|
||||
element.parentNode.insertBefore(node, element.nextSibling);
|
||||
},
|
||||
initializeRange: function(element, range) {
|
||||
range.setStartAfter(element);
|
||||
}
|
||||
after: function(element, node) {
|
||||
element.parentNode.insertBefore(node, element.nextSibling);
|
||||
},
|
||||
tags: {
|
||||
TABLE: ['<table>', '</table>', 1],
|
||||
|
@ -2532,7 +2510,6 @@ Element._insertionTranslations = {
|
|||
};
|
||||
|
||||
(function() {
|
||||
this.bottom.initializeRange = this.top.initializeRange;
|
||||
Object.extend(this.tags, {
|
||||
THEAD: this.tags.TBODY,
|
||||
TFOOT: this.tags.TBODY,
|
||||
|
@ -2716,7 +2693,7 @@ document.viewport = {
|
|||
window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
|
||||
}
|
||||
};
|
||||
/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
|
||||
/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
|
||||
* part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
|
||||
* license. Please see http://www.yui-ext.com/ for more information. */
|
||||
|
||||
|
@ -2959,13 +2936,13 @@ Object.extend(Selector, {
|
|||
},
|
||||
|
||||
criteria: {
|
||||
tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
|
||||
className: 'n = h.className(n, r, "#{1}", c); c = false;',
|
||||
id: 'n = h.id(n, r, "#{1}", c); c = false;',
|
||||
attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
|
||||
tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
|
||||
className: 'n = h.className(n, r, "#{1}", c); c = false;',
|
||||
id: 'n = h.id(n, r, "#{1}", c); c = false;',
|
||||
attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
|
||||
attr: function(m) {
|
||||
m[3] = (m[5] || m[6]);
|
||||
return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
|
||||
return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
|
||||
},
|
||||
pseudo: function(m) {
|
||||
if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
|
||||
|
@ -2989,7 +2966,8 @@ Object.extend(Selector, {
|
|||
tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
|
||||
id: /^#([\w\-\*]+)(\b|$)/,
|
||||
className: /^\.([\w\-\*]+)(\b|$)/,
|
||||
pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
|
||||
pseudo:
|
||||
/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
|
||||
attrPresence: /^\[([\w]+)\]/,
|
||||
attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
|
||||
},
|
||||
|
@ -3014,7 +2992,7 @@ Object.extend(Selector, {
|
|||
|
||||
attr: function(element, matches) {
|
||||
var nodeValue = Element.readAttribute(element, matches[1]);
|
||||
return Selector.operators[matches[2]](nodeValue, matches[3]);
|
||||
return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -3029,14 +3007,15 @@ Object.extend(Selector, {
|
|||
|
||||
// marks an array of nodes for counting
|
||||
mark: function(nodes) {
|
||||
var _true = Prototype.emptyFunction;
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
node._counted = true;
|
||||
node._countedByPrototype = _true;
|
||||
return nodes;
|
||||
},
|
||||
|
||||
unmark: function(nodes) {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
node._counted = undefined;
|
||||
node._countedByPrototype = undefined;
|
||||
return nodes;
|
||||
},
|
||||
|
||||
|
@ -3044,15 +3023,15 @@ Object.extend(Selector, {
|
|||
// "ofType" flag indicates whether we're indexing for nth-of-type
|
||||
// rather than nth-child
|
||||
index: function(parentNode, reverse, ofType) {
|
||||
parentNode._counted = true;
|
||||
parentNode._countedByPrototype = Prototype.emptyFunction;
|
||||
if (reverse) {
|
||||
for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
|
||||
var node = nodes[i];
|
||||
if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
|
||||
if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
|
||||
if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
|
||||
if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -3061,8 +3040,8 @@ Object.extend(Selector, {
|
|||
if (nodes.length == 0) return nodes;
|
||||
var results = [], n;
|
||||
for (var i = 0, l = nodes.length; i < l; i++)
|
||||
if (!(n = nodes[i])._counted) {
|
||||
n._counted = true;
|
||||
if (!(n = nodes[i])._countedByPrototype) {
|
||||
n._countedByPrototype = Prototype.emptyFunction;
|
||||
results.push(Element.extend(n));
|
||||
}
|
||||
return Selector.handlers.unmark(results);
|
||||
|
@ -3114,7 +3093,7 @@ Object.extend(Selector, {
|
|||
|
||||
// TOKEN FUNCTIONS
|
||||
tagName: function(nodes, root, tagName, combinator) {
|
||||
tagName = tagName.toUpperCase();
|
||||
var uTagName = tagName.toUpperCase();
|
||||
var results = [], h = Selector.handlers;
|
||||
if (nodes) {
|
||||
if (combinator) {
|
||||
|
@ -3127,7 +3106,7 @@ Object.extend(Selector, {
|
|||
if (tagName == "*") return nodes;
|
||||
}
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (node.tagName.toUpperCase() == tagName) results.push(node);
|
||||
if (node.tagName.toUpperCase() === uTagName) results.push(node);
|
||||
return results;
|
||||
} else return root.getElementsByTagName(tagName);
|
||||
},
|
||||
|
@ -3174,16 +3153,18 @@ Object.extend(Selector, {
|
|||
return results;
|
||||
},
|
||||
|
||||
attrPresence: function(nodes, root, attr) {
|
||||
attrPresence: function(nodes, root, attr, combinator) {
|
||||
if (!nodes) nodes = root.getElementsByTagName("*");
|
||||
if (nodes && combinator) nodes = this[combinator](nodes);
|
||||
var results = [];
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (Element.hasAttribute(node, attr)) results.push(node);
|
||||
return results;
|
||||
},
|
||||
|
||||
attr: function(nodes, root, attr, value, operator) {
|
||||
attr: function(nodes, root, attr, value, operator, combinator) {
|
||||
if (!nodes) nodes = root.getElementsByTagName("*");
|
||||
if (nodes && combinator) nodes = this[combinator](nodes);
|
||||
var handler = Selector.operators[operator], results = [];
|
||||
for (var i = 0, node; node = nodes[i]; i++) {
|
||||
var nodeValue = Element.readAttribute(node, attr);
|
||||
|
@ -3262,7 +3243,7 @@ Object.extend(Selector, {
|
|||
var h = Selector.handlers, results = [], indexed = [], m;
|
||||
h.mark(nodes);
|
||||
for (var i = 0, node; node = nodes[i]; i++) {
|
||||
if (!node.parentNode._counted) {
|
||||
if (!node.parentNode._countedByPrototype) {
|
||||
h.index(node.parentNode, reverse, ofType);
|
||||
indexed.push(node.parentNode);
|
||||
}
|
||||
|
@ -3300,7 +3281,7 @@ Object.extend(Selector, {
|
|||
var exclusions = new Selector(selector).findElements(root);
|
||||
h.mark(exclusions);
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
if (!node._counted) results.push(node);
|
||||
if (!node._countedByPrototype) results.push(node);
|
||||
h.unmark(exclusions);
|
||||
return results;
|
||||
},
|
||||
|
@ -3334,11 +3315,19 @@ Object.extend(Selector, {
|
|||
'|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
|
||||
},
|
||||
|
||||
split: function(expression) {
|
||||
var expressions = [];
|
||||
expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
|
||||
expressions.push(m[1].strip());
|
||||
});
|
||||
return expressions;
|
||||
},
|
||||
|
||||
matchElements: function(elements, expression) {
|
||||
var matches = new Selector(expression).findElements(), h = Selector.handlers;
|
||||
var matches = $$(expression), h = Selector.handlers;
|
||||
h.mark(matches);
|
||||
for (var i = 0, results = [], element; element = elements[i]; i++)
|
||||
if (element._counted) results.push(element);
|
||||
if (element._countedByPrototype) results.push(element);
|
||||
h.unmark(matches);
|
||||
return results;
|
||||
},
|
||||
|
@ -3351,11 +3340,7 @@ Object.extend(Selector, {
|
|||
},
|
||||
|
||||
findChildElements: function(element, expressions) {
|
||||
var exprs = expressions.join(',');
|
||||
expressions = [];
|
||||
exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
|
||||
expressions.push(m[1].strip());
|
||||
});
|
||||
expressions = Selector.split(expressions.join(','));
|
||||
var results = [], h = Selector.handlers;
|
||||
for (var i = 0, l = expressions.length, selector; i < l; i++) {
|
||||
selector = new Selector(expressions[i].strip());
|
||||
|
@ -3366,13 +3351,22 @@ Object.extend(Selector, {
|
|||
});
|
||||
|
||||
if (Prototype.Browser.IE) {
|
||||
// IE returns comment nodes on getElementsByTagName("*").
|
||||
// Filter them out.
|
||||
Selector.handlers.concat = function(a, b) {
|
||||
for (var i = 0, node; node = b[i]; i++)
|
||||
if (node.tagName !== "!") a.push(node);
|
||||
return a;
|
||||
};
|
||||
Object.extend(Selector.handlers, {
|
||||
// IE returns comment nodes on getElementsByTagName("*").
|
||||
// Filter them out.
|
||||
concat: function(a, b) {
|
||||
for (var i = 0, node; node = b[i]; i++)
|
||||
if (node.tagName !== "!") a.push(node);
|
||||
return a;
|
||||
},
|
||||
|
||||
// IE improperly serializes _countedByPrototype in (inner|outer)HTML.
|
||||
unmark: function(nodes) {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
node.removeAttribute('_countedByPrototype');
|
||||
return nodes;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function $$() {
|
||||
|
@ -3850,9 +3844,9 @@ Object.extend(Event, (function() {
|
|||
var cache = Event.cache;
|
||||
|
||||
function getEventID(element) {
|
||||
if (element._eventID) return element._eventID;
|
||||
if (element._prototypeEventID) return element._prototypeEventID[0];
|
||||
arguments.callee.id = arguments.callee.id || 1;
|
||||
return element._eventID = ++arguments.callee.id;
|
||||
return element._prototypeEventID = [++arguments.callee.id];
|
||||
}
|
||||
|
||||
function getDOMEventName(eventName) {
|
||||
|
@ -3880,7 +3874,7 @@ Object.extend(Event, (function() {
|
|||
return false;
|
||||
|
||||
Event.extend(event);
|
||||
handler.call(element, event)
|
||||
handler.call(element, event);
|
||||
};
|
||||
|
||||
wrapper.handler = handler;
|
||||
|
@ -3962,11 +3956,12 @@ Object.extend(Event, (function() {
|
|||
if (element == document && document.createEvent && !element.dispatchEvent)
|
||||
element = document.documentElement;
|
||||
|
||||
var event;
|
||||
if (document.createEvent) {
|
||||
var event = document.createEvent("HTMLEvents");
|
||||
event = document.createEvent("HTMLEvents");
|
||||
event.initEvent("dataavailable", true, true);
|
||||
} else {
|
||||
var event = document.createEventObject();
|
||||
event = document.createEventObject();
|
||||
event.eventType = "ondataavailable";
|
||||
}
|
||||
|
||||
|
@ -3995,20 +3990,21 @@ Element.addMethods({
|
|||
Object.extend(document, {
|
||||
fire: Element.Methods.fire.methodize(),
|
||||
observe: Element.Methods.observe.methodize(),
|
||||
stopObserving: Element.Methods.stopObserving.methodize()
|
||||
stopObserving: Element.Methods.stopObserving.methodize(),
|
||||
loaded: false
|
||||
});
|
||||
|
||||
(function() {
|
||||
/* Support for the DOMContentLoaded event is based on work by Dan Webb,
|
||||
Matthias Miller, Dean Edwards and John Resig. */
|
||||
|
||||
var timer, fired = false;
|
||||
var timer;
|
||||
|
||||
function fireContentLoadedEvent() {
|
||||
if (fired) return;
|
||||
if (document.loaded) return;
|
||||
if (timer) window.clearInterval(timer);
|
||||
document.fire("dom:loaded");
|
||||
fired = true;
|
||||
document.loaded = true;
|
||||
}
|
||||
|
||||
if (document.addEventListener) {
|
||||
|
|
|
@ -22,14 +22,14 @@ module ActionView
|
|||
# number_to_phone(1235551234, :country_code => 1) # => +1-123-555-1234
|
||||
#
|
||||
# number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".")
|
||||
# => +1.123.555.1234 x 1343
|
||||
# => +1.123.555.1234 x 1343
|
||||
def number_to_phone(number, options = {})
|
||||
number = number.to_s.strip unless number.nil?
|
||||
options = options.stringify_keys
|
||||
area_code = options["area_code"] || nil
|
||||
delimiter = options["delimiter"] || "-"
|
||||
extension = options["extension"].to_s.strip || nil
|
||||
country_code = options["country_code"] || nil
|
||||
options = options.symbolize_keys
|
||||
area_code = options[:area_code] || nil
|
||||
delimiter = options[:delimiter] || "-"
|
||||
extension = options[:extension].to_s.strip || nil
|
||||
country_code = options[:country_code] || nil
|
||||
|
||||
begin
|
||||
str = ""
|
||||
|
@ -51,10 +51,10 @@ module ActionView
|
|||
#
|
||||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the level of precision (defaults to 2).
|
||||
# * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$").
|
||||
# * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$").
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
|
||||
# * <tt>:format</tt> - Sets the format of the output string (defaults to "%u%n"). The field types are:
|
||||
# * <tt>:format</tt> - Sets the format of the output string (defaults to "%u%n"). The field types are:
|
||||
#
|
||||
# %u The currency unit
|
||||
# %n The number
|
||||
|
@ -69,16 +69,25 @@ module ActionView
|
|||
# number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u")
|
||||
# # => 1234567890,50 £
|
||||
def number_to_currency(number, options = {})
|
||||
options = options.stringify_keys
|
||||
precision = options["precision"] || 2
|
||||
unit = options["unit"] || "$"
|
||||
separator = precision > 0 ? options["separator"] || "." : ""
|
||||
delimiter = options["delimiter"] || ","
|
||||
format = options["format"] || "%u%n"
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
defaults = defaults.merge(currency)
|
||||
|
||||
precision = options[:precision] || defaults[:precision]
|
||||
unit = options[:unit] || defaults[:unit]
|
||||
separator = options[:separator] || defaults[:separator]
|
||||
delimiter = options[:delimiter] || defaults[:delimiter]
|
||||
format = options[:format] || defaults[:format]
|
||||
separator = '' if precision == 0
|
||||
|
||||
begin
|
||||
parts = number_with_precision(number, precision).split('.')
|
||||
format.gsub(/%n/, number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s).gsub(/%u/, unit)
|
||||
format.gsub(/%n/, number_with_precision(number,
|
||||
:precision => precision,
|
||||
:delimiter => delimiter,
|
||||
:separator => separator)
|
||||
).gsub(/%u/, unit)
|
||||
rescue
|
||||
number
|
||||
end
|
||||
|
@ -90,96 +99,192 @@ module ActionView
|
|||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the level of precision (defaults to 3).
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
|
||||
#
|
||||
# ==== Examples
|
||||
# number_to_percentage(100) # => 100.000%
|
||||
# number_to_percentage(100, :precision => 0) # => 100%
|
||||
#
|
||||
# number_to_percentage(302.24398923423, :precision => 5)
|
||||
# # => 302.24399%
|
||||
# number_to_percentage(100) # => 100.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%
|
||||
def number_to_percentage(number, options = {})
|
||||
options = options.stringify_keys
|
||||
precision = options["precision"] || 3
|
||||
separator = options["separator"] || "."
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
defaults = defaults.merge(percentage)
|
||||
|
||||
precision = options[:precision] || defaults[:precision]
|
||||
separator = options[:separator] || defaults[:separator]
|
||||
delimiter = options[:delimiter] || defaults[:delimiter]
|
||||
|
||||
begin
|
||||
number = number_with_precision(number, precision)
|
||||
parts = number.split('.')
|
||||
if parts.at(1).nil?
|
||||
parts[0] + "%"
|
||||
else
|
||||
parts[0] + separator + parts[1].to_s + "%"
|
||||
end
|
||||
number_with_precision(number,
|
||||
:precision => precision,
|
||||
:separator => separator,
|
||||
:delimiter => delimiter) + "%"
|
||||
rescue
|
||||
number
|
||||
end
|
||||
end
|
||||
|
||||
# Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You
|
||||
# can customize the format using optional <em>delimiter</em> and <em>separator</em> parameters.
|
||||
# Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can
|
||||
# customize the format in the +options+ hash.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>delimiter</tt> - Sets the thousands delimiter (defaults to ",").
|
||||
# * <tt>separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
#
|
||||
# ==== Examples
|
||||
# number_with_delimiter(12345678) # => 12,345,678
|
||||
# number_with_delimiter(12345678.05) # => 12,345,678.05
|
||||
# number_with_delimiter(12345678, ".") # => 12.345.678
|
||||
#
|
||||
# number_with_delimiter(98765432.98, " ", ",")
|
||||
# number_with_delimiter(12345678) # => 12,345,678
|
||||
# number_with_delimiter(12345678.05) # => 12,345,678.05
|
||||
# number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678
|
||||
# number_with_delimiter(12345678, :seperator => ",") # => 12,345,678
|
||||
# number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",")
|
||||
# # => 98 765 432,98
|
||||
def number_with_delimiter(number, delimiter=",", separator=".")
|
||||
#
|
||||
# You can still use <tt>number_with_delimiter</tt> with the old API that accepts the
|
||||
# +delimiter+ as its optional second and the +separator+ as its
|
||||
# optional third parameter:
|
||||
# number_with_delimiter(12345678, " ") # => 12 345.678
|
||||
# number_with_delimiter(12345678.05, ".", ",") # => 12.345.678,05
|
||||
def number_with_delimiter(number, *args)
|
||||
options = args.extract_options!
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
|
||||
unless args.empty?
|
||||
ActiveSupport::Deprecation.warn('number_with_delimiter takes an option hash ' +
|
||||
'instead of separate delimiter and precision arguments.', caller)
|
||||
delimiter = args[0] || defaults[:delimiter]
|
||||
separator = args[1] || defaults[:separator]
|
||||
end
|
||||
|
||||
delimiter ||= (options[:delimiter] || defaults[:delimiter])
|
||||
separator ||= (options[:separator] || defaults[:separator])
|
||||
|
||||
begin
|
||||
parts = number.to_s.split('.')
|
||||
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
|
||||
parts.join separator
|
||||
parts.join(separator)
|
||||
rescue
|
||||
number
|
||||
end
|
||||
end
|
||||
|
||||
# Formats a +number+ with the specified level of +precision+ (e.g., 112.32 has a precision of 2). The default
|
||||
# level of precision is 3.
|
||||
# Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision of 2).
|
||||
# You can customize the format in the +options+ hash.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the level of precision (defaults to 3).
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
|
||||
#
|
||||
# ==== Examples
|
||||
# number_with_precision(111.2345) # => 111.235
|
||||
# number_with_precision(111.2345, 2) # => 111.23
|
||||
# number_with_precision(13, 5) # => 13.00000
|
||||
# number_with_precision(389.32314, 0) # => 389
|
||||
def number_with_precision(number, precision=3)
|
||||
"%01.#{precision}f" % ((Float(number) * (10 ** precision)).round.to_f / 10 ** precision)
|
||||
rescue
|
||||
number
|
||||
# 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(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
|
||||
# # => 1.111,23
|
||||
#
|
||||
# You can still use <tt>number_with_precision</tt> with the old API that accepts the
|
||||
# +precision+ as its optional second parameter:
|
||||
# number_with_precision(number_with_precision(111.2345, 2) # => 111.23
|
||||
def number_with_precision(number, *args)
|
||||
options = args.extract_options!
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale],
|
||||
:raise => true) rescue {}
|
||||
defaults = defaults.merge(precision_defaults)
|
||||
|
||||
unless args.empty?
|
||||
ActiveSupport::Deprecation.warn('number_with_precision takes an option hash ' +
|
||||
'instead of a separate precision argument.', caller)
|
||||
precision = args[0] || defaults[:precision]
|
||||
end
|
||||
|
||||
precision ||= (options[:precision] || defaults[:precision])
|
||||
separator ||= (options[:separator] || defaults[:separator])
|
||||
delimiter ||= (options[:delimiter] || defaults[:delimiter])
|
||||
|
||||
begin
|
||||
rounded_number = (Float(number) * (10 ** precision)).round.to_f / 10 ** precision
|
||||
number_with_delimiter("%01.#{precision}f" % rounded_number,
|
||||
:separator => separator,
|
||||
:delimiter => delimiter)
|
||||
rescue
|
||||
number
|
||||
end
|
||||
end
|
||||
|
||||
STORAGE_UNITS = %w( Bytes KB MB GB TB ).freeze
|
||||
|
||||
# Formats the bytes in +size+ into a more understandable representation
|
||||
# (e.g., giving it 1500 yields 1.5 KB). This method is useful for
|
||||
# (e.g., giving it 1500 yields 1.5 KB). This method is useful for
|
||||
# reporting file sizes to users. This method returns nil if
|
||||
# +size+ cannot be converted into a number. You can change the default
|
||||
# precision of 1 using the precision parameter +precision+.
|
||||
# +size+ cannot be converted into a number. You can customize the
|
||||
# format in the +options+ hash.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the level of precision (defaults to 1).
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
|
||||
#
|
||||
# ==== Examples
|
||||
# number_to_human_size(123) # => 123 Bytes
|
||||
# number_to_human_size(1234) # => 1.2 KB
|
||||
# number_to_human_size(12345) # => 12.1 KB
|
||||
# number_to_human_size(1234567) # => 1.2 MB
|
||||
# number_to_human_size(1234567890) # => 1.1 GB
|
||||
# number_to_human_size(1234567890123) # => 1.1 TB
|
||||
# number_to_human_size(123) # => 123 Bytes
|
||||
# number_to_human_size(1234) # => 1.2 KB
|
||||
# number_to_human_size(12345) # => 12.1 KB
|
||||
# number_to_human_size(1234567) # => 1.2 MB
|
||||
# number_to_human_size(1234567890) # => 1.1 GB
|
||||
# number_to_human_size(1234567890123) # => 1.1 TB
|
||||
# number_to_human_size(1234567, :precision => 2) # => 1.18 MB
|
||||
# number_to_human_size(483989, :precision => 0) # => 473 KB
|
||||
# number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,18 MB
|
||||
#
|
||||
# You can still use <tt>number_to_human_size</tt> with the old API that accepts the
|
||||
# +precision+ as its optional second parameter:
|
||||
# number_to_human_size(1234567, 2) # => 1.18 MB
|
||||
# number_to_human_size(483989, 0) # => 4 MB
|
||||
def number_to_human_size(size, precision=1)
|
||||
size = Kernel.Float(size)
|
||||
case
|
||||
when size.to_i == 1; "1 Byte"
|
||||
when size < 1.kilobyte; "%d Bytes" % size
|
||||
when size < 1.megabyte; "%.#{precision}f KB" % (size / 1.0.kilobyte)
|
||||
when size < 1.gigabyte; "%.#{precision}f MB" % (size / 1.0.megabyte)
|
||||
when size < 1.terabyte; "%.#{precision}f GB" % (size / 1.0.gigabyte)
|
||||
else "%.#{precision}f TB" % (size / 1.0.terabyte)
|
||||
end.sub(/([0-9]\.\d*?)0+ /, '\1 ' ).sub(/\. /,' ')
|
||||
rescue
|
||||
nil
|
||||
# number_to_human_size(483989, 0) # => 473 KB
|
||||
def number_to_human_size(number, *args)
|
||||
return number.nil? ? nil : pluralize(number.to_i, "Byte") if number.to_i < 1024
|
||||
|
||||
options = args.extract_options!
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
human = I18n.translate(:'number.human.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
defaults = defaults.merge(human)
|
||||
|
||||
unless args.empty?
|
||||
ActiveSupport::Deprecation.warn('number_to_human_size takes an option hash ' +
|
||||
'instead of a separate precision argument.', caller)
|
||||
precision = args[0] || defaults[:precision]
|
||||
end
|
||||
|
||||
precision ||= (options[:precision] || defaults[:precision])
|
||||
separator ||= (options[:separator] || defaults[:separator])
|
||||
delimiter ||= (options[:delimiter] || defaults[:delimiter])
|
||||
|
||||
max_exp = STORAGE_UNITS.size - 1
|
||||
number = Float(number)
|
||||
exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
|
||||
exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
|
||||
number /= 1024 ** exponent
|
||||
unit = STORAGE_UNITS[exponent]
|
||||
|
||||
begin
|
||||
escaped_separator = Regexp.escape(separator)
|
||||
number_with_precision(number,
|
||||
:precision => precision,
|
||||
:separator => separator,
|
||||
:delimiter => delimiter
|
||||
).sub(/(\d)(#{escaped_separator}[1-9]*)?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') + " #{unit}"
|
||||
rescue
|
||||
number
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue