Merge with trunk.

This commit is contained in:
Jason Blevins 2007-10-17 08:04:42 -04:00
commit 84b6602792
120 changed files with 2592 additions and 662 deletions

View file

@ -1,9 +1,30 @@
* TRUNK:
------------------------------------------------------------------------------
* 0.12.0:
0.12 is mainly a bugfix release. We recommend all instiki Users to upgrade.
In this version, some security holes where fixed
- An XSS vulnerability in categories
- An XSS vulnerability in <nowiki>
- fixes that Instiki allows "dangerous" operations as HTTP GETs
as well as some other small improvements.
- fixes for instiki running on mongrel
- fixes for instiki running on mongrel_cluster
We added a lot of tests, synced with Jacques Distler's version and fixed
small bugs as well. A note to Mac OSX users: use the Ruby One-Click-Installer
for OSX ( http://rubyosx.com ) or make sure you are not running into problems
with sqlite (see http://instiki.5uper.net/instiki/show/SQLite+issues+on+OSX)
------------------------------------------------------------------------------
* 0.11.pl1
- ANTISPAM:
- updated and included spam_patterns.txt
- included dnsbl_check - DNS Blackhole Lists check
[thanks to joost from http://www.spacebabies.nl ]
- included the form-spam-protection rails plugin
http://form-spam-protection.googlecode.com/svn/form_spam_protection/
- BUGFIXES:
- fix PDF output not to contain garbage chars [Jesse Newland]
@ -16,10 +37,9 @@
- lots of small bugfixes and changes
- UPDATES:
- Rails 1.2 tested and packaged with instiki
- Rails 1.2.1 tested and packaged with instiki
- updated RubyZip to 0.9.1
- updated RedCloth to 3.0.4
- updated packaged sqlite3-ruby to 1.2.0
- updated packaged sqlite3-ruby
- FEATURES:
- fix: being logged in on more Webs at once works now [Jaques Distler]

127
README
View file

@ -1,39 +1,61 @@
===What is Instiki?
Admitted, it's YetAnotherWikiClone[http://c2.com/cgi/wiki?WikiWikiClones], but with a strong focus
on simplicity of installation and running:
= Instiki
Step 1. Download
Step 2. Run "instiki"
Instiki is a wiki clone so pretty and easy to set up, you'll wonder if its really a wiki. Runs on Rails and focuses on portability and stability. Supports file uploads, PDF export, RSS, multiple users and password protection. Some use Instiki as a CMS (Content Management System) because of it's ability to export static pages.
If you are on Windows:
"Step 3. Chuckle... "There's no step three!" (TM)"
Instiki lowers the barriers of interest for when you might consider using a wiki. It's so simple to get running that you'll find yourself using it for anything -- taking notes, brainstorming, organizing a gathering.
You're now running a perfectly suitable wiki on port 2500
that'll present you with one-step setup, followed by a textarea for the home page
on http://localhost:2500
Instiki lowers the barriers of interest for when you might consider
using a wiki. It's so simple to get running that you'll find yourself
using it for anything -- taking notes, brainstorming, organizing a
gathering.
== Supported Platforms
Having said all that, if you are not on Windows, in this version of Instiki it is a somewhat different story.
Since the author has no Linux or Mac at hand, and Instiki is moving to a SQL-based backend, this is what it takes
to install (until somebody sends a patch to properly package Instiki for all those other platforms):
Instiki only relies on Ruby - the sole external dependency (it includes all other dependencies). Any OS that can run Ruby can run Instiki - that includes Windows, Linux, Mac OS X and most known Unix flavors.
3. Kill "instiki"
4. Install SQLite 3 database engine from http://www.sqlite.org/
5. Install SQLite 3 driver for Ruby from http://sqlite-ruby.rubyforge.org/
6. Install Rake from http://rake.rubyforge.org/
7. Execute rm -f db/*.db
8. Execute 'rake environment RAILS_ENV=production migrate'
9. Make an embarrassed sigh (as I do while writing this)
10. Run 'instiki' again
11. Pat yourself on the shoulder for being such a talented geek
12. At least, there is no step twelve! (TM)
Instiki on BeOS, Amiga OS, OS2, Zeta OS and support for various exotic Platforms is planned. Mostly it already works, if Ruby runs there (download the linux version in this case). Please contact parasew if you want to help out for your OS that is not listed. Please also get in touch if you are able to create an instiki package for your favorite unix or linux distribution.
== 3 easy Steps to get the Instiki experience
Step 1. Get Ruby, Download Instiki
Step 2. Run "instiki"
Step 3. Chuckle... "There's no step three!" (TM)
== Details
You need at least Ruby Version 1.8.4 installed on your System. The second dependency is a Database System, but don't worry, maybe you are already served.
=== If you are on Windows
- Get the *Ruby One-Click Installer - Windows* http://rubyforge.org/projects/rubyinstaller
- double-click instiki.bat or instiki.cmd and there you go!
if you are running Windows 95, 98 or ME and cannot get instiki to run, try Version 0.11.pl1 which is the last instiki Version to support that old-style OS's. Please update to some Unix-OS or complain to the Ruby on Rails List at http://www.ruby-forum.com/forum/3 (Rails does not support your old Windows.)
=== If you are on Mac OSX
Since the Apple guys really screwed it up, having an old Ruby Version (1.8.2) and a broken Readline Library with MacOSX Tiger, you have to
- use the Ruby One-Click-Installer for OSX ( http://rubyosx.com ) if you don't already have macports' Ruby
- make sure you read http://instiki.5uper.net/instiki/show/SQLite+issues+on+OSX
- run "ruby instiki.rb" via command-line in the directory
=== If you are on Linux
=== Any other System
- get Ruby for your System, compile if nessesary: http://ruby-lang.org
- get SQLite or compile from http://sqlite.org (you can also use mysql or any other supported database system if you want)
- run instiki
You're now running a perfectly suitable wiki on port 2500 that'll present you with one-step setup, followed by a textarea for the home page on http://localhost:2500
== Features
===Features:
* Regular expression search: Find deep stuff really fast
* Revisions: Follow the changes on every page from birth. Rollback to an earlier rev
* Export to HTML or markup in a zip: Take the entire wiki with you home or for reference
@ -42,24 +64,27 @@ to install (until somebody sends a patch to properly package Instiki for all tho
* Password-protected webs: Keep it private
* Authors: Each revision is associated with an author, so you can see who changed what
* Reference tracker: Which other pages are pointing to the current?
* Speed: Using Madelein[http://madeleine.sourceforge.net] for persistence (all pages are in memory)
* Three markup choices: Textile[http://www.textism.com/tools/textile]
(default / RedCloth[http://www.whytheluckystiff.net/ruby/redcloth]),
* Three markup choices: Textile http://www.textism.com/tools/textile
(default / RedCloth http://www.whytheluckystiff.net/ruby/redcloth ),
Markdown (BlueCloth[http://bluecloth.rubyforge.org]), and RDoc[http://rdoc.sourceforge.net/doc]
* Embedded webserver: Through WEBrick[http://www.webrick.org]
* Embedded webserver: Through WEBrick[http://www.webrick.org], also runs on Mongel if you want to.
* Internationalization: Wiki words in any latin, greek, cyrillian, or armenian characters
* Color diffs: Track changes through revisions
* Definitely can run on SQLite and MySQL
* May be able to run on Postgres, Oracle, DB2 and SqlServer. If you try this, and it works
(or, it doesn't, but you make it work) please write about it on Instiki.org.
* Runs on SQLite per default, can be configured to run on PostgreSQL, MySQL, DB2, Firebird, Openbase, Oracle, SQL Server or Sybase
== Command-line options:
===Command-line options:
* Run "ruby instiki --help"
===History:
== History:
* See CHANGELOG
===Migrating Instiki 0.10.2 storage to Instiki 0.11.0 database
== Migrating Instiki 0.10.2 storage to Instiki 0.11.0 database
1. Install Instiki 0.11 and check that it works (you should be able to create a web, edit and save a HomePage)
2. Execute
ruby script\import_storage \
@ -81,7 +106,9 @@ to install (until somebody sends a patch to properly package Instiki for all tho
The most common migration problem is this: if you open All Pages and see a lot of orphaned pages,
you forgot to run ruby script\reset_references after importing the data.
===Upgrading from Instiki-AR Beta 1
In Beta 2, we switch to ActiveRecord:Migrations. Therefore:
1. Back up your production database.
2. Open command-line session to your database and execute:
@ -93,21 +120,33 @@ Step 2 creates a table that tells to ActiveRecord:Migrations that the current ve
of this database is 1 (corresponding to Beta 1), and step 3 makes it up-to-date with
the current version of Instiki.
===Download the latest release from:
== Download the latest release from:
* http://rubyforge.org/project/showfiles.php?group_id=186
===Visit the "official" Instiki wiki:
== Visit the "official" Instiki wiki:
* http://instiki.org
===License:
== License:
* same as Ruby's
---
Authors::
Versions 0.0 to 0.9.1:: David Heinemeier Hansson
Email:: david@loudthinking.com
Weblog:: http://www.loudthinking.com
Email:: david[AT]loudthinking.com
Weblog:: http://www.loudthinking.com[http://www.loudthinking.com]
From 0.9.2 onwards:: Alexey Verkhovsky
Email:: alex@verk.info
Email:: alex[AT]verk.info
From 0.11 onwards:: Matthias Tarasiewicz and 5uper.net
Email:: parasew[AT]gmail.com
Website:: http://5uper.net[http://5uper.net]

View file

@ -219,7 +219,7 @@ end
module Instiki
module VERSION #:nodoc:
MAJOR = 0
MINOR = 12
MINOR = 13
TINY = 0
SUFFIX = '(MML+)'
PRERELEASE = 'pre' # false

View file

@ -25,7 +25,7 @@ class Page < ActiveRecord::Base
if (revisions_size > 0) && continous_revision?(time, author)
current_revision.update_attributes(:content => content, :revised_at => time)
else
revisions.create(:content => content, :author => author, :revised_at => time)
revisions.build(:content => content, :author => author, :revised_at => time)
end
save
self

2
instiki.bat Executable file
View file

@ -0,0 +1,2 @@
set PATH=.\lib\native\win32;%PATH%
ruby.exe script\server -e production

View file

@ -143,19 +143,19 @@ class PageRenderer
else
link_type = WikiReference.link_type(@revision.page.web, referenced_name)
end
references.create :referenced_name => referenced_name, :link_type => link_type
references.build :referenced_name => referenced_name, :link_type => link_type
end
include_chunks = rendering_result.find_chunks(Include)
includes = include_chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq
includes.each do |included_page_name|
references.create :referenced_name => included_page_name,
references.build :referenced_name => included_page_name,
:link_type => WikiReference::INCLUDED_PAGE
end
categories = rendering_result.find_chunks(Category).map { |cat| cat.list }.flatten
categories.each do |category|
references.create :referenced_name => category, :link_type => WikiReference::CATEGORY
references.build :referenced_name => category, :link_type => WikiReference::CATEGORY
end
end
end

View file

@ -1,5 +1,6 @@
require 'yaml'
require 'time'
require 'md5'
module ActionController
class AbstractResponse #:nodoc:

View file

@ -1,3 +1,13 @@
*1.3.5* (October 12th, 2007)
* Depend on Action Pack 1.13.5
*1.3.4* (October 4th, 2007)
* Depend on Action Pack 1.13.4
*1.3.3* (March 12th, 2007)
* Depend on Action Pack 1.13.3

View file

@ -54,7 +54,7 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "actionmailer"
s.homepage = "http://www.rubyonrails.org"
s.add_dependency('actionpack', '= 1.13.3' + PKG_BUILD)
s.add_dependency('actionpack', '= 1.13.5' + PKG_BUILD)
s.has_rdoc = true
s.requirements << 'none'

View file

@ -2,7 +2,7 @@ module ActionMailer
module VERSION #:nodoc:
MAJOR = 1
MINOR = 3
TINY = 3
TINY = 5
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -1,6 +1,55 @@
*1.13.5* (October 12th, 2007)
* Backport: allow array and hash query parameters. Array route parameters are converted/to/a/path as before. #6765, #7047, #7462 [bgipsy, Jeremy McAnally, Dan Kubb, brendan, Diego Algorta Casamayou]
* Fix in place editor's setter action with non-string fields. #7418 [Andreas]
*1.13.4* (October 4th, 2007)
* Only accept session ids from cookies, prevents session fixation attacks. [bradediger]
* Change the resource seperator from ; to / change the generated routes to use the new-style named routes. e.g. new_group_user_path(@group) instead of group_new_user_path(@group). [pixeltrix]
* Integration tests: introduce methods for other HTTP methods. #6353 [caboose]
* Improve performance of action caching. Closes #8231 [skaes]
* Fix errors with around_filters which do not yield, restore 1.1 behaviour with after filters. Closes #8891 [skaes]
After filters will *no longer* be run if an around_filter fails to yield, users relying on
this behaviour are advised to put the code in question after a yield statement in an around filter.
* Allow you to delete cookies with options. Closes #3685 [josh, Chris Wanstrath]
* Deprecate pagination. Install the classic_pagination plugin for forward compatibility, or move to the superior will_paginate plugin. #8157 [Mislav Marohnic]
* Fix filtered parameter logging with nil parameter values. #8422 [choonkeat]
* Integration tests: alias xhr to xml_http_request and add a request_method argument instead of always using POST. #7124 [Nik Wakelin, Francois Beausoleil, Wizard]
* Document caches_action. #5419 [Jarkko Laine]
* observe_form always sends the serialized form. #5271 [manfred, normelton@gmail.com]
* Update UrlWriter to accept :anchor parameter. Closes #6771. [octopod]
* Replace the current block/continuation filter chain handling by an implementation based on a simple loop. Closes #8226 [Stefan Kaes]
* Return the string representation from an Xml Builder when rendering a partial. #5044 [tpope]
* Cleaned up, corrected, and mildly expanded ActionPack documentation. Closes #7190 [jeremymcanally]
* Small collection of ActionController documentation cleanups. Closes #7319 [jeremymcanally]
* Performance: patch cgi/session/pstore to require digest/md5 once rather than per #initialize. #7583 [Stefan Kaes]
* Deprecation: verification with :redirect_to => :named_route shouldn't be deprecated. #7525 [Justin French]
*1.13.3* (March 12th, 2007)
* Apply [5709] to stable.
* Fix a bug in Routing where a parameter taken from the path of the current request could not be used as a query parameter for the next. #6752 [Nicholas Seckar]
* session_enabled? works with session :off. #6680 [Catfish]
@ -440,7 +489,7 @@
* Avoid naming collision among compiled view methods. [Jeremy Kemper]
* Fix CGI extensions when they expect string but get nil in Windows. Closes #5276 [mislav@nippur.irb.hr]
* Fix CGI extensions when they expect string but get nil in Windows. Closes #5276 [Mislav Marohnic]
* Determine the correct template_root for deeply nested components. #2841 [s.brink@web.de]

View file

@ -75,7 +75,7 @@ spec = Gem::Specification.new do |s|
s.has_rdoc = true
s.requirements << 'none'
s.add_dependency('activesupport', '= 1.4.2' + PKG_BUILD)
s.add_dependency('activesupport', '= 1.4.4' + PKG_BUILD)
s.require_path = 'lib'
s.autorequire = 'action_controller'

View file

@ -1,7 +1,7 @@
module ActionController
module Assertions
module DomAssertions
# test 2 html strings to be equivalent, i.e. identical up to reordering of attributes
# Test two HTML strings for equivalency (e.g., identical up to reordering of attributes)
def assert_dom_equal(expected, actual, message="")
clean_backtrace do
expected_dom = HTML::Document.new(expected).root
@ -11,7 +11,7 @@ module ActionController
end
end
# negated form of +assert_dom_equivalent+
# The negated form of +assert_dom_equivalent+.
def assert_dom_not_equal(expected, actual, message="")
clean_backtrace do
expected_dom = HTML::Document.new(expected).root

View file

@ -1,7 +1,7 @@
module ActionController
module Assertions
module ModelAssertions
# ensures that the passed record is valid by active record standards. returns the error messages if not
# Ensures that the passed record is valid by ActiveRecord standards and returns any error messages if it is not.
def assert_valid(record)
clean_backtrace do
assert record.valid?, record.errors.full_messages.join("\n")

View file

@ -69,6 +69,7 @@ module ActionController
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)
@ -120,6 +121,7 @@ module ActionController
end
private
# Recognizes the route for a given path.
def recognized_request_for(path, request_method = nil)
path = "/#{path}" unless path.first == '/'
@ -132,6 +134,7 @@ module ActionController
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

View file

@ -82,6 +82,7 @@ module ActionController
end
private
# Recognizes the route for a given path.
def recognized_request_for(path, request_method = nil)
path = "/#{path}" unless path.first == '/'

View file

@ -561,6 +561,8 @@ module ActionController
# RJS encodes double quotes and line breaks.
unescaped= rjs_string.gsub('\"', '"')
unescaped.gsub!('\n', "\n")
unescaped.gsub!('\076', '>')
unescaped.gsub!('\074', '<')
# RJS encodes non-ascii characters.
unescaped.gsub!(RJS_PATTERN_UNICODE_ESCAPED_CHAR) {|u| [$1.hex].pack('U*')}
unescaped

View file

@ -7,7 +7,6 @@ require 'action_controller/url_rewriter'
require 'action_controller/status_codes'
require 'drb'
require 'set'
require 'md5'
module ActionController #:nodoc:
class ActionControllerError < StandardError #:nodoc:
@ -293,6 +292,10 @@ module ActionController #:nodoc:
# Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates.
cattr_accessor :ignore_missing_templates
# Controls the resource action separator
@@resource_action_separator = "/"
cattr_accessor :resource_action_separator
# Holds the request object that's primarily used to get environment variables through access like
# <tt>request.env["REQUEST_URI"]</tt>.
attr_internal :request
@ -394,7 +397,8 @@ module ActionController #:nodoc:
elsif value.is_a?(Hash)
filtered_parameters[key] = filter_parameters(value)
elsif block_given?
key, value = key.dup, value.dup
key = key.dup
value = value.dup if value
yield key, value
filtered_parameters[key] = value
else
@ -539,6 +543,7 @@ module ActionController #:nodoc:
self.class.controller_path
end
# Test whether the session is enabled for this request.
def session_enabled?
request.session_options && request.session_options[:disabled] != false
end
@ -600,12 +605,6 @@ module ActionController #:nodoc:
# _Deprecation_ _notice_: This used to have the signatures
# <tt>render_partial(partial_path = default_template_name, object = nil, local_assigns = {})</tt> and
# <tt>render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = {})</tt>.
# == Automatic etagging
#
# Rendering will automatically insert the etag header on 200 OK responses. The etag is calculated using MD5 of the
# response body. If a request comes in that has a matching etag, the response will be changed to a 304 Not Modified
# and the response body will be set to an empty string.
#
#
# === Rendering a template
#
@ -829,8 +828,6 @@ module ActionController #:nodoc:
else
response.body = text
end
response.body
end
def render_javascript(javascript, status = nil, append_response = true) #:nodoc:

View file

@ -1,5 +1,6 @@
require 'fileutils'
require 'uri'
require 'set'
module ActionController #:nodoc:
# Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
@ -163,13 +164,24 @@ module ActionController #:nodoc:
module Actions
def self.included(base) #:nodoc:
base.extend(ClassMethods)
base.send(:attr_accessor, :rendered_action_cache)
base.class_eval do
attr_accessor :rendered_action_cache, :action_cache_path
alias_method_chain :protected_instance_variables, :action_caching
end
end
module ClassMethods #:nodoc:
def protected_instance_variables_with_action_caching
protected_instance_variables_without_action_caching + %w(@action_cache_path)
end
module ClassMethods
# Declares that +actions+ should be cached.
# See ActionController::Caching::Actions for details.
def caches_action(*actions)
return unless perform_caching
around_filter(ActionCacheFilter.new(*actions))
action_cache_filter = ActionCacheFilter.new(*actions)
before_filter action_cache_filter
after_filter action_cache_filter
end
end
@ -185,70 +197,59 @@ module ActionController #:nodoc:
end
class ActionCacheFilter #:nodoc:
def initialize(*actions, &block)
@actions = actions
def initialize(*actions)
@actions = Set.new actions
end
def before(controller)
return unless @actions.include?(controller.action_name.intern)
action_cache_path = ActionCachePath.new(controller)
if cache = controller.read_fragment(action_cache_path.path)
return unless @actions.include?(controller.action_name.to_sym)
cache_path = ActionCachePath.new(controller, {})
if cache = controller.read_fragment(cache_path.path)
controller.rendered_action_cache = true
set_content_type!(action_cache_path)
set_content_type!(controller, cache_path.extension)
controller.send(:render_text, cache)
false
else
controller.action_cache_path = cache_path
end
end
def after(controller)
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache
controller.write_fragment(ActionCachePath.path_for(controller), controller.response.body)
return if !@actions.include?(controller.action_name.to_sym) || controller.rendered_action_cache
controller.write_fragment(controller.action_cache_path.path, controller.response.body)
end
private
def set_content_type!(action_cache_path)
if extention = action_cache_path.extension
content_type = Mime::EXTENSION_LOOKUP[extention]
action_cache_path.controller.response.content_type = content_type.to_s
end
def set_content_type!(controller, extension)
controller.response.content_type = Mime::EXTENSION_LOOKUP[extension].to_s if extension
end
end
class ActionCachePath
attr_reader :controller, :options
attr_reader :path, :extension
class << self
def path_for(*args, &block)
new(*args).path
def path_for(controller, options)
new(controller, options).path
end
end
def initialize(controller, options = {})
@controller = controller
@options = options
end
def path
return @path if @path
@path = controller.url_for(options).split('://').last
normalize!
add_extension!
URI.unescape(@path)
end
def extension
@extension ||= extract_extension(controller.request.path)
@extension = extract_extension(controller.request.path)
path = controller.url_for(options).split('://').last
normalize!(path)
add_extension!(path, @extension)
@path = URI.unescape(path)
end
private
def normalize!
@path << 'index' if @path.last == '/'
def normalize!(path)
path << 'index' if path[-1] == ?/
end
def add_extension!
@path << ".#{extension}" if extension
def add_extension!(path, extension)
path << ".#{extension}" if extension
end
def extract_extension(file_path)
@ -472,7 +473,6 @@ module ActionController #:nodoc:
end
def write(name, value, options = nil) #:nodoc:
File.umask(0006)
ensure_cache_path(File.dirname(real_file_path(name)))
File.open(real_file_path(name), "wb+") { |f| f.write(value) }
rescue => e

View file

@ -0,0 +1,30 @@
# CGI::Session::PStore.initialize requires 'digest/md5' on every call.
# This makes sense when spawning processes per request, but is
# unnecessarily expensive when serving requests from a long-lived
# process.
require 'cgi/session'
require 'cgi/session/pstore'
require 'digest/md5'
class CGI::Session::PStore #:nodoc:
def initialize(session, option={})
dir = option['tmpdir'] || Dir::tmpdir
prefix = option['prefix'] || ''
id = session.session_id
md5 = Digest::MD5.hexdigest(id)[0,16]
path = dir+"/"+prefix+md5
path.untaint
if File::exist?(path)
@hash = nil
else
unless session.new_session
raise CGI::Session::NoSession, "uninitialized session"
end
@hash = {}
end
@p = ::PStore.new(path)
@p.transaction do |p|
File.chmod(0600, p.path)
end
end
end

View file

@ -65,7 +65,7 @@ class CGI #:nodoc:
if env_qs.blank? && !(uri = env_table['REQUEST_URI']).blank?
uri.split('?', 2)[1] || ''
else
env_qs
env_qs || ''
end
end
end

View file

@ -2,6 +2,7 @@ require 'action_controller/cgi_ext/cgi_ext'
require 'action_controller/cgi_ext/cookie_performance_fix'
require 'action_controller/cgi_ext/raw_post_data_fix'
require 'action_controller/cgi_ext/session_performance_fix'
require 'action_controller/cgi_ext/pstore_performance_fix'
module ActionController #:nodoc:
class Base
@ -12,8 +13,8 @@ module ActionController #:nodoc:
# (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
# lib/action_controller/session.
# * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
# * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ parameter
# of the request, or automatically generated for a new session.
# * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ cookie, or
# automatically generated for a new session.
# * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
# exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
# an ArgumentError is raised.
@ -23,6 +24,8 @@ module ActionController #:nodoc:
# server.
# * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
# * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
# * <tt>:cookie_only</tt> - if +true+ (the default), session IDs will only be accepted from cookies and not from
# the query string or POST parameters. This protects against session fixation attacks.
def self.process_cgi(cgi = CGI.new, session_options = {})
new.process_cgi(cgi, session_options)
end
@ -33,18 +36,21 @@ module ActionController #:nodoc:
end
class CgiRequest < AbstractRequest #:nodoc:
attr_accessor :cgi, :session_options
attr_accessor :cgi, :session_options, :cookie_only
class SessionFixationAttempt < StandardError; end #:nodoc:
DEFAULT_SESSION_OPTIONS = {
:database_manager => CGI::Session::PStore,
:prefix => "ruby_sess.",
:session_path => "/"
:session_path => "/",
:cookie_only => true
} unless const_defined?(:DEFAULT_SESSION_OPTIONS)
def initialize(cgi, session_options = {})
@cgi = cgi
@session_options = session_options
@env = @cgi.send(:env_table)
@cookie_only = session_options.delete :cookie_only
super()
end
@ -108,6 +114,9 @@ module ActionController #:nodoc:
@session = Hash.new
else
stale_session_check! do
if @cookie_only && request_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

View file

@ -62,9 +62,11 @@ module ActionController #:nodoc:
end
# Removes the cookie on the client machine by setting the value to an empty string
# and setting its expiration date into the past
def delete(name)
set_cookie("name" => name.to_s, "value" => "", "expires" => Time.at(0))
# and setting its expiration date into the past. Like []=, you can pass in an options
# hash to delete cookies with extra data such as a +path+.
def delete(name, options = {})
options.stringify_keys!
set_cookie(options.merge("name" => name.to_s, "value" => "", "expires" => Time.at(0)))
end
private

View file

@ -214,9 +214,10 @@ module ActionController #:nodoc:
# == Filter Chain Halting
#
# <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request
# before controller action is run. This is useful, for example, to deny
# before a controller action is run. This is useful, for example, to deny
# access to unauthenticated users or to redirect from http to https.
# Simply return false from the filter or call render or redirect.
# After filters will not be executed if the filter chain is halted.
#
# Around filters halt the request unless the action block is called.
# Given these filters
@ -238,12 +239,12 @@ module ActionController #:nodoc:
# . . /
# . #around (code after yield)
# . /
# #after (actual filter code is run)
# #after (actual filter code is run, unless the around filter does not yield)
#
# If #around returns before yielding, only #after will be run. The #before
# If #around returns before yielding, #after will still not be run. The #before
# filter and controller action will not be run. If #before returns false,
# the second half of #around and all of #after will still run but the
# action will not.
# the second half of #around and will still run but #after and the
# action will not. If #around does not yield, #after will not be run.
module ClassMethods
# The passed <tt>filters</tt> will be appended to the filter_chain and
# will execute before the action on this controller is performed.
@ -263,13 +264,13 @@ module ActionController #:nodoc:
# The passed <tt>filters</tt> will be appended to the array of filters
# that run _after_ actions on this controller are performed.
def append_after_filter(*filters, &block)
prepend_filter_to_chain(filters, :after, &block)
append_filter_to_chain(filters, :after, &block)
end
# The passed <tt>filters</tt> will be prepended to the array of filters
# that run _after_ actions on this controller are performed.
def prepend_after_filter(*filters, &block)
append_filter_to_chain(filters, :after, &block)
prepend_filter_to_chain(filters, :after, &block)
end
# Shorthand for append_after_filter since it's the most common.
@ -362,12 +363,12 @@ module ActionController #:nodoc:
# Returns a mapping between filters and the actions that may run them.
def included_actions #:nodoc:
read_inheritable_attribute("included_actions") || {}
@included_actions ||= read_inheritable_attribute("included_actions") || {}
end
# Returns a mapping between filters and actions that may not run them.
def excluded_actions #:nodoc:
read_inheritable_attribute("excluded_actions") || {}
@excluded_actions ||= read_inheritable_attribute("excluded_actions") || {}
end
# Find a filter in the filter_chain where the filter method matches the _filter_ param
@ -381,10 +382,11 @@ module ActionController #:nodoc:
# Returns true if the filter is excluded from the given action
def filter_excluded_from_action?(filter,action) #:nodoc:
if (ia = included_actions[filter]) && !ia.empty?
case
when ia = included_actions[filter]
!ia.include?(action)
else
(excluded_actions[filter] || []).include?(action)
when ea = excluded_actions[filter]
ea.include?(action)
end
end
@ -397,20 +399,28 @@ module ActionController #:nodoc:
@filter = filter
end
def type
:around
end
def before?
false
type == :before
end
def after?
false
type == :after
end
def around?
true
type == :around
end
def run(controller)
raise ActionControllerError, 'No filter type: Nothing to do here.'
end
def call(controller, &block)
raise(ActionControllerError, 'No filter type: Nothing to do here.')
run(controller)
end
end
@ -420,35 +430,38 @@ module ActionController #:nodoc:
def filter
@filter.filter
end
def around?
false
end
end
class BeforeFilterProxy < FilterProxy #:nodoc:
def before?
true
def type
:before
end
def call(controller, &block)
if false == @filter.call(controller) # must only stop if equal to false. only filters returning false are halted.
controller.halt_filter_chain(@filter, :returned_false)
else
yield
def run(controller)
# only filters returning false are halted.
if false == @filter.call(controller)
controller.send :halt_filter_chain, @filter, :returned_false
end
end
def call(controller)
yield unless run(controller)
end
end
class AfterFilterProxy < FilterProxy #:nodoc:
def after?
true
def type
:after
end
def call(controller, &block)
yield
def run(controller)
@filter.call(controller)
end
def call(controller)
yield
run(controller)
end
end
class SymbolFilter < Filter #:nodoc:
@ -485,29 +498,72 @@ module ActionController #:nodoc:
end
end
class ClassBeforeFilter < Filter #:nodoc:
def call(controller, &block)
@filter.before(controller)
end
end
class ClassAfterFilter < Filter #:nodoc:
def call(controller, &block)
@filter.after(controller)
end
end
protected
def append_filter_to_chain(filters, position = :around, &block)
write_inheritable_array('filter_chain', create_filters(filters, position, &block) )
def append_filter_to_chain(filters, filter_type = :around, &block)
pos = find_filter_append_position(filters, filter_type)
update_filter_chain(filters, filter_type, pos, &block)
end
def prepend_filter_to_chain(filters, position = :around, &block)
write_inheritable_attribute('filter_chain', create_filters(filters, position, &block) + filter_chain)
def prepend_filter_to_chain(filters, filter_type = :around, &block)
pos = find_filter_prepend_position(filters, filter_type)
update_filter_chain(filters, filter_type, pos, &block)
end
def create_filters(filters, position, &block) #:nodoc:
def update_filter_chain(filters, filter_type, pos, &block)
new_filters = create_filters(filters, filter_type, &block)
new_chain = filter_chain.insert(pos, new_filters).flatten
write_inheritable_attribute('filter_chain', new_chain)
end
def find_filter_append_position(filters, filter_type)
# appending an after filter puts it at the end of the call chain
# before and around filters go before the first after filter in the chain
unless filter_type == :after
filter_chain.each_with_index do |f,i|
return i if f.after?
end
end
return -1
end
def find_filter_prepend_position(filters, filter_type)
# prepending a before or around filter puts it at the front of the call chain
# after filters go before the first after filter in the chain
if filter_type == :after
filter_chain.each_with_index do |f,i|
return i if f.after?
end
return -1
end
return 0
end
def create_filters(filters, filter_type, &block) #:nodoc:
filters, conditions = extract_conditions(filters, &block)
filters.map! { |filter| find_or_create_filter(filter,position) }
filters.map! { |filter| find_or_create_filter(filter, filter_type) }
update_conditions(filters, conditions)
filters
end
def find_or_create_filter(filter,position)
if found_filter = find_filter(filter) { |f| f.send("#{position}?") }
def find_or_create_filter(filter, filter_type)
if found_filter = find_filter(filter) { |f| f.type == filter_type }
found_filter
else
f = class_for_filter(filter).new(filter)
f = class_for_filter(filter, filter_type).new(filter)
# apply proxy to filter if necessary
case position
case filter_type
when :before
BeforeFilterProxy.new(f)
when :after
@ -520,7 +576,7 @@ module ActionController #:nodoc:
# The determination of the filter type was once done at run time.
# This method is here to extract as much logic from the filter run time as possible
def class_for_filter(filter) #:nodoc:
def class_for_filter(filter, filter_type) #:nodoc:
case
when filter.is_a?(Symbol)
SymbolFilter
@ -534,8 +590,12 @@ module ActionController #:nodoc:
end
when filter.respond_to?(:filter)
ClassFilter
when filter.respond_to?(:before) && filter_type == :before
ClassBeforeFilter
when filter.respond_to?(:after) && filter_type == :after
ClassAfterFilter
else
raise(ActionControllerError, 'A filters must be a Symbol, Proc, Method, or object responding to filter.')
raise(ActionControllerError, 'A filter must be a Symbol, Proc, Method, or object responding to filter, after or before.')
end
end
@ -550,8 +610,8 @@ module ActionController #:nodoc:
return if conditions.empty?
if conditions[:only]
write_inheritable_hash('included_actions', condition_hash(filters, conditions[:only]))
else
write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except])) if conditions[:except]
elsif conditions[:except]
write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except]))
end
end
@ -576,9 +636,9 @@ module ActionController #:nodoc:
def remove_actions_from_included_actions!(filters,*actions)
actions = actions.flatten.map(&:to_s)
updated_hash = filters.inject(included_actions) do |hash,filter|
updated_hash = filters.inject(read_inheritable_attribute('included_actions')||{}) do |hash,filter|
ia = (hash[filter] || []) - actions
ia.blank? ? hash.delete(filter) : hash[filter] = ia
ia.empty? ? hash.delete(filter) : hash[filter] = ia
hash
end
write_inheritable_attribute('included_actions', updated_hash)
@ -595,7 +655,9 @@ module ActionController #:nodoc:
def proxy_before_and_after_filter(filter) #:nodoc:
return filter unless filter_responds_to_before_and_after(filter)
Proc.new do |controller, action|
unless filter.before(controller) == false
if filter.before(controller) == false
controller.send :halt_filter_chain, filter, :returned_false
else
begin
action.call
ensure
@ -615,46 +677,83 @@ module ActionController #:nodoc:
end
end
def perform_action_with_filters
call_filter(self.class.filter_chain, 0)
end
protected
def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
@before_filter_chain_aborted = false
process_without_filters(request, response, method, *arguments)
end
def filter_chain
self.class.filter_chain
end
def call_filter(chain, index)
return (performed? || perform_action_without_filters) if index >= chain.size
filter = chain[index]
return call_filter(chain, index.next) if self.class.filter_excluded_from_action?(filter,action_name)
halted = false
filter.call(self) do
halted = call_filter(chain, index.next)
end
halt_filter_chain(filter.filter, :no_yield) if halted == false unless @before_filter_chain_aborted
halted
end
def halt_filter_chain(filter, reason)
if logger
case reason
when :no_yield
logger.info "Filter chain halted as [#{filter.inspect}] did not yield."
when :returned_false
logger.info "Filter chain halted as [#{filter.inspect}] returned false."
end
end
@before_filter_chain_aborted = true
return false
def perform_action_with_filters
call_filters(self.class.filter_chain, 0, 0)
end
private
def call_filters(chain, index, nesting)
index = run_before_filters(chain, index, nesting)
aborted = @before_filter_chain_aborted
perform_action_without_filters unless performed? || aborted
return index if nesting != 0 || aborted
run_after_filters(chain, index)
end
def skip_excluded_filters(chain, index)
while (filter = chain[index]) && self.class.filter_excluded_from_action?(filter, action_name)
index = index.next
end
[filter, index]
end
def run_before_filters(chain, index, nesting)
while chain[index]
filter, index = skip_excluded_filters(chain, index)
break unless filter # end of call chain reached
case filter.type
when :before
filter.run(self) # invoke before filter
index = index.next
break if @before_filter_chain_aborted
when :around
yielded = false
filter.call(self) do
yielded = true
# all remaining before and around filters will be run in this call
index = call_filters(chain, index.next, nesting.next)
end
halt_filter_chain(filter, :did_not_yield) unless yielded
break
else
break # no before or around filters left
end
end
index
end
def run_after_filters(chain, index)
seen_after_filter = false
while chain[index]
filter, index = skip_excluded_filters(chain, index)
break unless filter # end of call chain reached
case filter.type
when :after
seen_after_filter = true
filter.run(self) # invoke after filter
else
# implementation error or someone has mucked with the filter chain
raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter
end
index = index.next
end
index.next
end
def halt_filter_chain(filter, reason)
@before_filter_chain_aborted = true
logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger
false
end
def process_cleanup_with_filters
if @before_filter_chain_aborted
close_session

View file

@ -172,21 +172,31 @@ module ActionController
process :head, path, parameters, headers
end
# Performs an XMLHttpRequest request with the given parameters, mimicing
# the request environment created by the Prototype library. The parameters
# may be +nil+, a Hash, or a string that is appropriately encoded
# (application/x-www-form-urlencoded or multipart/form-data). The headers
# should be a hash. The keys will automatically be upcased, with the
# prefix 'HTTP_' added if needed.
def xml_http_request(path, parameters=nil, headers=nil)
headers = (headers || {}).merge(
"X-Requested-With" => "XMLHttpRequest",
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
)
post(path, parameters, headers)
# Performs an XMLHttpRequest request with the given parameters, mirroring
# a request from the Prototype library.
#
# The request_method is :get, :post, :put, :delete or :head; the
# parameters are +nil+, a hash, or a url-encoded or multipart string;
# the headers are a hash. Keys are automatically upcased and prefixed
# with 'HTTP_' if not already.
#
# This method used to omit the request_method parameter, assuming it
# was :post. This was deprecated in Rails 1.2.4. Always pass the request
# method as the first argument.
def xml_http_request(request_method, path, parameters = nil, headers = nil)
unless request_method.is_a?(Symbol)
ActiveSupport::Deprecation.warn 'xml_http_request now takes the request_method (:get, :post, etc.) as the first argument. It used to assume :post, so add the :post argument to your existing method calls to silence this warning.'
request_method, path, parameters, headers = :post, request_method, path, parameters
end
headers ||= {}
headers['X-Requested-With'] = 'XMLHttpRequest'
headers['Accept'] = 'text/javascript, text/html, application/xml, text/xml, */*'
process(request_method, path, parameters, headers)
end
alias xhr :xml_http_request
# Returns the URL for the given options, according to the rules specified
# in the application's routes.
def url_for(options)
@ -490,7 +500,7 @@ module ActionController
@integration_session = open_session
end
%w(get post cookies assigns xml_http_request).each do |method|
%w(get post put head delete cookies assigns xml_http_request).each do |method|
define_method(method) do |*args|
reset! unless @integration_session
# reset the html_document variable, but only for new get/post calls

View file

@ -24,7 +24,7 @@ module ActionController
define_method("set_#{object}_#{attribute}") do
@item = object.to_s.camelize.constantize.find(params[:id])
@item.update_attribute(attribute, params[:value])
render :text => @item.send(attribute)
render :text => @item.send(attribute).to_s
end
end
end

View file

@ -1,7 +1,9 @@
module ActionController
# === Action Pack pagination for Active Record collections
#
# DEPRECATION WARNING: Pagination will be separated into its own plugin with Rails 2.0.
# DEPRECATION WARNING: Pagination will be moved to a plugin in Rails 2.0.
# Install the classic_pagination plugin for forward compatibility:
# script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination
#
# The Pagination module aids in the process of paging large collections of
# Active Record objects. It offers macro-style automatic fetching of your
@ -130,6 +132,8 @@ module ActionController
paginator_and_collection_for(collection_id, options)
end
deprecate :paginate => 'Pagination is moving to a plugin in Rails 2.0: script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination'
# These methods become class methods on any controller
module ClassMethods
# Creates a +before_filter+ which automatically paginates an Active
@ -148,6 +152,8 @@ module ActionController
OPTIONS[self][collection_id] = options
end
end
deprecate :paginate => 'Pagination is moving to a plugin in Rails 2.0: script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination'
end
def create_paginators_and_retrieve_collections #:nodoc:

View file

@ -50,10 +50,6 @@ module ActionController
@env['REQUEST_METHOD'].downcase.to_sym == :head
end
def headers
@env
end
# Determine whether the body of a HTTP call is URL-encoded (default)
# or matches one of the registered param_parsers.
#

View file

@ -2,7 +2,7 @@ module ActionController
module Resources
class Resource #:nodoc:
attr_reader :collection_methods, :member_methods, :new_methods
attr_reader :path_prefix, :name_prefix
attr_reader :path_prefix, :new_name_prefix
attr_reader :plural, :singular
attr_reader :options
@ -37,6 +37,26 @@ module ActionController
@nesting_path_prefix ||= "#{path}/:#{singular}_id"
end
def deprecate_name_prefix?
@name_prefix.blank? && !@new_name_prefix.blank?
end
def name_prefix
deprecate_name_prefix? ? @new_name_prefix : @name_prefix
end
def old_name_prefix
@name_prefix
end
def nesting_name_prefix
"#{new_name_prefix}#{singular}_"
end
def action_separator
@action_separator ||= Base.resource_action_separator
end
protected
def arrange_actions
@collection_methods = arrange_actions_by_methods(options.delete(:collection))
@ -52,6 +72,7 @@ module ActionController
def set_prefixes
@path_prefix = options.delete(:path_prefix)
@name_prefix = options.delete(:name_prefix)
@new_name_prefix = options.delete(:new_name_prefix)
end
def arrange_actions_by_methods(actions)
@ -178,11 +199,11 @@ module ActionController
#
# The comment resources work the same, but must now include a value for :article_id.
#
# comments_url(@article)
# comment_url(@article, @comment)
# article_comments_url(@article)
# article_comment_url(@article, @comment)
#
# comments_url(:article_id => @article)
# comment_url(:article_id => @article, :id => @comment)
# article_comments_url(:article_id => @article)
# article_comment_url(:article_id => @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.
@ -192,7 +213,7 @@ module ActionController
#
# * <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.
# 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 :collection, but for actions that operate on a specific member.
# * <tt>:new</tt> -- same as :collection, but for actions that operate on the new resource action.
#
@ -204,19 +225,19 @@ module ActionController
# # --> GET /thread/7/messages/1
#
# map.resources :messages, :collection => { :rss => :get }
# # --> GET /messages;rss (maps to the #rss action)
# # --> GET /messages/rss (maps to the #rss action)
# # also adds a named route called "rss_messages"
#
# map.resources :messages, :member => { :mark => :post }
# # --> POST /messages/1;mark (maps to the #mark action)
# # --> POST /messages/1/mark (maps to the #mark action)
# # also adds a named route called "mark_message"
#
# map.resources :messages, :new => { :preview => :post }
# # --> POST /messages/new;preview (maps to the #preview action)
# # --> POST /messages/new/preview (maps to the #preview action)
# # also adds a named route called "preview_new_message"
#
# map.resources :messages, :new => { :new => :any, :preview => :post }
# # --> POST /messages/new;preview (maps to the #preview action)
# # --> POST /messages/new/preview (maps to the #preview action)
# # also adds a named route called "preview_new_message"
# # --> /messages/new can be invoked via any request method
#
@ -235,9 +256,10 @@ module ActionController
# /account profile.
#
# See map.resources for general conventions. These are the main differences:
# - a singular name is given to map.resource. The default controller name is taken from the singular name.
# - To specify a custom plural name, use the :plural option. There is no :singular option
# - No default index, new, or create routes are created for the singleton resource controller.
# - A singular name is given to map.resource. The default controller name is taken from the singular name.
# - There is no <tt>:collection</tt> option as there is only the singleton resource.
# - There is no <tt>:singular</tt> option as the singular name is passed to map.resource.
# - 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')
#
# Example:
@ -300,7 +322,7 @@ module ActionController
map_member_actions(map, resource)
if block_given?
with_options(:path_prefix => resource.nesting_path_prefix, &block)
with_options(:path_prefix => resource.nesting_path_prefix, :new_name_prefix => resource.nesting_name_prefix, &block)
end
end
end
@ -315,7 +337,7 @@ module ActionController
map_member_actions(map, resource)
if block_given?
with_options(:path_prefix => resource.nesting_path_prefix, &block)
with_options(:path_prefix => resource.nesting_path_prefix, :new_name_prefix => resource.nesting_name_prefix, &block)
end
end
end
@ -324,8 +346,21 @@ module ActionController
resource.collection_methods.each do |method, actions|
actions.each do |action|
action_options = action_options_for(action, resource, method)
map.named_route("#{resource.name_prefix}#{action}_#{resource.plural}", "#{resource.path};#{action}", action_options)
map.named_route("formatted_#{resource.name_prefix}#{action}_#{resource.plural}", "#{resource.path}.:format;#{action}", action_options)
unless resource.old_name_prefix.blank?
map.deprecated_named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.old_name_prefix}#{action}_#{resource.plural}")
map.deprecated_named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "formatted_#{resource.old_name_prefix}#{action}_#{resource.plural}")
end
if resource.deprecate_name_prefix?
map.deprecated_named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{action}_#{resource.plural}")
map.deprecated_named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "formatted_#{action}_#{resource.plural}")
end
map.named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
map.connect("#{resource.path};#{action}", action_options)
map.connect("#{resource.path}.:format;#{action}", action_options)
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}.:format", action_options)
end
end
end
@ -335,6 +370,11 @@ module ActionController
map.named_route("#{resource.name_prefix}#{resource.plural}", resource.path, index_action_options)
map.named_route("formatted_#{resource.name_prefix}#{resource.plural}", "#{resource.path}.:format", index_action_options)
if resource.deprecate_name_prefix?
map.deprecated_named_route("#{resource.name_prefix}#{resource.plural}", "#{resource.plural}")
map.deprecated_named_route("formatted_#{resource.name_prefix}#{resource.plural}", "formatted_#{resource.plural}")
end
create_action_options = action_options_for("create", resource)
map.connect(resource.path, create_action_options)
map.connect("#{resource.path}.:format", create_action_options)
@ -351,11 +391,37 @@ module ActionController
actions.each do |action|
action_options = action_options_for(action, resource, method)
if action == :new
map.named_route("#{resource.name_prefix}new_#{resource.singular}", resource.new_path, action_options)
map.named_route("formatted_#{resource.name_prefix}new_#{resource.singular}", "#{resource.new_path}.:format", action_options)
unless resource.old_name_prefix.blank?
map.deprecated_named_route("new_#{resource.name_prefix}#{resource.singular}", "#{resource.old_name_prefix}new_#{resource.singular}")
map.deprecated_named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "formatted_#{resource.old_name_prefix}new_#{resource.singular}")
end
if resource.deprecate_name_prefix?
map.deprecated_named_route("new_#{resource.name_prefix}#{resource.singular}", "new_#{resource.singular}")
map.deprecated_named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "formatted_new_#{resource.singular}")
end
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)
else
map.named_route("#{resource.name_prefix}#{action}_new_#{resource.singular}", "#{resource.new_path};#{action}", action_options)
map.named_route("formatted_#{resource.name_prefix}#{action}_new_#{resource.singular}", "#{resource.new_path}.:format;#{action}", action_options)
unless resource.old_name_prefix.blank?
map.deprecated_named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.old_name_prefix}#{action}_new_#{resource.singular}")
map.deprecated_named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "formatted_#{resource.old_name_prefix}#{action}_new_#{resource.singular}")
end
if resource.deprecate_name_prefix?
map.deprecated_named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{action}_new_#{resource.singular}")
map.deprecated_named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "formatted_#{action}_new_#{resource.singular}")
end
map.named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
map.connect("#{resource.new_path};#{action}", action_options)
map.connect("#{resource.new_path}.:format;#{action}", action_options)
map.named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}.:format", action_options)
end
end
end
@ -365,8 +431,22 @@ module ActionController
resource.member_methods.each do |method, actions|
actions.each do |action|
action_options = action_options_for(action, resource, method)
map.named_route("#{resource.name_prefix}#{action}_#{resource.singular}", "#{resource.member_path};#{action}", action_options)
map.named_route("formatted_#{resource.name_prefix}#{action}_#{resource.singular}", "#{resource.member_path}.:format;#{action}",action_options)
unless resource.old_name_prefix.blank?
map.deprecated_named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.old_name_prefix}#{action}_#{resource.singular}")
map.deprecated_named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "formatted_#{resource.old_name_prefix}#{action}_#{resource.singular}")
end
if resource.deprecate_name_prefix?
map.deprecated_named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{action}_#{resource.singular}")
map.deprecated_named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "formatted_#{action}_#{resource.singular}")
end
map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}", action_options)
map.connect("#{resource.member_path};#{action}", action_options)
map.connect("#{resource.member_path}.:format;#{action}", action_options)
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}.:format", action_options)
end
end
@ -374,6 +454,11 @@ module ActionController
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)
if resource.deprecate_name_prefix?
map.deprecated_named_route("#{resource.name_prefix}#{resource.singular}", "#{resource.singular}")
map.deprecated_named_route("formatted_#{resource.name_prefix}#{resource.singular}", "formatted_#{resource.singular}")
end
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)

