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
827 changed files with 123652 additions and 11027 deletions
|
@ -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…
Add table
Reference in a new issue