View file

@ -451,24 +451,15 @@ module ActionController
# 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
# values.
def build_query_string(hash, only_keys=nil)
def build_query_string(hash, only_keys = nil)
elements = []
only_keys ||= hash.keys
only_keys.each do |key|
value = hash[key] or next
key = CGI.escape key.to_s
if value.class == Array
key << '[]'
else
value = [ value ]
(only_keys || hash.keys).each do |key|
if value = hash[key]
elements << value.to_query(key)
end
value.each { |val| elements << "#{key}=#{CGI.escape(val.to_param.to_s)}" }
end
query_string = "?#{elements.join("&")}" unless elements.empty?
query_string || ""
elements.empty? ? '' : "?#{elements.sort * '&'}"
end
# Write the real recognition implementation and then resend the message.
@ -668,7 +659,7 @@ module ActionController
end
def extract_value
"#{local_name} = hash[:#{key}] #{"|| #{default.inspect}" if default}"
"#{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
@ -990,6 +981,10 @@ module ActionController
@set.add_named_route(name, path, options)
end
def deprecated_named_route(name, deprecated_name, options = {})
@set.add_deprecated_named_route(name, deprecated_name)
end
# Added deprecation notice for anyone who already added a named route called "root".
# It'll be used as a shortcut for map.connect '' in Rails 2.0.
def root(*args, &proc)
@ -1056,6 +1051,38 @@ module ActionController
Array(destinations).each { |dest| dest.send :include, @module }
end
def define_deprecated_named_route_methods(name, deprecated_name)
[:url, :path].each do |kind|
@module.send :module_eval, <<-end_eval # We use module_eval to avoid leaks
def #{url_helper_name(deprecated_name, kind)}(*args)
ActiveSupport::Deprecation.warn(
'The named route "#{url_helper_name(deprecated_name, kind)}" uses a format that has been deprecated. ' +
'You should use "#{url_helper_name(name, kind)}" instead.', caller
)
send :#{url_helper_name(name, kind)}, *args
end
def #{hash_access_name(deprecated_name, kind)}(*args)
ActiveSupport::Deprecation.warn(
'The named route "#{hash_access_name(deprecated_name, kind)}" uses a format that has been deprecated. ' +
'You should use "#{hash_access_name(name, kind)}" instead.', caller
)
send :#{hash_access_name(name, kind)}, *args
end
end_eval
end
end
private
def url_helper_name(name, kind = :url)
:"#{name}_#{kind}"
@ -1178,6 +1205,10 @@ module ActionController
named_routes[name] = add_route(path, options)
end
def add_deprecated_named_route(name, deprecated_name)
named_routes.define_deprecated_named_route_methods(name, deprecated_name)
end
def options_as_params(options)
# If an explicit :controller was given, always make :action explicit
# too, so that action expiry works as expected for things like
@ -1190,10 +1221,9 @@ module ActionController
#
# great fun, eh?
options_as_params = options[:controller] ? { :action => "index" } : {}
options.each do |k, value|
options_as_params[k] = value.to_param
end
options_as_params = options.clone
options_as_params[:action] ||= 'index' if options[:controller]
options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
options_as_params
end
@ -1224,6 +1254,9 @@ module ActionController
options = options_as_params(options)
expire_on = build_expiry(options, recall)
if options[:controller]
options[:controller] = options[:controller].to_s
end
# if the controller has changed, make sure it changes relative to the
# current controller module, if any. In other words, if we're currently
# on admin/get, and the new controller is 'set', the new controller

View file

@ -24,6 +24,7 @@ module ActionController #:nodoc:
attr_accessor :cookies, :session_options
attr_accessor :query_parameters, :request_parameters, :path, :session, :env
attr_accessor :host
attr_reader :request_uri_overridden
def initialize(query_parameters = nil, request_parameters = nil, session = nil)
@query_parameters = query_parameters || {}
@ -67,12 +68,14 @@ module ActionController #:nodoc:
# Used to check AbstractRequest's request_uri functionality.
# Disables the use of @path and @request_uri so superclass can handle those.
def set_REQUEST_URI(value)
@request_uri_overridden = true
@env["REQUEST_URI"] = value
@request_uri = nil
@path = nil
end
def request_uri=(uri)
@env["REQUEST_URI"] = uri
@request_uri = uri
@path = uri.split("?").first
end
@ -426,12 +429,12 @@ module ActionController #:nodoc:
end
def build_request_uri(action, parameters)
unless @request.env['REQUEST_URI']
unless @request.request_uri_overridden
options = @controller.send(:rewrite_options, parameters)
options.update(:only_path => true, :action => action)
url = ActionController::UrlRewriter.new(@request, parameters)
@request.set_REQUEST_URI(url.rewrite(options))
@request.request_uri = url.rewrite(options)
end
end

View file

@ -52,8 +52,9 @@ module ActionController
# Delete the unused options to prevent their appearance in the query string
[:protocol, :host, :port].each { |k| options.delete k }
end
anchor = "##{options.delete(:anchor)}" if options.key?(:anchor)
url << Routing::Routes.generate(options, {})
return url
return "#{url}#{anchor}"
end
end
@ -76,6 +77,7 @@ module ActionController
alias_method :to_s, :to_str
private
# Given a path and options, returns a rewritten URL string
def rewrite_url(path, options)
rewritten_url = ""
unless options[:only_path]
@ -91,6 +93,7 @@ module ActionController
rewritten_url
end
# Given a Hash of options, generates a route
def rewrite_path(options)
options = options.symbolize_keys
options.update(options[:params].symbolize_keys) if options[:params]

View file

@ -95,6 +95,7 @@ module ActionController #:nodoc:
response.headers.update(options[:add_headers]) if options[:add_headers]
unless performed?
render(options[:render]) if options[:render]
options[:redirect_to] = self.send(options[:redirect_to]) if options[:redirect_to].is_a? Symbol
redirect_to(options[:redirect_to]) if options[:redirect_to]
end
return false

View file

@ -2,7 +2,7 @@ module ActionPack #:nodoc:
module VERSION #:nodoc:
MAJOR = 1
MINOR = 13
TINY = 3
TINY = 5
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -148,7 +148,7 @@ module ActionView #:nodoc:
#
# This refreshes the sidebar, removes a person element and highlights the user list.
#
# See the ActionView::Helpers::PrototypeHelper::JavaScriptGenerator documentation for more details.
# See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details.
class Base
include ERB::Util
@ -160,7 +160,7 @@ module ActionView #:nodoc:
attr_internal *ActionController::Base::DEPRECATED_INSTANCE_VARIABLES
# Specify trim mode for the ERB compiler. Defaults to '-'.
# See ERB documentation for suitable values.
# See ERb documentation for suitable values.
@@erb_trim_mode = '-'
cattr_accessor :erb_trim_mode
@ -191,17 +191,17 @@ module ActionView #:nodoc:
end
include CompiledTemplates
# maps inline templates to their method names
# Maps inline templates to their method names
@@method_names = {}
# map method names to their compile time
# Map method names to their compile time
@@compile_time = {}
# map method names to the names passed in local assigns so far
# Map method names to the names passed in local assigns so far
@@template_args = {}
# count the number of inline templates
# Count the number of inline templates
@@inline_template_count = 0
# maps template paths without extension to their file extension returned by pick_template_extension.
# if for a given path, path.ext1 and path.ext2 exist on the file system, the order of extensions
# used by pick_template_extension determines whether ext1 or ext2 will be stored
# Maps template paths without extension to their file extension returned by pick_template_extension.
# If for a given path, path.ext1 and path.ext2 exist on the file system, the order of extensions
# used by pick_template_extension determines whether ext1 or ext2 will be stored.
@@cached_template_extension = {}
class ObjectWrapper < Struct.new(:value) #:nodoc:
@ -305,7 +305,6 @@ module ActionView #:nodoc:
# Render the provided template with the given local assigns. If the template has not been rendered with the provided
# local assigns yet, or if the template has been updated on disk, then the template will be compiled to a method.
#
# Either, but not both, of template and file_path may be nil. If file_path is given, the template
# will only be read if it has to be compiled.
#
@ -371,10 +370,12 @@ module ActionView #:nodoc:
end
private
# Builds a string holding the full path of the template including extension
def full_template_path(template_path, extension)
"#{@base_path}/#{template_path}.#{extension}"
end
# Asserts the existence of a template.
def template_exists?(template_path, extension)
file_path = full_template_path(template_path, extension)
@@method_names.has_key?(file_path) || FileTest.exists?(file_path)
@ -389,6 +390,7 @@ module ActionView #:nodoc:
@@cache_template_extensions && @@cached_template_extension[template_path]
end
# Determines the template's file extension, such as rhtml, rxml, or rjs.
def find_template_extension_for(template_path)
if match = delegate_template_exists?(template_path)
match.first.to_sym
@ -405,6 +407,7 @@ module ActionView #:nodoc:
File.read(template_path)
end
# Evaluate the local assigns and pushes them to the view.
def evaluate_assigns
unless @assigns_added
assign_variables_from_controller
@ -416,6 +419,7 @@ module ActionView #:nodoc:
handler.new(self).render(template, local_assigns)
end
# Assigns instance variables from the controller to the view.
def assign_variables_from_controller
@assigns.each { |key, value| instance_variable_set("@#{key}", value) }
end
@ -427,10 +431,10 @@ module ActionView #:nodoc:
((args = @@template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) })
end
# Check whether compilation is necessary.
# Compile if the inline template or file has not been compiled yet.
# Or if local_assigns has a new key, which isn't supported by the compiled code yet.
# Or if the file has changed on disk and checking file mods hasn't been disabled.
# Method to check whether template compilation is necessary.
# The template will be compiled if the inline template or file has not been compiled yet,
# if local_assigns has a new key, which isn't supported by the compiled code yet,
# or if the file has changed on disk and checking file mods hasn't been disabled.
def compile_template?(template, file_name, local_assigns)
method_key = file_name || template
render_symbol = @@method_names[method_key]
@ -445,14 +449,15 @@ module ActionView #:nodoc:
end
end
# Create source code for given template
# Method to create the source code for a given template.
def create_template_source(extension, template, render_symbol, locals)
if template_requires_setup?(extension)
body = case extension.to_sym
when :rxml
"controller.response.content_type ||= 'application/xml'\n" +
"xml = Builder::XmlMarkup.new(:indent => 2)\n" +
template
"xml ||= Builder::XmlMarkup.new(:indent => 2)\n" +
template +
"\nxml.target!\n"
when :rjs
"controller.response.content_type ||= 'text/javascript'\n" +
"update_page do |page|\n#{template}\nend"
@ -473,11 +478,11 @@ module ActionView #:nodoc:
"def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend"
end
def template_requires_setup?(extension)
def template_requires_setup?(extension) #:nodoc:
templates_requiring_setup.include? extension.to_s
end
def templates_requiring_setup
def templates_requiring_setup #:nodoc:
%w(rxml rjs)
end
@ -501,6 +506,7 @@ module ActionView #:nodoc:
end
end
# Compile and evaluate the template's code
def compile_template(extension, template, file_name, local_assigns)
render_symbol = assign_method_name(extension, template, file_name)
render_source = create_template_source(extension, template, render_symbol, local_assigns.keys)

View file

@ -3,14 +3,14 @@ module ActionView
# CompiledTemplates modules hold methods that have been compiled.
# Templates are compiled into these methods so that they do not need to be
# re-read and re-parsed each request.
# read and parsed for each request.
#
# Each template may be compiled into one or more methods. Each method accepts a given
# set of parameters which is used to implement local assigns passing.
#
# To use a compiled template module, create a new instance and include it into the class
# in which you want the template to be rendered.
class CompiledTemplates < Module #:nodoc:
class CompiledTemplates < Module
attr_reader :method_names
def initialize

View file

@ -13,17 +13,18 @@ module ActionView
# is a great of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form.
# In that case, it's better to use the input method and the specialized form methods in link:classes/ActionView/Helpers/FormHelper.html
module ActiveRecordHelper
# Returns a default input tag for the type of object returned by the method. Example
# (title is a VARCHAR column and holds "Hello World"):
# Returns a default input tag for the type of object returned by the method. For example, let's say you have a model
# that has an attribute +title+ of type VARCHAR column, and this instance holds "Hello World":
# input("post", "title") =>
# <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
def input(record_name, method, options = {})
InstanceTag.new(record_name, method, self).to_tag(options)
end
# Returns an entire form with input tags and everything for a specified Active Record object. Example
# (post is a new record that has a title using VARCHAR and a body using TEXT):
# form("post") =>
# Returns an entire form with all needed input tags for a specified Active Record object. For example, let's say you
# have a table model <tt>Post</tt> with attributes named <tt>title</tt> of type <tt>VARCHAR</tt> and <tt>body</tt> of type <tt>TEXT</tt>:
# form("post")
# That line would yield a form like the following:
# <form action='/post/create' method='post'>
# <p>
# <label for="post_title">Title</label><br />
@ -32,14 +33,13 @@ module ActionView
# <p>
# <label for="post_body">Body</label><br />
# <textarea cols="40" id="post_body" name="post[body]" rows="20">
# Back to the hill and over it again!
# </textarea>
# </p>
# <input type='submit' value='Create' />
# </form>
#
# It's possible to specialize the form builder by using a different action name and by supplying another
# block renderer. Example (entry is a new record that has a message attribute using VARCHAR):
# block renderer. For example, let's say you have a model <tt>Entry</tt> with an attribute <tt>message</tt> of type <tt>VARCHAR</tt>:
#
# form("entry", :action => "sign", :input_block =>
# Proc.new { |record, column| "#{column.human_name}: #{input(record, column.name)}<br />" }) =>
@ -74,16 +74,16 @@ module ActionView
content_tag('form', contents, :action => action, :method => 'post', :enctype => options[:multipart] ? 'multipart/form-data': nil)
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 DIV tag, which can be specialized to include both a +prepend_text+ and +append_text+
# to properly introduce the error and a +css_class+ to style it accordingly. Examples (post has an error message
# "can't be empty" on the title attribute):
# 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. As an example, let's say you have a model
# +post+ 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", "Title simply ", " (or it won't work)", "inputError" %> =>
# <div class="inputError">Title simply can't be empty (or it won't work)</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")
if (obj = 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)
@ -92,11 +92,11 @@ module ActionView
end
end
# Returns a string with a div containing all of the error messages for the objects located as instance variables by the names
# Returns a string with a <tt>DIV</tt> containing all of the error messages for the objects located as instance variables by the names
# given. If more than one object is specified, the errors for the objects are displayed in the order that the object names are
# provided.
#
# This div can be tailored by the following options:
# This <tt>DIV</tt> can be tailored by the following options:
#
# * <tt>header_tag</tt> - Used for the header of the error div (default: h2)
# * <tt>id</tt> - The id of the error div (default: errorExplanation)
@ -105,12 +105,12 @@ module ActionView
# any text that you prefer. If <tt>object_name</tt> is not set, the name of
# the first object will be used.
#
# Specifying one object:
# To specify the display for one object, you simply provide its name as a parameter. For example, for the +User+ model:
#
# error_messages_for 'user'
#
# Specifying more than one object (and using the name 'user' in the
# header as the <tt>object_name</tt> instead of 'user_common'):
# To specify more than one object, you simply list them; optionally, you can add an extra +object_name+ parameter, which
# be the name in the header.
#
# error_messages_for 'user_common', 'user', :object_name => 'user'
#

View file

@ -3,6 +3,16 @@ module ActionView
# Provides a set of methods for making it easier to locate problems.
module DebugHelper
# Returns a <pre>-tag set with the +object+ dumped by YAML. Very readable way to inspect an object.
# 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>
def debug(object)
begin
Marshal::dump(object)

View file

@ -2,6 +2,9 @@ module ActionView
module Helpers
module PrototypeHelper
# Method to execute an element update using Prototype.
# DEPRECATION WARNING: This helper has been depercated; use RJS instead.
# See ActionView::Helpers::PrototypeHelper::JavaScriptGenerator::GeneratorMethods for more information.
def update_element_function(element_id, options = {}, &block)
content = escape_javascript(options[:content] || '')
content = escape_javascript(capture(&block)) if block

View file

@ -250,8 +250,10 @@ module ActionView
return function
end
# Observes the field with the DOM ID specified by +field_id+ and makes
# an Ajax call when its contents have changed.
# Observes the field with the DOM ID specified by +field_id+ and calls a
# callback when its contents have changed. The default callback is an
# Ajax call. By default the value of the observed field is sent as a
# parameter with the Ajax call.
#
# Required +options+ are either of:
# <tt>:url</tt>:: +url_for+-style options for the action to call
@ -268,14 +270,24 @@ module ActionView
# <tt>:update</tt>:: Specifies the DOM ID of the element whose
# innerHTML should be updated with the
# XMLHttpRequest response text.
# <tt>:with</tt>:: A JavaScript expression specifying the
# parameters for the XMLHttpRequest. This defaults
# to 'value', which in the evaluated context
# refers to the new field value. If you specify a
# string without a "=", it'll be extended to mean
# the form key that the value should be assigned to.
# So :with => "term" gives "'term'=value". If a "=" is
# present, no extension will happen.
# <tt>:with</tt>:: A JavaScript expression specifying the parameters
# for the XMLHttpRequest. The default is to send the
# key and value of the observed field. Any custom
# expressions should return a valid URL query string.
# The value of the field is stored in the JavaScript
# variable +value+.
#
# Examples
#
# :with => "'my_custom_key=' + value"
# :with => "'person[name]=' + prompt('New name')"
# :with => "Form.Element.serialize('other-field')"
#
# Finally
# :with => 'name'
# is shorthand for
# :with => "'name=' + value"
# This essentially just changes the key of the parameter.
# <tt>:on</tt>:: Specifies which event handler to observe. By default,
# it's set to "changed" for text fields and areas and
# "click" for radio buttons and checkboxes. With this,
@ -292,10 +304,14 @@ module ActionView
end
end
# Like +observe_field+, but operates on an entire form identified by the
# DOM ID +form_id+. +options+ are the same as +observe_field+, except
# the default value of the <tt>:with</tt> option evaluates to the
# serialized (request string) value of the form.
# Observes the form with the DOM ID specified by +form_id+ and calls a
# callback when its contents have changed. The default callback is an
# Ajax call. By default all fields of the observed field are sent as
# parameters with the Ajax call.
#
# The +options+ for +observe_form+ are the same as the options for
# +observe_field+. The JavaScript variable +value+ available to the
# <tt>:with</tt> option is set to the serialized form by default.
def observe_form(form_id, options = {})
if options[:frequency]
build_observer('Form.Observer', form_id, options)
@ -660,10 +676,10 @@ module ActionView
end
def build_observer(klass, name, options = {})
if options[:with] && !options[:with].include?("=")
if options[:with] && (options[:with] !~ /[=(.]/)
options[:with] = "'#{options[:with]}=' + value"
else
options[:with] ||= 'value' if options[:update]
options[:with] ||= 'value' unless options[:function]
end
callback = options[:function] || remote_function(options)

View file

@ -6,6 +6,8 @@ class PaginationTest < ActiveRecordTestCase
class PaginationController < ActionController::Base
self.template_root = "#{File.dirname(__FILE__)}/../fixtures/"
around_filter :silence_deprecation_warnings
def simple_paginate
@topic_pages, @topics = paginate(:topics)
render :nothing => true
@ -68,6 +70,13 @@ class PaginationTest < ActiveRecordTestCase
render :nothing => true
end
def silence_deprecation_warnings
ActiveSupport::Deprecation.silence do
yield
end
end
def rescue_errors(e) raise e end
def rescue_action(e) raise end

View file

@ -19,6 +19,8 @@ class ActionPackAssertionsController < ActionController::Base
def redirect_to_controller() redirect_to :controller => "elsewhere", :action => "flash_me"; end
def redirect_to_controller_with_symbol() redirect_to :controller => :elsewhere, :action => :flash_me; end
def redirect_to_path() redirect_to '/some/path' end
def redirect_to_named_route() redirect_to route_one_url end
@ -555,6 +557,17 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
assert_redirected_to 'http://test.host/some/path'
end
def test_assert_redirection_with_symbol
process :redirect_to_controller_with_symbol
assert_nothing_raised {
assert_redirected_to :controller => "elsewhere", :action => "flash_me"
}
process :redirect_to_controller_with_symbol
assert_nothing_raised {
assert_redirected_to :controller => :elsewhere, :action => :flash_me
}
end
def test_redirected_to_with_nested_controller
@controller = Admin::InnerModuleController.new
get :redirect_to_absolute_controller

View file

@ -39,7 +39,10 @@ class AddressesTest < Test::Unit::TestCase
end
def test_list
# because pagination is deprecated
ActiveSupport::Deprecation.silence do
get :list
end
assert_equal "We only need to get this far!", @response.body.chomp
end
end

View file

@ -88,7 +88,7 @@ class ControllerInstanceTests < Test::Unit::TestCase
# Mocha adds methods to Object which are then included in the public_instance_methods
# This method hides those from the controller so the above tests won't know the difference
def hide_mocha_methods_from_controller(controller)
mocha_methods = [:expects, :metaclass, :mocha, :mocha_inspect, :reset_mocha, :stubba_object, :stubba_method, :stubs, :verify]
mocha_methods = [:expects, :metaclass, :mocha, :mocha_inspect, :reset_mocha, :stubba_object, :stubba_method, :stubs, :verify, :__is_a__, :__metaclass__]
controller.class.send(:hide_action, *mocha_methods)
end

View file

@ -97,6 +97,7 @@ class ActionCachingTestController < ActionController::Base
caches_action :index
def index
sleep 0.01
@cache_this = Time.now.to_f.to_s
render :text => @cache_this
end
@ -195,7 +196,7 @@ class ActionCacheTest < Test::Unit::TestCase
def test_xml_version_of_resource_is_treated_as_different_cache
@mock_controller.mock_url_for = 'http://example.org/posts/'
@mock_controller.mock_path = '/posts/index.xml'
path_object = @path_class.new(@mock_controller)
path_object = @path_class.new(@mock_controller, {})
assert_equal 'xml', path_object.extension
assert_equal 'example.org/posts/index.xml', path_object.path
end
@ -204,7 +205,7 @@ class ActionCacheTest < Test::Unit::TestCase
@mock_controller.mock_url_for = 'http://example.org/'
@mock_controller.mock_path = '/'
assert_equal 'example.org/index', @path_class.path_for(@mock_controller)
assert_equal 'example.org/index', @path_class.path_for(@mock_controller, {})
end
def test_file_extensions

View file

@ -31,6 +31,11 @@ class CookieTest < Test::Unit::TestCase
cookies.delete("user_name")
end
def delete_cookie_with_path
cookies.delete("user_name", :path => '/beaten')
render_text "hello world"
end
def rescue_action(e)
raise unless ActionController::MissingTemplate # No templates here, and we don't care about the output
end
@ -85,4 +90,10 @@ class CookieTest < Test::Unit::TestCase
assert_equal "david", jar["user_name"]
assert_equal nil, jar["something_else"]
end
def test_delete_cookie_with_path
get :delete_cookie_with_path
assert_equal "/beaten", @response.headers["cookie"].first.path
assert_not_equal "/", @response.headers["cookie"].first.path
end
end

View file

@ -1,6 +1,12 @@
require File.dirname(__FILE__) + '/../../abstract_unit'
class DeprecatedBaseMethodsTest < Test::Unit::TestCase
# ActiveRecord model mock to test pagination deprecation
class DummyModel
def self.find(*args) [] end
def self.count(*args) 0 end
end
class Target < ActionController::Base
def deprecated_symbol_parameter_to_url_for
redirect_to(url_for(:home_url, "superstars"))
@ -18,6 +24,11 @@ class DeprecatedBaseMethodsTest < Test::Unit::TestCase
this_method_doesnt_exist
end
def pagination
paginate :dummy_models, :class_name => 'DeprecatedBaseMethodsTest::DummyModel'
render :nothing => true
end
def rescue_action(e) raise e end
end
@ -27,6 +38,7 @@ class DeprecatedBaseMethodsTest < Test::Unit::TestCase
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@controller = Target.new
@controller.logger = Logger.new(nil) unless @controller.logger
end
def test_deprecated_symbol_parameter_to_url_for
@ -57,4 +69,10 @@ class DeprecatedBaseMethodsTest < Test::Unit::TestCase
error = Test::Unit::Error.new('testing ur doodz', e)
assert_not_deprecated { error.message }
end
def test_pagination_deprecation
assert_deprecated('svn://errtheblog.com/svn/plugins/classic_pagination') do
get :pagination
end
end
end

View file

@ -16,6 +16,7 @@ class FilterParamTest < Test::Unit::TestCase
assert @controller.respond_to?(:filter_parameters)
test_hashes = [[{},{},[]],
[{'foo'=>nil},{'foo'=>nil},[]],
[{'foo'=>'bar'},{'foo'=>'bar'},[]],
[{'foo'=>'bar'},{'foo'=>'bar'},%w'food'],
[{'foo'=>'bar'},{'foo'=>'[FILTERED]'},%w'foo'],

View file

@ -131,6 +131,14 @@ class FilterTest < Test::Unit::TestCase
before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.assigns["ran_proc_filter1"] = true }, :except => :show_without_filter) { |c| c.assigns["ran_proc_filter2"] = true}
end
class EmptyFilterChainController < TestController
self.filter_chain.clear
def show
@action_executed = true
render :text => "yawp!"
end
end
class PrependingController < TestController
prepend_before_filter :wonderful_life
# skip_before_filter :fire_flash
@ -234,6 +242,14 @@ class FilterTest < Test::Unit::TestCase
around_filter AroundFilter.new
end
class BeforeAfterClassFilterController < PrependingController
begin
filter = AroundFilter.new
before_filter filter
after_filter filter
end
end
class MixedFilterController < PrependingController
cattr_accessor :execution_log
@ -285,6 +301,101 @@ class FilterTest < Test::Unit::TestCase
end
end
class PrependingBeforeAndAfterController < ActionController::Base
prepend_before_filter :before_all
prepend_after_filter :after_all
before_filter :between_before_all_and_after_all
def before_all
@ran_filter ||= []
@ran_filter << 'before_all'
end
def after_all
@ran_filter ||= []
@ran_filter << 'after_all'
end
def between_before_all_and_after_all
@ran_filter ||= []
@ran_filter << 'between_before_all_and_after_all'
end
def show
render :text => 'hello'
end
end
class NonYieldingAroundFilterController < ActionController::Base
before_filter :filter_one
around_filter :non_yielding_filter
before_filter :filter_two
after_filter :filter_three
def index
render :inline => "index"
end
#make sure the controller complains
def rescue_action(e); raise e; end
private
def filter_one
@filters ||= []
@filters << "filter_one"
end
def filter_two
@filters << "filter_two"
end
def non_yielding_filter
@filters << "zomg it didn't yield"
@filter_return_value
end
def filter_three
@filters << "filter_three"
end
end
def test_non_yielding_around_filters_not_returning_false_do_not_raise
controller = NonYieldingAroundFilterController.new
controller.instance_variable_set "@filter_return_value", true
assert_nothing_raised do
test_process(controller, "index")
end
end
def test_non_yielding_around_filters_returning_false_do_not_raise
controller = NonYieldingAroundFilterController.new
controller.instance_variable_set "@filter_return_value", false
assert_nothing_raised do
test_process(controller, "index")
end
end
def test_after_filters_are_not_run_if_around_filter_returns_false
controller = NonYieldingAroundFilterController.new
controller.instance_variable_set "@filter_return_value", false
test_process(controller, "index")
assert_equal ["filter_one", "zomg it didn't yield"], controller.assigns['filters']
end
def test_after_filters_are_not_run_if_around_filter_does_not_yield
controller = NonYieldingAroundFilterController.new
controller.instance_variable_set "@filter_return_value", true
test_process(controller, "index")
assert_equal ["filter_one", "zomg it didn't yield"], controller.assigns['filters']
end
def test_empty_filter_chain
assert_equal 0, EmptyFilterChainController.filter_chain.size
assert test_process(EmptyFilterChainController).template.assigns['action_executed']
end
def test_added_filter_to_inheritance_graph
assert_equal [ :ensure_login ], TestController.before_filters
end
@ -373,6 +484,12 @@ class FilterTest < Test::Unit::TestCase
assert controller.template.assigns["after_ran"]
end
def test_before_after_class_filter
controller = test_process(BeforeAfterClassFilterController)
assert controller.template.assigns["before_ran"]
assert controller.template.assigns["after_ran"]
end
def test_having_properties_in_around_filter
controller = test_process(AroundFilterController)
assert_equal "before and after", controller.template.assigns["execution_log"]
@ -412,6 +529,12 @@ class FilterTest < Test::Unit::TestCase
end
end
def test_running_prepended_before_and_after_filter
assert_equal 3, PrependingBeforeAndAfterController.filter_chain.length
response = test_process(PrependingBeforeAndAfterController)
assert_equal %w( before_all between_before_all_and_after_all after_all ), response.template.assigns["ran_filter"]
end
def test_conditional_skipping_of_filters
assert_nil test_process(ConditionalSkippingController, "login").template.assigns["ran_filter"]
assert_equal %w( ensure_login find_user ), test_process(ConditionalSkippingController, "change_password").template.assigns["ran_filter"]

View file

@ -11,7 +11,7 @@ require 'stubba'
module ActionController
module Integration
class Session
def process
def process(*args)
end
def generic_url_rewriter
@ -56,8 +56,7 @@ class SessionTest < Test::Unit::TestCase
@session.expects(:get).with(path,args)
redirects = [true, true, false]
@session.stubs(:redirect?).returns(lambda { redirects.shift })
@session.stubs(:redirect?).returns(true).then.returns(true).then.returns(false)
@session.expects(:follow_redirect!).times(2)
@session.stubs(:status).returns(200)
@ -69,8 +68,7 @@ class SessionTest < Test::Unit::TestCase
@session.expects(:post).with(path,args)
redirects = [true, true, false]
@session.stubs(:redirect?).returns(lambda { redirects.shift })
@session.stubs(:redirect?).returns(true).then.returns(true).then.returns(false)
@session.expects(:follow_redirect!).times(2)
@session.stubs(:status).returns(200)
@ -134,15 +132,102 @@ class SessionTest < Test::Unit::TestCase
@session.head(path,params,headers)
end
def test_xml_http_request
def test_xml_http_request_deprecated_call
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
"X-Requested-With" => "XMLHttpRequest",
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
)
@session.expects(:post).with(path,params,headers_after_xhr)
@session.xml_http_request(path,params,headers)
@session.expects(:process).with(:post,path,params,headers_after_xhr)
assert_deprecated { @session.xml_http_request(path,params,headers) }
end
def test_xml_http_request_get
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
"X-Requested-With" => "XMLHttpRequest",
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
)
@session.expects(:process).with(:get,path,params,headers_after_xhr)
@session.xml_http_request(:get,path,params,headers)
end
def test_xml_http_request_post
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
"X-Requested-With" => "XMLHttpRequest",
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
)
@session.expects(:process).with(:post,path,params,headers_after_xhr)
@session.xml_http_request(:post,path,params,headers)
end
def test_xml_http_request_put
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
"X-Requested-With" => "XMLHttpRequest",
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
)
@session.expects(:process).with(:put,path,params,headers_after_xhr)
@session.xml_http_request(:put,path,params,headers)
end
def test_xml_http_request_delete
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
"X-Requested-With" => "XMLHttpRequest",
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
)
@session.expects(:process).with(:delete,path,params,headers_after_xhr)
@session.xml_http_request(:delete,path,params,headers)
end
def test_xml_http_request_head
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
"X-Requested-With" => "XMLHttpRequest",
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
)
@session.expects(:process).with(:head,path,params,headers_after_xhr)
@session.xml_http_request(:head,path,params,headers)
end
end
class IntegrationTestTest < Test::Unit::TestCase
def setup
@test = ::ActionController::IntegrationTest.new(:default_test)
@test.class.stubs(:fixture_table_names).returns([])
@session = @test.open_session
end
def test_opens_new_session
@test.class.expects(:fixture_table_names).times(2).returns(['foo'])
session1 = @test.open_session { |sess| }
session2 = @test.open_session # implicit session
assert_equal ::ActionController::Integration::Session, session1.class
assert_equal ::ActionController::Integration::Session, session2.class
assert_not_equal session1, session2
end
end
# Tests that integration tests don't call Controller test methods for processing.
# Integration tests have their own setup and teardown.
class IntegrationTestUsesCorrectClass < ActionController::IntegrationTest
def self.fixture_table_names
[]
end
def test_integration_methods_called
%w( get post head put delete ).each do |verb|
assert_nothing_raised("'#{verb}' should use integration test methods") { send(verb, '/') }
end
end
end
# TODO

View file

@ -69,10 +69,6 @@ class TestController < ActionController::Base
render "test/hello"
end
def heading
head :ok
end
def greeting
# let's just rely on the template
end
@ -290,50 +286,8 @@ class RenderTest < Test::Unit::TestCase
assert_equal "Goodbye, Local David", @response.body
end
def test_render_200_should_set_etag
get :render_hello_world_from_variable
assert_equal etag_for("hello david"), @response.headers['Etag']
end
def test_render_against_etag_request_should_304_when_match
@request.headers["HTTP_IF_NONE_MATCH"] = etag_for("hello david")
get :render_hello_world_from_variable
assert_equal "304 Not Modified", @response.headers['Status']
assert @response.body.empty?
end
def test_render_against_etag_request_should_200_when_no_match
@request.headers["HTTP_IF_NONE_MATCH"] = etag_for("hello somewhere else")
get :render_hello_world_from_variable
assert_equal "200 OK", @response.headers['Status']
assert !@response.body.empty?
end
def test_render_with_etag
get :render_hello_world_from_variable
expected_etag = "\"#{MD5.new("hello david").to_s}\""
assert_equal expected_etag, @response.headers['Etag']
@request.headers["HTTP_IF_NONE_MATCH"] = expected_etag
get :render_hello_world_from_variable
assert_equal "304 Not Modified", @response.headers['Status']
@request.headers["HTTP_IF_NONE_MATCH"] = "\"diftag\""
get :render_hello_world_from_variable
assert_equal "200 OK", @response.headers['Status']
end
def render_with_404_shouldnt_have_etag
get :render_custom_code
assert_nil @response.headers['Etag']
end
protected
def assert_deprecated_render(&block)
assert_deprecated(/render/, &block)
end
def etag_for(text)
"\"#{MD5.new(text).to_s}\""
end
end

View file

@ -12,6 +12,7 @@ class CommentsController < ResourcesController; end
class AccountController < ResourcesController; end
class AdminController < ResourcesController; end
class ProductsController < ResourcesController; end
class ResourcesTest < Test::Unit::TestCase
def test_should_arrange_actions
@ -63,7 +64,7 @@ class ResourcesTest < Test::Unit::TestCase
end
end
def test_multile_with_path_prefix
def test_multiple_with_path_prefix
with_restful_routing :messages, :comments, :path_prefix => '/thread/:thread_id' do
assert_simply_restful_for :messages, :path_prefix => 'thread/5/', :options => { :thread_id => '5' }
assert_simply_restful_for :comments, :path_prefix => 'thread/5/', :options => { :thread_id => '5' }
@ -78,7 +79,7 @@ class ResourcesTest < Test::Unit::TestCase
def test_with_collection_action
rss_options = {:action => 'rss'}
rss_path = "/messages;rss"
rss_path = "/messages/rss"
actions = { 'a' => :put, 'b' => :post, 'c' => :delete }
with_restful_routing :messages, :collection => { :rss => :get }.merge(actions) do
@ -86,14 +87,14 @@ class ResourcesTest < Test::Unit::TestCase
assert_routing rss_path, options.merge(rss_options)
actions.each do |action, method|
assert_recognizes(options.merge(:action => action), :path => "/messages;#{action}", :method => method)
assert_recognizes(options.merge(:action => action), :path => "/messages/#{action}", :method => method)
end
end
assert_restful_named_routes_for :messages do |options|
assert_named_route rss_path, :rss_messages_path, rss_options
actions.keys.each do |action|
assert_named_route "/messages;#{action}", "#{action}_messages_path", :action => action
assert_named_route "/messages/#{action}", "#{action}_messages_path", :action => action
end
end
end
@ -103,7 +104,7 @@ class ResourcesTest < Test::Unit::TestCase
[:put, :post].each do |method|
with_restful_routing :messages, :member => { :mark => method } do
mark_options = {:action => 'mark', :id => '1'}
mark_path = "/messages/1;mark"
mark_path = "/messages/1/mark"
assert_restful_routes_for :messages do |options|
assert_recognizes(options.merge(mark_options), :path => mark_path, :method => method)
end
@ -120,7 +121,7 @@ class ResourcesTest < Test::Unit::TestCase
with_restful_routing :messages, :member => { :mark => method, :unmark => method } do
%w(mark unmark).each do |action|
action_options = {:action => action, :id => '1'}
action_path = "/messages/1;#{action}"
action_path = "/messages/1/#{action}"
assert_restful_routes_for :messages do |options|
assert_recognizes(options.merge(action_options), :path => action_path, :method => method)
end
@ -136,7 +137,7 @@ class ResourcesTest < Test::Unit::TestCase
def test_with_new_action
with_restful_routing :messages, :new => { :preview => :post } do
preview_options = {:action => 'preview'}
preview_path = "/messages/new;preview"
preview_path = "/messages/new/preview"
assert_restful_routes_for :messages do |options|
assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post)
end
@ -178,9 +179,11 @@ class ResourcesTest < Test::Unit::TestCase
assert_simply_restful_for :threads
assert_simply_restful_for :messages,
:path_prefix => 'threads/1/',
:name_prefix => 'thread_',
:options => { :thread_id => '1' }
assert_simply_restful_for :comments,
:path_prefix => 'threads/1/messages/2/',
:name_prefix => 'thread_message_',
:options => { :thread_id => '1', :message_id => '2' }
end
end
@ -219,7 +222,7 @@ class ResourcesTest < Test::Unit::TestCase
end
assert_singleton_restful_for :admin
assert_singleton_restful_for :account, :path_prefix => 'admin/'
assert_singleton_restful_for :account, :path_prefix => 'admin/', :name_prefix => 'admin_'
end
end
@ -227,7 +230,7 @@ class ResourcesTest < Test::Unit::TestCase
[:put, :post].each do |method|
with_singleton_resources :account, :member => { :reset => method } do
reset_options = {:action => 'reset'}
reset_path = "/account;reset"
reset_path = "/account/reset"
assert_singleton_routes_for :account do |options|
assert_recognizes(options.merge(reset_options), :path => reset_path, :method => method)
end
@ -244,7 +247,7 @@ class ResourcesTest < Test::Unit::TestCase
with_singleton_resources :account, :member => { :reset => method, :disable => method } do
%w(reset disable).each do |action|
action_options = {:action => action}
action_path = "/account;#{action}"
action_path = "/account/#{action}"
assert_singleton_routes_for :account do |options|
assert_recognizes(options.merge(action_options), :path => action_path, :method => method)
end
@ -266,7 +269,7 @@ class ResourcesTest < Test::Unit::TestCase
end
assert_singleton_restful_for :account
assert_simply_restful_for :messages, :path_prefix => 'account/'
assert_simply_restful_for :messages, :path_prefix => 'account/', :name_prefix => 'account_'
end
end
@ -279,7 +282,7 @@ class ResourcesTest < Test::Unit::TestCase
end
assert_singleton_restful_for :account, :path_prefix => '7/', :options => { :site_id => '7' }
assert_simply_restful_for :messages, :path_prefix => '7/account/', :options => { :site_id => '7' }
assert_simply_restful_for :messages, :path_prefix => '7/account/', :name_prefix => 'account_', :options => { :site_id => '7' }
end
end
@ -292,7 +295,7 @@ class ResourcesTest < Test::Unit::TestCase
end
assert_simply_restful_for :threads
assert_singleton_restful_for :admin, :path_prefix => 'threads/5/', :options => { :thread_id => '5' }
assert_singleton_restful_for :admin, :path_prefix => 'threads/5/', :name_prefix => 'thread_', :options => { :thread_id => '5' }
end
end
@ -312,6 +315,181 @@ class ResourcesTest < Test::Unit::TestCase
end
end
def test_resource_action_separator
with_routing do |set|
set.draw do |map|
map.resources :messages, :collection => {:search => :get}, :new => {:preview => :any}, :name_prefix => 'thread_', :path_prefix => '/threads/:thread_id'
map.resource :account, :member => {:login => :get}, :new => {:preview => :any}, :name_prefix => 'admin_', :path_prefix => '/admin'
end
action_separator = ActionController::Base.resource_action_separator
assert_simply_restful_for :messages, :name_prefix => 'thread_', :path_prefix => 'threads/1/', :options => { :thread_id => '1' }
assert_named_route "/threads/1/messages#{action_separator}search", "search_thread_messages_path", {}
assert_named_route "/threads/1/messages/new", "new_thread_message_path", {}
assert_named_route "/threads/1/messages/new#{action_separator}preview", "preview_new_thread_message_path", {}
assert_singleton_restful_for :account, :name_prefix => 'admin_', :path_prefix => 'admin/'
assert_named_route "/admin/account#{action_separator}login", "login_admin_account_path", {}
assert_named_route "/admin/account/new", "new_admin_account_path", {}
assert_named_route "/admin/account/new#{action_separator}preview", "preview_new_admin_account_path", {}
end
end
def test_new_style_named_routes_for_resource
with_routing do |set|
set.draw do |map|
map.resources :messages, :collection => {:search => :get}, :new => {:preview => :any}, :name_prefix => 'thread_', :path_prefix => '/threads/:thread_id'
end
assert_simply_restful_for :messages, :name_prefix => 'thread_', :path_prefix => 'threads/1/', :options => { :thread_id => '1' }
assert_named_route "/threads/1/messages/search", "search_thread_messages_path", {}
assert_named_route "/threads/1/messages/new", "new_thread_message_path", {}
assert_named_route "/threads/1/messages/new/preview", "preview_new_thread_message_path", {}
end
end
def test_new_style_named_routes_for_singleton_resource
with_routing do |set|
set.draw do |map|
map.resource :account, :member => {:login => :get}, :new => {:preview => :any}, :name_prefix => 'admin_', :path_prefix => '/admin'
end
assert_singleton_restful_for :account, :name_prefix => 'admin_', :path_prefix => 'admin/'
assert_named_route "/admin/account/login", "login_admin_account_path", {}
assert_named_route "/admin/account/new", "new_admin_account_path", {}
assert_named_route "/admin/account/new/preview", "preview_new_admin_account_path", {}
end
end
def test_should_add_deprecated_named_routes_for_resource
with_routing do |set|
set.draw do |map|
map.resources :messages, :collection => {:search => :get}, :new => {:preview => :any}, :name_prefix => 'thread_', :path_prefix => '/threads/:thread_id'
end
assert_simply_restful_for :messages, :name_prefix => 'thread_', :path_prefix => 'threads/1/', :options => { :thread_id => '1' }
assert_deprecated do
assert_named_route "/threads/1/messages/search", "thread_search_messages_path", {}
assert_named_route "/threads/1/messages/new", "thread_new_message_path", {}
assert_named_route "/threads/1/messages/new/preview", "thread_preview_new_message_path", {}
end
end
end
def test_should_add_deprecated_named_routes_for_singleton_resource
with_routing do |set|
set.draw do |map|
map.resource :account, :member => {:login => :get}, :new => {:preview => :any}, :name_prefix => 'admin_', :path_prefix => '/admin'
end
assert_singleton_restful_for :account, :name_prefix => 'admin_', :path_prefix => 'admin/'
assert_deprecated do
assert_named_route "/admin/account/login", "admin_login_account_path", {}
assert_named_route "/admin/account/new", "admin_new_account_path", {}
assert_named_route "/admin/account/new/preview", "admin_preview_new_account_path", {}
end
end
end
def test_should_add_deprecated_named_routes_for_nested_resources
with_routing do |set|
set.draw do |map|
map.resources :threads do |map|
map.resources :messages do |map|
map.resources :comments
end
end
end
assert_simply_restful_for :threads
assert_simply_restful_for :messages,
:path_prefix => 'threads/1/',
:name_prefix => 'thread_',
:options => { :thread_id => '1' }
assert_simply_restful_for :comments,
:path_prefix => 'threads/1/messages/2/',
:name_prefix => 'thread_message_',
:options => { :thread_id => '1', :message_id => '2' }
assert_deprecated do
assert_named_route "/threads/1/messages", "messages_path", {}
assert_named_route "/threads/1/messages/1", "message_path", {:thread_id => '1', :id => '1'}
assert_named_route "/threads/1/messages/new", "new_message_path", {:thread_id => '1'}
assert_named_route "/threads/1/messages/1/edit", "edit_message_path", {:thread_id => '1', :id => '1'}
end
end
end
def test_should_add_deprecated_named_routes_for_nested_singleton_resources
with_routing do |set|
set.draw do |map|
map.resource :admin do |admin|
admin.resource :account
end
end
assert_singleton_restful_for :admin
assert_singleton_restful_for :account, :path_prefix => 'admin/', :name_prefix => 'admin_'
assert_deprecated do
assert_named_route "/admin/account", "account_path", {}
assert_named_route "/admin/account/new", "new_account_path", {}
assert_named_route "/admin/account/edit", "edit_account_path", {}
end
end
end
def test_should_add_deprecated_named_routes_for_nested_resources_in_singleton_resource
with_routing do |set|
set.draw do |map|
map.resource :account do |account|
account.resources :messages
end
end
assert_singleton_restful_for :account
assert_simply_restful_for :messages, :path_prefix => 'account/', :name_prefix => 'account_'
assert_deprecated do
assert_named_route "/account/messages", "messages_path", {}
assert_named_route "/account/messages/1", "message_path", {:id => '1'}
assert_named_route "/account/messages/new", "new_message_path", {}
assert_named_route "/account/messages/1/edit", "edit_message_path", {:id => '1'}
end
end
end
def test_should_add_deprecated_named_routes_for_nested_singleton_resource_in_resources
with_routing do |set|
set.draw do |map|
map.resources :threads do |thread|
thread.resource :admin
end
end
assert_simply_restful_for :threads
assert_singleton_restful_for :admin, :path_prefix => 'threads/5/', :name_prefix => 'thread_', :options => { :thread_id => '5' }
assert_deprecated do
assert_named_route "/threads/5/admin", "admin_path", {}
assert_named_route "/threads/5/admin/new", "new_admin_path", {}
assert_named_route "/threads/5/admin/edit", "edit_admin_path", {}
end
end
end
def test_should_add_deprecated_formatted_routes
with_routing do |set|
set.draw do |map|
map.resources :products, :collection => { :specials => :get }, :member => { :thumbnail => :get }
map.resource :account, :member => { :icon => :get }
end
assert_restful_routes_for :products do |options|
assert_recognizes options.merge({ :action => 'specials', :format => 'xml' }), :path => '/products.xml;specials', :method => :get
assert_recognizes options.merge({ :action => 'thumbnail', :format => 'jpg', :id => '1' }), :path => '/products/1.jpg;thumbnail', :method => :get
end
assert_singleton_restful_for :account do |options|
assert_recognizes options.merge({ :action => 'icon', :format => 'jpg' }), :path => '/account.jpg;icon', :method => :get
end
end
end
protected
def with_restful_routing(*args)
with_routing do |set|
@ -344,8 +522,8 @@ class ResourcesTest < Test::Unit::TestCase
collection_path = "/#{options[:path_prefix]}#{controller_name}"
member_path = "#{collection_path}/1"
new_path = "#{collection_path}/new"
edit_member_path = "#{member_path};edit"
formatted_edit_member_path = "#{member_path}.xml;edit"
edit_member_path = "#{member_path}/edit"
formatted_edit_member_path = "#{member_path}/edit.xml"
with_options(options[:options]) do |controller|
controller.assert_routing collection_path, :action => 'index'
@ -395,13 +573,13 @@ class ResourcesTest < Test::Unit::TestCase
name_prefix = options[:name_prefix]
assert_named_route "#{full_prefix}", "#{name_prefix}#{controller_name}_path", options[:options]
assert_named_route "#{full_prefix}/new", "#{name_prefix}new_#{singular_name}_path", options[:options]
assert_named_route "#{full_prefix}/new", "new_#{name_prefix}#{singular_name}_path", options[:options]
assert_named_route "#{full_prefix}/1", "#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1')
assert_named_route "#{full_prefix}/1;edit", "#{name_prefix}edit_#{singular_name}_path", options[:options].merge(:id => '1')
assert_named_route "#{full_prefix}/1/edit", "edit_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1')
assert_named_route "#{full_prefix}.xml", "formatted_#{name_prefix}#{controller_name}_path", options[:options].merge( :format => 'xml')
assert_named_route "#{full_prefix}/new.xml", "formatted_#{name_prefix}new_#{singular_name}_path", options[:options].merge( :format => 'xml')
assert_named_route "#{full_prefix}/new.xml", "formatted_new_#{name_prefix}#{singular_name}_path", options[:options].merge( :format => 'xml')
assert_named_route "#{full_prefix}/1.xml", "formatted_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1', :format => 'xml')
assert_named_route "#{full_prefix}/1.xml;edit", "formatted_#{name_prefix}edit_#{singular_name}_path", options[:options].merge(:id => '1', :format => 'xml')
assert_named_route "#{full_prefix}/1/edit.xml", "formatted_edit_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1', :format => 'xml')
yield options[:options] if block_given?
end
@ -410,8 +588,8 @@ class ResourcesTest < Test::Unit::TestCase
full_path = "/#{options[:path_prefix]}#{singleton_name}"
new_path = "#{full_path}/new"
edit_path = "#{full_path};edit"
formatted_edit_path = "#{full_path}.xml;edit"
edit_path = "#{full_path}/edit"
formatted_edit_path = "#{full_path}/edit.xml"
with_options options[:options] do |controller|
controller.assert_routing full_path, :action => 'show'
@ -448,13 +626,14 @@ class ResourcesTest < Test::Unit::TestCase
options[:options].delete :action
full_path = "/#{options[:path_prefix]}#{singleton_name}"
full_name = "#{options[:name_prefix]}#{singleton_name}"
assert_named_route "#{full_path}", "#{singleton_name}_path", options[:options]
assert_named_route "#{full_path}/new", "new_#{singleton_name}_path", options[:options]
assert_named_route "#{full_path};edit", "edit_#{singleton_name}_path", options[:options]
assert_named_route "#{full_path}.xml", "formatted_#{singleton_name}_path", options[:options].merge(:format => 'xml')
assert_named_route "#{full_path}/new.xml", "formatted_new_#{singleton_name}_path", options[:options].merge(:format => 'xml')
assert_named_route "#{full_path}.xml;edit", "formatted_edit_#{singleton_name}_path", options[:options].merge(:format => 'xml')
assert_named_route "#{full_path}", "#{full_name}_path", options[:options]
assert_named_route "#{full_path}/new", "new_#{full_name}_path", options[:options]
assert_named_route "#{full_path}/edit", "edit_#{full_name}_path", options[:options]
assert_named_route "#{full_path}.xml", "formatted_#{full_name}_path", options[:options].merge(:format => 'xml')
assert_named_route "#{full_path}/new.xml", "formatted_new_#{full_name}_path", options[:options].merge(:format => 'xml')
assert_named_route "#{full_path}/edit.xml", "formatted_edit_#{full_name}_path", options[:options].merge(:format => 'xml')
end
def assert_named_route(expected, route, options)

View file

@ -265,7 +265,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
map.content '/content/:query', :controller => 'content', :action => 'show'
end
exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'content', :action => 'show', :use_route => "content") }
expected_message = %[content_url failed to generate from {:action=>"show", :controller=>"content"} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: ["content", :query] - are they all satisifed?]
expected_message = "content_url failed to generate from #{{:action=>"show", :controller=>"content"}.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: [\"content\", :query] - are they all satisifed?"
assert_equal expected_message, exception.message
end
@ -946,7 +946,7 @@ class RouteTest < Test::Unit::TestCase
end
def test_expand_array_build_query_string
assert_equal '?x[]=1&x[]=2', order_query_string(@route.build_query_string(:x => [1, 2]))
assert_equal '?x%5B%5D=1&x%5B%5D=2', order_query_string(@route.build_query_string(:x => [1, 2]))
end
def test_escape_spaces_build_query_string_selected_keys

View file

@ -482,6 +482,22 @@ HTML
end
end
def test_request_uri_updates
get :test_params
uri = @request.request_uri
assert_equal @request.env['REQUEST_URI'], uri
get :test_uri
assert_not_equal uri, @request.request_uri
uri = @request.request_uri
assert_equal @request.env['REQUEST_URI'], uri
get :test_uri, :testing => true
assert_not_equal uri, @request.request_uri
uri = @request.request_uri
assert_equal @request.env['REQUEST_URI'], uri
end
protected
def with_foo_routing
with_routing do |set|

View file

@ -17,14 +17,11 @@ class UrlRewriterTests < Test::Unit::TestCase
assert_match %r(/hi/hi/2$), u
end
private
def split_query_string(str)
[str[0].chr] + str[1..-1].split(/&/).sort
end
def assert_query_equal(q1, q2)
assert_equal(split_query_string(q1), split_query_string(q2))
def test_anchor
assert_equal(
'http://test.host/c/a/i#anchor',
@rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor')
)
end
end
@ -76,6 +73,12 @@ class UrlWriterTests < Test::Unit::TestCase
)
end
def test_anchor
assert_equal('/c/a#anchor',
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor')
)
end
def test_named_route
ActionController::Routing::Routes.draw do |map|
map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
@ -112,4 +115,57 @@ class UrlWriterTests < Test::Unit::TestCase
ActionController::Routing::Routes.load!
end
def test_one_parameter
assert_equal('/c/a?param=val',
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :param => 'val')
)
end
def test_two_parameters
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2')
params = extract_params(url)
assert_equal params[0], { :p1 => 'X1' }.to_query
assert_equal params[1], { :p2 => 'Y2' }.to_query
end
def test_hash_parameter
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'})
params = extract_params(url)
assert_equal params[0], { 'query[category]' => 'prof' }.to_query
assert_equal params[1], { 'query[name]' => 'Bob' }.to_query
end
def test_array_parameter
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof'])
params = extract_params(url)
assert_equal params[0], { 'query[]' => 'Bob' }.to_query
assert_equal params[1], { 'query[]' => 'prof' }.to_query
end
def test_hash_recursive_parameters
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'})
params = extract_params(url)
assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
assert_equal params[2], { 'query[person][position]' => 'prof' }.to_query
end
def test_hash_recursive_and_array_parameters
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'})
assert_match %r(^/c/a/101), url
params = extract_params(url)
assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query
assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query
end
def test_path_generation_for_symbol_parameter_keys
assert_generates("/image", :controller=> :image)
end
private
def extract_params(url)
url.split('?', 2).last.split('&')
end
end

View file

@ -34,10 +34,17 @@ class VerificationTest < Test::Unit::TestCase
verify :only => :must_be_post, :method => :post, :render => { :status => 405, :text => "Must be post" }, :add_headers => { "Allow" => "POST" }
verify :only => :guarded_one_for_named_route_test, :params => "one",
:redirect_to => :foo_url
def guarded_one
render :text => "#{params[:one]}"
end
def guarded_one_for_named_route_test
render :text => "#{params[:one]}"
end
def guarded_with_flash
render :text => "#{params[:one]}"
end
@ -94,6 +101,14 @@ class VerificationTest < Test::Unit::TestCase
@controller = TestController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
ActionController::Routing::Routes.add_named_route :foo, '/foo', :controller => 'test', :action => 'foo'
end
def test_no_deprecation_warning_for_named_route
assert_not_deprecated do
get :guarded_one_for_named_route_test, :two => "not one"
assert_redirected_to '/foo'
end
end
def test_guarded_one_with_prereqs

View file

@ -1,3 +1,4 @@
xml.html do
xml.p "Hello"
end
"String return value"

View file

@ -165,7 +165,12 @@ class AssetTagHelperTest < Test::Unit::TestCase
def test_preset_empty_asset_id
Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/")
# on windows, setting ENV["XXX"] to "" makes ENV["XXX"] return nil
if RUBY_PLATFORM =~ /win32/
ENV["RAILS_ASSET_ID"] = " "
else
ENV["RAILS_ASSET_ID"] = ""
end
assert_equal %(<img alt="Rails" src="/images/rails.png" />), image_tag("rails.png")
end

View file

@ -71,7 +71,12 @@ class CompiledTemplateTests < Test::Unit::TestCase
end
def test_compile_time
`echo '#{@a}' > #{@a}; echo '#{@b}' > #{@b}; ln -s #{@a} #{@s}`
File.open(@a, "w"){|f| f.puts @a}
File.open(@b, "w"){|f| f.puts @b}
# windows doesn't support symlinks (even under cygwin)
windows = (RUBY_PLATFORM =~ /win32/)
`ln -s #{@a} #{@s}` unless windows
v = ActionView::Base.new
v.base_path = '.'
@ -79,47 +84,54 @@ class CompiledTemplateTests < Test::Unit::TestCase
sleep 1
t = Time.now
sleep 1
v.compile_and_render_template(:rhtml, '', @a)
v.compile_and_render_template(:rhtml, '', @b)
v.compile_and_render_template(:rhtml, '', @s)
v.compile_and_render_template(:rhtml, '', @s) unless windows
a_n = v.method_names[@a]
b_n = v.method_names[@b]
s_n = v.method_names[@s]
s_n = v.method_names[@s] unless windows
ct_a = v.compile_time[a_n]
ct_b = v.compile_time[b_n]
ct_s = v.compile_time[s_n] unless windows
# all of the files have changed since last compile
assert v.compile_time[a_n] > t
assert v.compile_time[b_n] > t
assert v.compile_time[s_n] > t
assert v.compile_time[s_n] > t unless windows
sleep 1
t = Time.now
v.compile_and_render_template(:rhtml, '', @a)
v.compile_and_render_template(:rhtml, '', @b)
v.compile_and_render_template(:rhtml, '', @s)
v.compile_and_render_template(:rhtml, '', @s) unless windows
# none of the files have changed since last compile
assert v.compile_time[a_n] < t
assert v.compile_time[b_n] < t
assert v.compile_time[s_n] < t
# so they should not have been recmpiled
assert_equal ct_a, v.compile_time[a_n]
assert_equal ct_b, v.compile_time[b_n]
assert_equal ct_s, v.compile_time[s_n] unless windows
`rm #{@s}; ln -s #{@b} #{@s}`
`rm #{@s}; ln -s #{@b} #{@s}` unless windows
v.compile_and_render_template(:rhtml, '', @a)
v.compile_and_render_template(:rhtml, '', @b)
v.compile_and_render_template(:rhtml, '', @s)
v.compile_and_render_template(:rhtml, '', @s) unless windows
# the symlink has changed since last compile
assert v.compile_time[a_n] < t
assert v.compile_time[b_n] < t
assert v.compile_time[s_n] > t
assert_equal ct_a, v.compile_time[a_n]
assert_equal ct_b, v.compile_time[b_n]
assert v.compile_time[s_n] > t unless windows
sleep 1
`touch #{@b}`
FileUtils.touch @b
t = Time.now
sleep 1
v.compile_and_render_template(:rhtml, '', @a)
v.compile_and_render_template(:rhtml, '', @b)
v.compile_and_render_template(:rhtml, '', @s)
v.compile_and_render_template(:rhtml, '', @s) unless windows
# the file at the end of the symlink has changed since last compile
# both the symlink and the file at the end of it should be recompiled
assert v.compile_time[a_n] < t
assert v.compile_time[b_n] > t
assert v.compile_time[s_n] > t
assert v.compile_time[s_n] > t unless windows
end
end

View file

@ -36,14 +36,14 @@ class JavaScriptHelperTest < Test::Unit::TestCase
html = link_to_function( "Greet me!" ) do |page|
page.replace_html 'header', "<h1>Greetings</h1>"
end
assert_dom_equal %(<a href="#" onclick="Element.update(&quot;header&quot;, &quot;&lt;h1&gt;Greetings&lt;/h1&gt;&quot;);; return false;">Greet me!</a>), html
assert_dom_equal %q(<a href="#" onclick="Element.update(&quot;header&quot;, &quot;\074h1\076Greetings\074/h1\076&quot;);; return false;">Greet me!</a>), html
end
def test_link_to_function_with_rjs_block_and_options
html = link_to_function( "Greet me!", :class => "updater" ) do |page|
page.replace_html 'header', "<h1>Greetings</h1>"
end
assert_dom_equal %(<a href="#" class="updater" onclick="Element.update(&quot;header&quot;, &quot;&lt;h1&gt;Greetings&lt;/h1&gt;&quot;);; return false;">Greet me!</a>), html
assert_dom_equal %q(<a href="#" class="updater" onclick="Element.update(&quot;header&quot;, &quot;\074h1\076Greetings\074/h1\076&quot;);; return false;">Greet me!</a>), html
end
def test_button_to_function
@ -55,13 +55,13 @@ class JavaScriptHelperTest < Test::Unit::TestCase
html = button_to_function( "Greet me!" ) do |page|
page.replace_html 'header', "<h1>Greetings</h1>"
end
assert_dom_equal %(<input type="button" onclick="Element.update(&quot;header&quot;, &quot;&lt;h1&gt;Greetings&lt;/h1&gt;&quot;);;" value="Greet me!" />), html
assert_dom_equal %q(<input type="button" onclick="Element.update(&quot;header&quot;, &quot;\074h1\076Greetings\074/h1\076&quot;);;" value="Greet me!" />), html
end
def test_button_to_function_with_rjs_block_and_options
html = button_to_function( "Greet me!", :class => "greeter" ) do |page|
page.replace_html 'header', "<h1>Greetings</h1>"
end
assert_dom_equal %(<input type="button" class="greeter" onclick="Element.update(&quot;header&quot;, &quot;&lt;h1&gt;Greetings&lt;/h1&gt;&quot;);;" value="Greet me!" />), html
assert_dom_equal %q(<input type="button" class="greeter" onclick="Element.update(&quot;header&quot;, &quot;\074h1\076Greetings\074/h1\076&quot;);;" value="Greet me!" />), html
end
end

View file

@ -22,7 +22,7 @@ class NumberHelperTest < Test::Unit::TestCase
def test_number_to_currency
assert_equal("$1,234,567,890.50", number_to_currency(1234567890.50))
assert_equal("$1,234,567,890.51", number_to_currency(1234567890.506))
assert_equal("$1,234,567,890", number_to_currency(1234567890.50, {:precision => 0}))
assert_equal("$1,234,567,891", number_to_currency(1234567890.51, {:precision => 0}))
assert_equal("$1,234,567,890.5", number_to_currency(1234567890.50, {:precision => 1}))
assert_equal("&pound;1234567890,50", number_to_currency(1234567890.50, {:unit => "&pound;", :separator => ",", :delimiter => ""}))
assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50"))

View file

@ -125,7 +125,7 @@ class PrototypeHelperTest < Test::Unit::TestCase
end
def test_observe_field
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/reorder_if_empty', {asynchronous:true, evalScripts:true})})\n//]]>\n</script>),
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/reorder_if_empty', {asynchronous:true, evalScripts:true, parameters:value})})\n//]]>\n</script>),
observe_field("glass", :frequency => 5.minutes, :url => { :action => "reorder_if_empty" })
end
@ -135,7 +135,7 @@ class PrototypeHelperTest < Test::Unit::TestCase
end
def test_observe_form
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {new Ajax.Request('http://www.example.com/cart_changed', {asynchronous:true, evalScripts:true})})\n//]]>\n</script>),
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {new Ajax.Request('http://www.example.com/cart_changed', {asynchronous:true, evalScripts:true, parameters:value})})\n//]]>\n</script>),
observe_form("cart", :frequency => 2, :url => { :action => "cart_changed" })
end
@ -170,23 +170,23 @@ class JavaScriptGeneratorTest < Test::Unit::TestCase
end
def test_insert_html_with_string
assert_equal 'new Insertion.Top("element", "<p>This is a test</p>");',
assert_equal 'new Insertion.Top("element", "\074p\076This is a test\074/p\076");',
@generator.insert_html(:top, 'element', '<p>This is a test</p>')
assert_equal 'new Insertion.Bottom("element", "<p>This is a test</p>");',
assert_equal 'new Insertion.Bottom("element", "\074p\076This is a test\074/p\076");',
@generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
assert_equal 'new Insertion.Before("element", "<p>This is a test</p>");',
assert_equal 'new Insertion.Before("element", "\074p\076This is a test\074/p\076");',
@generator.insert_html(:before, 'element', '<p>This is a test</p>')
assert_equal 'new Insertion.After("element", "<p>This is a test</p>");',
assert_equal 'new Insertion.After("element", "\074p\076This is a test\074/p\076");',
@generator.insert_html(:after, 'element', '<p>This is a test</p>')
end
def test_replace_html_with_string
assert_equal 'Element.update("element", "<p>This is a test</p>");',
assert_equal 'Element.update("element", "\074p\076This is a test\074/p\076");',
@generator.replace_html('element', '<p>This is a test</p>')
end
def test_replace_element_with_string
assert_equal 'Element.replace("element", "<div id=\"element\"><p>This is a test</p></div>");',
assert_equal 'Element.replace("element", "\074div id=\"element\"\076\074p\076This is a test\074/p\076\074/div\076");',
@generator.replace('element', '<div id="element"><p>This is a test</p></div>')
end
@ -241,12 +241,12 @@ class JavaScriptGeneratorTest < Test::Unit::TestCase
@generator.remove('foo', 'bar')
@generator.replace_html('baz', '<p>This is a test</p>')
assert_equal <<-EOS.chomp, @generator.to_s
new Insertion.Top("element", "<p>This is a test</p>");
new Insertion.Bottom("element", "<p>This is a test</p>");
expected = %q(new Insertion.Top("element", "\074p\076This is a test\074/p\076");
new Insertion.Bottom("element", "\074p\076This is a test\074/p\076");
["foo", "bar"].each(Element.remove);
Element.update("baz", "<p>This is a test</p>");
EOS
Element.update("baz", "\074p\076This is a test\074/p\076");)
assert_equal expected, @generator.to_s
end
def test_element_access

View file

@ -1,3 +1,17 @@
*1.2.5* (October 12th, 2007)
* Depend on Action Pack 1.13.5
* Depend on Active Record 1.15.5
*1.2.4* (October 4th, 2007)
* Depend on Action Pack 1.13.4
* Depend on Active Record 1.15.4
*1.2.3* (March 12th, 2007)
* Depend on Action Pack 1.13.3

View file

@ -71,8 +71,8 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "aws"
s.homepage = "http://www.rubyonrails.org"
s.add_dependency('actionpack', '= 1.13.3' + PKG_BUILD)
s.add_dependency('activerecord', '= 1.15.3' + PKG_BUILD)
s.add_dependency('actionpack', '= 1.13.5' + PKG_BUILD)
s.add_dependency('activerecord', '= 1.15.5' + PKG_BUILD)
s.has_rdoc = true
s.requirements << 'none'

View file

@ -2,7 +2,7 @@ module ActionWebService
module VERSION #:nodoc:
MAJOR = 1
MINOR = 2
TINY = 3
TINY = 5
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -1,3 +1,37 @@
*1.15.5* (October 12th, 2007)
* Depend on Action Pack 1.4.4
*1.15.4* (October 4th, 2007)
* Fix #count on a has_many :through association so that it recognizes the :uniq option. Closes #8801 [lifofifo]
* Don't clobber includes passed to has_many.count [danger]
* Make sure has_many uses :include when counting [danger]
* Save associated records only if the association is already loaded. #8713 [blaine]
* Changing the :default Date format doesn't break date quoting. #6312 [bshand, Elias]
* Allow nil serialized attributes with a set class constraint. #7293 [sandofsky]
* belongs_to assignment creates a new proxy rather than modifying its target in-place. #8412 [mmangino@elevatedrails.com]
* Fix column type detection while loading fixtures. Closes #7987 [roderickvd]
* Document deep eager includes. #6267 [Josh Susser, Dan Manges]
* Oracle: extract column length for CHAR also. #7866 [ymendel]
* Small additions and fixes for ActiveRecord documentation. Closes #7342 [jeremymcanally]
* SQLite: binary escaping works with $KCODE='u'. #7862 [tsuka]
* Improved cloning performance by relying less on exception raising #8159 [Blaine]
*1.15.3* (March 12th, 2007)
* Allow a polymorphic :source for has_many :through associations. Closes #7143 [protocool]

View file

@ -151,7 +151,7 @@ spec = Gem::Specification.new do |s|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
s.add_dependency('activesupport', '= 1.4.2' + PKG_BUILD)
s.add_dependency('activesupport', '= 1.4.4' + PKG_BUILD)
s.files.delete "test/fixtures/fixture_database.sqlite"
s.files.delete "test/fixtures/fixture_database_2.sqlite"

View file

@ -63,7 +63,7 @@ module ActiveRecord
#{scope_condition_method}
after_destroy :remove_from_list
before_destroy :remove_from_list
before_create :add_to_list_bottom
EOV
end
@ -74,6 +74,7 @@ module ActiveRecord
# lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return true if that chapter is
# the first in the list of all chapters.
module InstanceMethods
# Insert the item at the given position (defaults to the top position of 1).
def insert_at(position = 1)
insert_at_position(position)
end
@ -118,8 +119,12 @@ module ActiveRecord
end
end
# Removes the item from the list.
def remove_from_list
decrement_positions_on_lower_items if in_list?
if in_list?
decrement_positions_on_lower_items
update_attribute position_column, nil
end
end
# Increase the position of this item without adjusting the rest of the list.
@ -162,6 +167,7 @@ module ActiveRecord
)
end
# Test if this record is in a list
def in_list?
!send(position_column).nil?
end
@ -178,21 +184,26 @@ module ActiveRecord
# Overwrite this method to define the scope of the list changes
def scope_condition() "1" end
# Returns the bottom position number in the list.
# bottom_position_in_list # => 2
def bottom_position_in_list(except = nil)
item = bottom_item(except)
item ? item.send(position_column) : 0
end
# Returns the bottom item
def bottom_item(except = nil)
conditions = scope_condition
conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
end
# Forces item to assume the bottom position in the list.
def assume_bottom_position
update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
end
# Forces item to assume the top position in the list.
def assume_top_position
update_attribute(position_column, 1)
end
@ -227,6 +238,7 @@ module ActiveRecord
)
end
# Increments position (<tt>position_column</tt>) of all items in the list.
def increment_positions_on_all_items
acts_as_list_class.update_all(
"#{position_column} = (#{position_column} + 1)", "#{scope_condition}"

View file

@ -70,16 +70,23 @@ module ActiveRecord
nodes
end
# Returns the root node of the tree.
def root
node = self
node = node.parent while node.parent
node
end
# Returns all siblings of the current node.
#
# subchild1.siblings # => [subchild2]
def siblings
self_and_siblings - [self]
end
# Returns all siblings and a reference to the current node.
#
# subchild1.self_and_siblings # => [subchild1, subchild2]
def self_and_siblings
parent ? parent.children : self.class.roots
end

View file

@ -352,7 +352,15 @@ module ActiveRecord
# for post in Post.find(:all, :include => [ :author, :comments ])
#
# That'll add another join along the lines of: LEFT OUTER JOIN comments ON comments.post_id = posts.id. And we'll be down to 1 query.
# But that shouldn't fool you to think that you can pull out huge amounts of data with no performance penalty just because you've reduced
#
# To include a deep hierarchy of associations, using a hash:
#
# for post in Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ])
#
# That'll grab not only all the comments but all their authors and gravatar pictures. You can mix and match
# symbols, arrays and hashes in any combination to describe the associations you want to load.
#
# All of this power shouldn't fool you into thinking that you can pull out huge amounts of data with no performance penalty just because you've reduced
# the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So it's no
# catch-all for performance problems, but it's a great way to cut down on the number of queries in a situation as the one described above.
#
@ -734,6 +742,7 @@ module ActiveRecord
deprecated_association_comparison_method(reflection.name, reflection.class_name)
end
# Create the callbacks to update counter cache
if options[:counter_cache]
cache_column = options[:counter_cache] == true ?
"#{self.to_s.underscore.pluralize}_count" :
@ -871,6 +880,12 @@ module ActiveRecord
end
private
# Generate a join table name from two provided tables names.
# The order of names in join name is determined by lexical precedence.
# join_table_name("members", "clubs")
# => "clubs_members"
# join_table_name("members", "special_clubs")
# => "members_special_clubs"
def join_table_name(first_table_name, second_table_name)
if first_table_name < second_table_name
join_table = "#{first_table_name}_#{second_table_name}"
@ -901,7 +916,7 @@ module ActiveRecord
define_method("#{reflection.name}=") do |new_value|
association = instance_variable_get("@#{reflection.name}")
if association.nil?
if association.nil? || association.target != new_value
association = association_proxy_class.new(self, reflection)
end
@ -911,10 +926,7 @@ module ActiveRecord
instance_variable_set("@#{reflection.name}", association)
else
instance_variable_set("@#{reflection.name}", nil)
return nil
end
association
end
define_method("set_#{reflection.name}_target") do |target|
@ -982,12 +994,15 @@ module ActiveRecord
after_callback = <<-end_eval
association = instance_variable_get("@#{association_name}")
if association.respond_to?(:loaded?)
if @new_record_before_save
records_to_save = association
records_to_save = if @new_record_before_save
association
elsif association.respond_to?(:loaded?) && association.loaded?
association.select { |record| record.new_record? }
else
records_to_save = association.select { |record| record.new_record? }
[]
end
if !records_to_save.blank?
records_to_save.each { |record| association.send(:insert_record, record) }
association.send(:construct_sql) # reconstruct the SQL queries now that we know the owner's id
end

View file

@ -91,7 +91,11 @@ module ActiveRecord
attributes.collect { |attr| create(attr) }
else
record = build(attributes)
record.save unless @owner.new_record?
if @owner.new_record?
ActiveSupport::Deprecation.warn("Calling .create on a has_many association without saving its owner will not work in rails 2.0, you probably want .build instead")
else
record.save
end
record
end
end

View file

@ -50,7 +50,7 @@ module ActiveRecord
options[:conditions] = options[:conditions].nil? ?
@finder_sql :
@finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
options[:include] = @reflection.options[:include]
options[:include] ||= @reflection.options[:include]
@reflection.klass.count(column_name, options)
end
@ -138,7 +138,7 @@ module ActiveRecord
elsif @reflection.options[:counter_sql]
@reflection.klass.count_by_sql(@counter_sql)
else
@reflection.klass.count(:conditions => @counter_sql)
@reflection.klass.count(:conditions => @counter_sql, :include => @reflection.options[:include])
end
@target = [] and loaded if count == 0

View file

@ -102,6 +102,16 @@ module ActiveRecord
calculate(:sum, *args, &block)
end
def count(*args)
column_name, options = @reflection.klass.send(:construct_count_options_from_legacy_args, *args)
if @reflection.options[:uniq]
# This is needed becase 'SELECT count(DISTINCT *)..' is not valid sql statement.
column_name = "#{@reflection.klass.table_name}.#{@reflection.klass.primary_key}" if column_name == :all
options.merge!(:distinct => true)
end
@reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
end
protected
def method_missing(method, *args, &block)
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))

View file

@ -575,7 +575,7 @@ module ActiveRecord #:nodoc:
# Specifies that the attribute by the name of +attr_name+ should be serialized before saving to the database and unserialized
# after loading from the database. The serialization is done through YAML. If +class_name+ is specified, the serialized
# object must be of that class on retrieval or +SerializationTypeMismatch+ will be raised.
# object must be of that class on retrieval, or nil. Otherwise, +SerializationTypeMismatch+ will be raised.
def serialize(attr_name, class_name = Object)
serialized_attributes[attr_name.to_s] = class_name
end
@ -1188,6 +1188,9 @@ module ActiveRecord #:nodoc:
#
# It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
# is actually find_all_by_amount(amount, options).
#
# This also enables you to initialize a record if it is not found, such as find_or_initialize_by_amount(amount)
# or find_or_create_by_user_and_password(user, password).
def method_missing(method_id, *arguments)
if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
finder, deprecated_finder = determine_finder(match), determine_deprecated_finder(match)
@ -1957,7 +1960,7 @@ module ActiveRecord #:nodoc:
def unserialize_attribute(attr_name)
unserialized_object = object_from_yaml(@attributes[attr_name])
if unserialized_object.is_a?(self.class.serialized_attributes[attr_name])
if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
@attributes[attr_name] = unserialized_object
else
raise SerializationTypeMismatch,
@ -2156,7 +2159,13 @@ module ActiveRecord #:nodoc:
def clone_attribute_value(reader_method, attribute_name)
value = send(reader_method, attribute_name)
case value
when nil, Fixnum, true, false
value
else
value.clone
end
rescue TypeError, NoMethodError
value
end

View file

@ -242,8 +242,8 @@ module ActiveRecord
options.assert_valid_keys(CALCULATIONS_OPTIONS)
end
# converts a given key to the value that the database adapter returns as
#
# Converts a given key to the value that the database adapter returns as
# as a usable column name.
# users.id #=> users_id
# sum(id) #=> sum_id
# count(distinct users.id) #=> count_distinct_users_id

View file

@ -24,7 +24,7 @@ module ActiveRecord
when Float, Fixnum, Bignum then value.to_s
# BigDecimals need to be output in a non-normalized form and quoted.
when BigDecimal then value.to_s('F')
when Date then "'#{value.to_s}'"
when Date then "'#{value.to_s(:db)}'"
when Time, DateTime then "'#{quoted_date(value)}'"
else "'#{quote_string(value.to_yaml)}'"
end

View file

@ -320,6 +320,7 @@ begin
decode(data_type, 'NUMBER', data_precision,
'FLOAT', data_precision,
'VARCHAR2', data_length,
'CHAR', data_length,
null) as limit,
decode(data_type, 'NUMBER', data_scale, null) as scale
from all_tab_columns

View file

@ -68,7 +68,7 @@ module ActiveRecord
class SQLiteColumn < Column #:nodoc:
class << self
def string_to_binary(value)
value.gsub(/\0|\%/) do |b|
value.gsub(/\0|\%/n) do |b|
case b
when "\0" then "%00"
when "%" then "%25"
@ -77,7 +77,7 @@ module ActiveRecord
end
def binary_to_string(value)
value.gsub(/%00|%25/) do |b|
value.gsub(/%00|%25/n) do |b|
case b
when "%00" then "\0"
when "%25" then "%"

View file

@ -1,7 +1,7 @@
module ActiveRecord
class Base
class << self
# This method is deprecated in favor of find with the :conditions option.
# DEPRECATION NOTICE: This method is deprecated in favor of find with the :conditions option.
#
# Works like find, but the record matching +id+ must also meet the +conditions+.
# +RecordNotFound+ is raised if no record can be found matching the +id+ or meeting the condition.
@ -12,7 +12,7 @@ module ActiveRecord
end
deprecate :find_on_conditions => "use find(ids, :conditions => conditions)"
# This method is deprecated in favor of find(:first, options).
# DEPRECATION NOTICE: This method is deprecated in favor of find(:first, options).
#
# Returns the object for the first record responding to the conditions in +conditions+,
# such as "group = 'master'". If more than one record is returned from the query, it's the first that'll
@ -24,7 +24,7 @@ module ActiveRecord
end
deprecate :find_first => "use find(:first, ...)"
# This method is deprecated in favor of find(:all, options).
# DEPRECATION NOTICE: This method is deprecated in favor of find(:all, options).
#
# Returns an array of all the objects that could be instantiated from the associated
# table in the database. The +conditions+ can be used to narrow the selection of objects (WHERE-part),

View file

@ -412,7 +412,7 @@ class Fixture #:nodoc:
klass = @class_name.constantize rescue nil
list = @fixture.inject([]) do |fixtures, (key, value)|
col = klass.columns_hash[key] if klass.kind_of?(ActiveRecord::Base)
col = klass.columns_hash[key] if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
end
list * ', '

View file

@ -5,15 +5,6 @@ module ActiveRecord
# Timestamping can be turned off by setting
# <tt>ActiveRecord::Base.record_timestamps = false</tt>
#
# Keep in mind that, via inheritance, you can turn off timestamps on a per
# model basis by setting <tt>record_timestamps</tt> to false in the desired
# models.
#
# class Feed < ActiveRecord::Base
# self.record_timestamps = false
# # ...
# end
#
# Timestamps are in the local timezone by default but can use UTC by setting
# <tt>ActiveRecord::Base.default_timezone = :utc</tt>
module Timestamp

View file

@ -2,7 +2,7 @@ module ActiveRecord
module VERSION #:nodoc:
MAJOR = 1
MINOR = 15
TINY = 3
TINY = 5
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -169,6 +169,12 @@ class EagerAssociationTest < Test::Unit::TestCase
assert_equal 0, posts.size
end
def test_eager_count_performed_on_a_has_many_association_with_multi_table_conditional
author = authors(:david)
author_posts_without_comments = author.posts.select { |post| post.comments.blank? }
assert_equal author_posts_without_comments.size, author.posts.count(:all, :include => :comments, :conditions => 'comments.id is null')
end
def test_eager_with_has_and_belongs_to_many_and_limit
posts = Post.find(:all, :include => :categories, :order => "posts.id", :limit => 3)
assert_equal 3, posts.size
@ -272,6 +278,13 @@ class EagerAssociationTest < Test::Unit::TestCase
assert_equal companies(:first_firm, :reload).account, f.account
end
def test_eager_with_multi_table_conditional_properly_counts_the_records_when_using_size
author = authors(:david)
posts_with_no_comments = author.posts.select { |post| post.comments.blank? }
assert_equal posts_with_no_comments.size, author.posts_with_no_comments.size
assert_equal posts_with_no_comments, author.posts_with_no_comments
end
def test_eager_with_invalid_association_reference
assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
post = Post.find(6, :include=> :monkeys )

View file

@ -30,6 +30,15 @@ class AssociationsJoinModelTest < Test::Unit::TestCase
assert_equal 1, authors(:mary).unique_categorized_posts.size
end
def test_has_many_uniq_through_count
author = authors(:mary)
assert !authors(:mary).unique_categorized_posts.loaded?
assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count }
assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count(:title, {}) }
assert_queries(1) { assert_equal 0, author.unique_categorized_posts.count(:title, { :conditions => "title is NULL" }) }
assert !authors(:mary).unique_categorized_posts.loaded?
end
def test_polymorphic_has_many
assert posts(:welcome).taggings.include?(taggings(:welcome_general))
end

View file

@ -67,7 +67,7 @@ class AssociationsTest < Test::Unit::TestCase
end
class AssociationProxyTest < Test::Unit::TestCase
fixtures :authors, :posts
fixtures :authors, :posts, :developers, :projects, :developers_projects
def test_proxy_accessors
welcome = posts(:welcome)
@ -87,6 +87,19 @@ class AssociationProxyTest < Test::Unit::TestCase
david.posts_with_extension.first # force load target
assert_equal david.posts_with_extension, david.posts_with_extension.testing_proxy_target
end
def test_save_on_parent_does_not_load_target
david = developers(:david)
assert !david.projects.loaded?
david.update_attribute(:created_at, Time.now)
assert !david.projects.loaded?
end
def test_save_on_parent_saves_children
developer = Developer.create :name => "Bryan", :salary => 50_000
assert_equal 1, developer.reload.audit_logs.size
end
end
class HasOneAssociationsTest < Test::Unit::TestCase
@ -583,6 +596,13 @@ class HasManyAssociationsTest < Test::Unit::TestCase
assert_equal 3, first_firm.plain_clients.size
end
def test_regular_create_on_has_many_when_parent_is_new_raises
assert_deprecated(/.build instead/) do
firm = Firm.new
firm.plain_clients.create :name=>"Whoever"
end
end
def test_adding_a_mismatch_class
assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << nil }
assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << 1 }
@ -1008,6 +1028,19 @@ class BelongsToAssociationsTest < Test::Unit::TestCase
assert_equal apple.id, citibank.firm_id
end
def test_no_unexpected_aliasing
first_firm = companies(:first_firm)
another_firm = companies(:another_firm)
citibank = Account.create("credit_limit" => 10)
citibank.firm = first_firm
original_proxy = citibank.firm
citibank.firm = another_firm
assert_equal first_firm.object_id, original_proxy.object_id
assert_equal another_firm.object_id, citibank.firm.object_id
end
def test_creating_the_belonging_object
citibank = Account.create("credit_limit" => 10)
apple = citibank.create_firm("name" => "Apple")

View file

@ -1086,16 +1086,29 @@ class BasicsTest < Test::Unit::TestCase
assert_equal(myobj, topic.content)
end
def test_serialized_attribute_with_class_constraint
def test_nil_serialized_attribute_with_class_constraint
myobj = MyObject.new('value1', 'value2')
topic = Topic.create("content" => myobj)
topic = Topic.new
assert_nil topic.content
end
def test_should_raise_exception_on_serialized_attribute_with_type_mismatch
myobj = MyObject.new('value1', 'value2')
topic = Topic.new(:content => myobj)
assert topic.save
Topic.serialize(:content, Hash)
assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content }
ensure
Topic.serialize(:content)
end
def test_serialized_attribute_with_class_constraint
settings = { "color" => "blue" }
Topic.find(topic.id).update_attribute("content", settings)
Topic.serialize(:content, Hash)
topic = Topic.new(:content => settings)
assert topic.save
assert_equal(settings, Topic.find(topic.id).content)
ensure
Topic.serialize(:content)
end

View file

@ -25,6 +25,7 @@ class Author < ActiveRecord::Base
has_many :hello_posts, :class_name => "Post", :conditions => "posts.body = 'hello'"
has_many :hello_post_comments, :through => :hello_posts, :source => :comments
has_many :posts_with_no_comments, :class_name => 'Post', :conditions => 'comments.id is null', :include => :comments
has_many :other_posts, :class_name => "Post"
has_many :posts_with_callbacks, :class_name => "Post", :before_add => :log_before_adding,

View file

@ -0,0 +1,437 @@
flowers:
id: 1
data: !binary | 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
KKpVChRRRQAUUUUAFFFFABRRRQAUUUUAf//Z

View file

@ -57,4 +57,9 @@ ActiveRecord::Schema.define do
create_table :lock_without_defaults_cust, :force => true do |t|
t.column :custom_lock_version, :integer
end
create_table :audit_logs, :force => true do |t|
t.column :message, :string, :null=>false
t.column :developer_id, :integer, :null=>false
end
end

View file

@ -31,8 +31,18 @@ class Developer < ActiveRecord::Base
has_and_belongs_to_many :special_projects, :join_table => 'developers_projects', :association_foreign_key => 'project_id'
has_many :audit_logs
validates_inclusion_of :salary, :in => 50000..200000
validates_length_of :name, :within => 3..20
before_create do |developer|
developer.audit_logs.build :message => "Computer created"
end
end
class AuditLog < ActiveRecord::Base
belongs_to :developer
end
DeveloperSalary = Struct.new(:amount)

View file

@ -12,13 +12,15 @@ class FixturesTest < Test::Unit::TestCase
self.use_instantiated_fixtures = true
self.use_transactional_fixtures = false
fixtures :topics, :developers, :accounts, :tasks, :categories, :funny_jokes
fixtures :topics, :developers, :accounts, :tasks, :categories, :funny_jokes, :binaries
FIXTURES = %w( accounts companies customers
FIXTURES = %w( accounts binaries companies customers
developers developers_projects entrants
movies projects subscribers topics tasks )
MATCH_ATTRIBUTE_NAME = /[a-zA-Z][-_\w]*/
BINARY_FIXTURE_PATH = File.dirname(__FILE__) + '/fixtures/flowers.jpg'
def test_clean_fixtures
FIXTURES.each do |name|
fixtures = nil
@ -100,7 +102,6 @@ class FixturesTest < Test::Unit::TestCase
assert first
end
def test_bad_format
path = File.join(File.dirname(__FILE__), 'fixtures', 'bad_fixtures')
Dir.entries(path).each do |file|
@ -174,7 +175,6 @@ class FixturesTest < Test::Unit::TestCase
end
end
def test_yml_file_in_subdirectory
assert_equal(categories(:sub_special_1).name, "A special category in a subdir file")
assert_equal(categories(:sub_special_1).class, SpecialCategory)
@ -185,7 +185,11 @@ class FixturesTest < Test::Unit::TestCase
assert_equal(categories(:sub_special_3).class, SpecialCategory)
end
def test_binary_in_fixtures
assert_equal 1, @binaries.size
data = File.read(BINARY_FIXTURE_PATH).freeze
assert_equal data, @flowers.data
end
end
if Account.connection.respond_to?(:reset_pk_sequence!)

View file

@ -170,7 +170,7 @@ if ActiveRecord::Base.connection.supports_migrations?
# SQL Server and Sybase will not allow you to add a NOT NULL column
# to a table without specifying a default value, so the
# following test must be skipped
unless current_adapter?(:SQLServerAdapter, :SybaseAdapter)
unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :SQLiteAdapter)
def test_add_column_not_null_without_default
Person.connection.create_table :testings do |t|
t.column :foo, :string
@ -209,7 +209,7 @@ if ActiveRecord::Base.connection.supports_migrations?
def test_native_decimal_insert_manual_vs_automatic
# SQLite3 always uses float in violation of SQL
# 16 decimal places
correct_value = (current_adapter?(:SQLiteAdapter) ? '0.123456789012346E20' : '0012345678901234567890.0123456789').to_d
correct_value = '0012345678901234567890.0123456789'.to_d
Person.delete_all
Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10'
@ -227,8 +227,9 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_kind_of BigDecimal, row.wealth
# If this assert fails, that means the SELECT is broken!
unless current_adapter?(:SQLite3Adapter)
assert_equal correct_value, row.wealth
end
# Reset to old state
Person.delete_all
@ -240,8 +241,9 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_kind_of BigDecimal, row.wealth
# If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken!
unless current_adapter?(:SQLite3Adapter)
assert_equal correct_value, row.wealth
end
# Reset to old state
Person.connection.del_column "people", "wealth" rescue nil
Person.reset_column_information
@ -267,10 +269,7 @@ if ActiveRecord::Base.connection.supports_migrations?
# Test for 30 significent digits (beyond the 16 of float), 10 of them
# after the decimal place.
if current_adapter?(:SQLiteAdapter)
# SQLite3 uses float in violation of SQL. Test for 16 decimal places.
assert_equal BigDecimal.new('0.123456789012346E20'), bob.wealth
else
unless current_adapter?(:SQLite3Adapter)
assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth
end

View file

@ -211,6 +211,53 @@ class ListTest < Test::Unit::TestCase
assert_equal [new2, new1, new3], ListMixin.find(:all, :conditions => 'parent_id IS NULL', :order => 'pos')
end
def test_remove_from_list_should_then_fail_in_list?
assert_equal true, mixins(:list_1).in_list?
mixins(:list_1).remove_from_list
assert_equal false, mixins(:list_1).in_list?
end
def test_remove_from_list_should_set_position_to_nil
assert_equal [mixins(:list_1),
mixins(:list_2),
mixins(:list_3),
mixins(:list_4)],
ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
mixins(:list_2).remove_from_list
assert_equal [mixins(:list_2, :reload),
mixins(:list_1, :reload),
mixins(:list_3, :reload),
mixins(:list_4, :reload)],
ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
assert_equal 1, mixins(:list_1).pos
assert_equal nil, mixins(:list_2).pos
assert_equal 2, mixins(:list_3).pos
assert_equal 3, mixins(:list_4).pos
end
def test_remove_before_destroy_does_not_shift_lower_items_twice
assert_equal [mixins(:list_1),
mixins(:list_2),
mixins(:list_3),
mixins(:list_4)],
ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
mixins(:list_2).remove_from_list
mixins(:list_2).destroy
assert_equal [mixins(:list_1, :reload),
mixins(:list_3, :reload),
mixins(:list_4, :reload)],
ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
assert_equal 1, mixins(:list_1).pos
assert_equal 2, mixins(:list_3).pos
assert_equal 3, mixins(:list_4).pos
end
end
class TreeTest < Test::Unit::TestCase

View file

@ -631,7 +631,7 @@ class ValidationsTest < Test::Unit::TestCase
t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
assert !t.save
assert t.errors.on(:replies)
t.replies.create('title' => 'areply', 'content' => 'whateveragain')
t.replies.build('title' => 'areply', 'content' => 'whateveragain')
assert t.valid?
end
@ -824,7 +824,7 @@ class ValidationsTest < Test::Unit::TestCase
t = Topic.new('title' => 'あいうえお', 'content' => 'かきくけこ')
assert !t.save
assert t.errors.on(:replies)
t.replies.create('title' => 'あいうえお', 'content' => 'かきくけこ')
t.replies.build('title' => 'あいうえお', 'content' => 'かきくけこ')
assert t.valid?
end
end

View file

@ -1,3 +1,19 @@
*1.4.4* (October 12th, 2007)
* Backport: allow array and hash query parameters. Array route parameters are converted/to/a/path as before. #6765, #7047, #7462 [bgipsy, Jeremy McAnally, Dan Kubb, brendan, Diego Algorta Casamayou]
*1.4.3* (October 4th, 2007)
* Demote Hash#to_xml to use XmlSimple#xml_in_string so it can't read files or stdin. #8453 [candlerb, Jeremy Kemper]
* Document Object#blank?. #6491 [Chris Mear]
* Update Dependencies to ignore constants inherited from ancestors. Closes #6951. [Nicholas Seckar]
* Improved multibyte performance by relying less on exception raising #8159 [Blaine]
*1.4.2* (March 12th, 2007)
* Ruby 1.8.6 and 1.9 define private Time#to_date and #to_datetime; make them

View file

@ -1,5 +1,11 @@
class Object #:nodoc:
# "", " ", nil, [], and {} are blank
class Object
# An object is blank if it's nil, empty, or a whitespace string.
# For example, "", " ", nil, [], and {} are blank.
#
# This simplifies
# if !address.nil? && !address.empty?
# to
# if !address.blank?
def blank?
if respond_to?(:empty?) && respond_to?(:strip)
empty? or strip.empty?

View file

@ -1,5 +1,44 @@
require 'date'
require 'xml_simple'
require 'cgi'
# Extensions needed for Hash#to_query
class Object
def to_param #:nodoc:
to_s
end
def to_query(key) #:nodoc:
"#{CGI.escape(key.to_s)}=#{CGI.escape(to_param.to_s)}"
end
end
class Array
def to_query(key) #:nodoc:
collect { |value| value.to_query("#{key}[]") }.sort * '&'
end
end
# Locked down XmlSimple#xml_in_string
class XmlSimple
# Same as xml_in but doesn't try to smartly shoot itself in the foot.
def xml_in_string(string, options = nil)
handle_options('in', options)
@doc = parse(string)
result = collapse(@doc.root)
if @options['keeproot']
merge({}, @doc.root.name, result)
else
result
end
end
def self.xml_in_string(string, options = nil)
new.xml_in_string(string, options)
end
end
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
@ -27,6 +66,12 @@ module ActiveSupport #:nodoc:
klass.extend(ClassMethods)
end
def to_query(namespace = nil)
collect do |key, value|
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
end.sort * '&'
end
def to_xml(options = {})
options[:indent] ||= 2
options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]),
@ -81,7 +126,7 @@ module ActiveSupport #:nodoc:
module ClassMethods
def from_xml(xml)
# TODO: Refactor this into something much cleaner that doesn't rely on XmlSimple
undasherize_keys(typecast_xml_value(XmlSimple.xml_in(xml,
undasherize_keys(typecast_xml_value(XmlSimple.xml_in_string(xml,
'forcearray' => false,
'forcecontent' => true,
'keeproot' => true,

View file

@ -18,4 +18,18 @@ class Module
parents << Object unless parents.include? Object
parents
end
# Return the constants that have been defined locally by this object and not
# in an ancestor. This method may miss some constants if their definition in
# the ancestor is identical to their definition in the receiver.
def local_constants
inherited = {}
ancestors.each do |anc|
next if anc == self
anc.constants.each { |const| inherited[const] = anc.const_get(const) }
end
constants.select do |const|
! inherited.key?(const) || inherited[const].object_id != const_get(const).object_id
end
end
end

View file

@ -318,13 +318,13 @@ module Dependencies #:nodoc:
watch_frames = descs.collect do |desc|
if desc.is_a? Module
mod_name = desc.name
initial_constants = desc.constants
initial_constants = desc.local_constants
elsif desc.is_a?(String) || desc.is_a?(Symbol)
mod_name = desc.to_s
# Handle the case where the module has yet to be defined.
initial_constants = if qualified_const_defined?(mod_name)
mod_name.constantize.constants
mod_name.constantize.local_constants
else
[]
end
@ -349,7 +349,7 @@ module Dependencies #:nodoc:
mod = mod_name.constantize
next [] unless mod.is_a? Module
new_constants = mod.constants - prior_constants
new_constants = mod.local_constants - prior_constants
# Make sure no other frames takes credit for these constants.
constant_watch_stack.each do |frame_name, constants|

View file

@ -24,11 +24,13 @@ module ActiveSupport
"\r" => '\r',
"\t" => '\t',
'"' => '\"',
'\\' => '\\\\'
'\\' => '\\\\',
'<' => '\\074',
'>' => '\\076'
}
define_encoder String do |string|
'"' + string.gsub(/[\010\f\n\r\t"\\]/) { |s|
'"' + string.gsub(/[\010\f\n\r\t"\\<>]/) { |s|
ESCAPED_CHARS[s]
}.gsub(/([\xC0-\xDF][\x80-\xBF]|
[\xE0-\xEF][\x80-\xBF]{2}|

Some files were not shown because too many files have changed in this diff Show more