mirror of
https://github.com/github/rails.git
synced 2026-01-13 00:28:26 -05:00
Compare commits
172 Commits
v2.3.8
...
2.3.14.git
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe11782158 | ||
|
|
899e99a025 | ||
|
|
e0774e4730 | ||
|
|
60f783d9ce | ||
|
|
6b46d65597 | ||
|
|
fb1588c5ff | ||
|
|
dea5a10f71 | ||
|
|
11dafeaa75 | ||
|
|
bb99aa1149 | ||
|
|
b132992978 | ||
|
|
78a1fda7c8 | ||
|
|
8d02083f23 | ||
|
|
b1c36b7088 | ||
|
|
b2d4142fb7 | ||
|
|
1aae5e70ef | ||
|
|
a2a34133d8 | ||
|
|
79aa54d0c7 | ||
|
|
3ad5fd1879 | ||
|
|
4c3725723f | ||
|
|
c20a4d18e3 | ||
|
|
01a9fbbcca | ||
|
|
8d4ca9edc6 | ||
|
|
d793a56121 | ||
|
|
f424efe97f | ||
|
|
9f7ff621bd | ||
|
|
b0be721dd9 | ||
|
|
8ca8ac379d | ||
|
|
589ce09564 | ||
|
|
6c42c142e2 | ||
|
|
abc06a2f76 | ||
|
|
b0c3d451a2 | ||
|
|
7e86f9b4d2 | ||
|
|
abe97736b8 | ||
|
|
7e0f60d2ed | ||
|
|
3afa5385c9 | ||
|
|
c545331f9e | ||
|
|
cd0ecff00b | ||
|
|
a0c761dc6b | ||
|
|
b5cf2b4b82 | ||
|
|
8378a44ff9 | ||
|
|
4f0c8ef9f1 | ||
|
|
bc302f2aec | ||
|
|
08d94d3f7e | ||
|
|
10ec012f58 | ||
|
|
92fd824480 | ||
|
|
6d916329b8 | ||
|
|
84465a2cc1 | ||
|
|
0fee359278 | ||
|
|
e0eb8e9c65 | ||
|
|
2826324e56 | ||
|
|
1681ede605 | ||
|
|
44db47c63e | ||
|
|
25139ac92c | ||
|
|
0e52a609fd | ||
|
|
df78de2bc8 | ||
|
|
36b91e34f4 | ||
|
|
bdfddb09d7 | ||
|
|
fdfc8e3b9c | ||
|
|
f5ed5c317e | ||
|
|
96183e0f28 | ||
|
|
f2e32e4fd7 | ||
|
|
8beb84fa33 | ||
|
|
a448e74661 | ||
|
|
fb526a0470 | ||
|
|
96c19ff7cc | ||
|
|
9b78af95be | ||
|
|
5a63df211d | ||
|
|
1851596db5 | ||
|
|
0665182950 | ||
|
|
515917f5d8 | ||
|
|
bc52d81306 | ||
|
|
dbbf2fd19c | ||
|
|
9476d628a3 | ||
|
|
7240e8af6a | ||
|
|
f2990620d7 | ||
|
|
17f2fb44c0 | ||
|
|
8c049c6b20 | ||
|
|
761c9cd5db | ||
|
|
a159fd0b8c | ||
|
|
e8b84ab1b4 | ||
|
|
383ea02e38 | ||
|
|
597fb1da94 | ||
|
|
c6e33d30c1 | ||
|
|
a61a39ecd4 | ||
|
|
b64d1fe637 | ||
|
|
6f17422ca7 | ||
|
|
bac12fa5fc | ||
|
|
56fdfeb265 | ||
|
|
881712cf50 | ||
|
|
b2c91983dc | ||
|
|
bdace5d6aa | ||
|
|
0fcb4302e1 | ||
|
|
11361a9e79 | ||
|
|
add3ccbca6 | ||
|
|
d35a67bba3 | ||
|
|
7e79889d1c | ||
|
|
43e2bbe28e | ||
|
|
b154b97ea4 | ||
|
|
15cafbe267 | ||
|
|
12bbc34aca | ||
|
|
8141f0894e | ||
|
|
27651c1fad | ||
|
|
a9ef2fd56c | ||
|
|
ae63d5c90d | ||
|
|
6f3896751a | ||
|
|
5b0f839054 | ||
|
|
a5d8c95a7c | ||
|
|
dec2c4f4e3 | ||
|
|
99cdea7cbe | ||
|
|
c2d13a9a53 | ||
|
|
fb615cd7fd | ||
|
|
4ae4828953 | ||
|
|
f57ca87729 | ||
|
|
7b6383f263 | ||
|
|
257a29d3cc | ||
|
|
8298bef72e | ||
|
|
046c900df2 | ||
|
|
504f7cfbb3 | ||
|
|
0963774c0a | ||
|
|
2d3bc99b0d | ||
|
|
ba9c469113 | ||
|
|
bfbdeeae30 | ||
|
|
67e18c523c | ||
|
|
526f1e5f15 | ||
|
|
f8f4872fcc | ||
|
|
fad166c152 | ||
|
|
78e4d88c70 | ||
|
|
ac42e6951f | ||
|
|
d0d10f51d7 | ||
|
|
69c4e4ce65 | ||
|
|
80473e035a | ||
|
|
70af7efa16 | ||
|
|
56b35afbdd | ||
|
|
0e9190c902 | ||
|
|
449cf50d85 | ||
|
|
05defcd63a | ||
|
|
f8f365346e | ||
|
|
4a745ca670 | ||
|
|
68bfd8a392 | ||
|
|
549b2ad77c | ||
|
|
09a23d2290 | ||
|
|
7d2173ec5c | ||
|
|
cc53229378 | ||
|
|
844da12ba6 | ||
|
|
a9c69f3bb0 | ||
|
|
687d7f52c4 | ||
|
|
cbf36cf57c | ||
|
|
52c922fad1 | ||
|
|
da93d69bcb | ||
|
|
e703fc101b | ||
|
|
85b6d79d8a | ||
|
|
5ed6a8447b | ||
|
|
54a5088cd5 | ||
|
|
08302d2feb | ||
|
|
c7e875abdb | ||
|
|
1ac00a6844 | ||
|
|
e4accdec0c | ||
|
|
b41c3ba154 | ||
|
|
0f44d37d04 | ||
|
|
ed8cabcec2 | ||
|
|
3d6ed50187 | ||
|
|
b760d699a8 | ||
|
|
5796a92433 | ||
|
|
b1a97a4998 | ||
|
|
a815f0c5a3 | ||
|
|
9da7ff8842 | ||
|
|
2ed893bdce | ||
|
|
9e08e196fa | ||
|
|
17b4fd25e4 | ||
|
|
bd9ca9aed0 | ||
|
|
8be3e09fcf | ||
|
|
ef0591efc2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,3 @@
|
||||
*.gem
|
||||
pkg
|
||||
.bundle
|
||||
debug.log
|
||||
|
||||
6
Rakefile
6
Rakefile
@@ -1,5 +1,5 @@
|
||||
require 'rake'
|
||||
require 'rake/rdoctask'
|
||||
require 'rdoc/task'
|
||||
|
||||
env = %(PKG_BUILD="#{ENV['PKG_BUILD']}") if ENV['PKG_BUILD']
|
||||
|
||||
@@ -23,13 +23,15 @@ end
|
||||
|
||||
|
||||
desc "Generate documentation for the Rails framework"
|
||||
Rake::RDocTask.new do |rdoc|
|
||||
RDoc::Task.new do |rdoc|
|
||||
rdoc.rdoc_dir = 'doc/rdoc'
|
||||
rdoc.title = "Ruby on Rails Documentation"
|
||||
rdoc.main = "railties/README"
|
||||
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
rdoc.options << '-A cattr_accessor=object'
|
||||
rdoc.options << '--charset' << 'utf-8'
|
||||
rdoc.options << '--main' << 'railties/README'
|
||||
|
||||
rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : './doc/template/horo'
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
*2.3.11 (February 9, 2011)*
|
||||
*2.3.10 (October 15, 2010)*
|
||||
*2.3.9 (September 4, 2010)*
|
||||
*2.3.8 (May 24, 2010)*
|
||||
|
||||
* Version bump.
|
||||
|
||||
|
||||
*2.3.7 (May 24, 2010)*
|
||||
|
||||
* Version bump.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
require 'rubygems'
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
require 'rdoc/task'
|
||||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rubygems/package_task'
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version')
|
||||
|
||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
||||
@@ -29,7 +29,7 @@ Rake::TestTask.new { |t|
|
||||
|
||||
|
||||
# Generate the RDoc documentation
|
||||
Rake::RDocTask.new { |rdoc|
|
||||
RDoc::Task.new { |rdoc|
|
||||
rdoc.rdoc_dir = 'doc'
|
||||
rdoc.title = "Action Mailer -- Easy email delivery and testing"
|
||||
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
||||
@@ -54,19 +54,17 @@ spec = Gem::Specification.new do |s|
|
||||
s.rubyforge_project = "actionmailer"
|
||||
s.homepage = "http://www.rubyonrails.org"
|
||||
|
||||
s.add_dependency('actionpack', '= 2.3.8' + PKG_BUILD)
|
||||
s.add_dependency('actionpack', '= 2.3.14' + PKG_BUILD)
|
||||
|
||||
s.has_rdoc = true
|
||||
s.requirements << 'none'
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'action_mailer'
|
||||
|
||||
s.files = [ "Rakefile", "install.rb", "README", "CHANGELOG", "MIT-LICENSE" ]
|
||||
s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
||||
s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
||||
end
|
||||
|
||||
Rake::GemPackageTask.new(spec) do |p|
|
||||
Gem::PackageTask.new(spec) do |p|
|
||||
p.gem_spec = spec
|
||||
p.need_tar = true
|
||||
p.need_zip = true
|
||||
|
||||
@@ -195,6 +195,39 @@ module ActionMailer #:nodoc:
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# = Multipart Emails with Attachments
|
||||
#
|
||||
# Multipart emails that also have attachments can be created by nesting a "multipart/alternative" part
|
||||
# within an email that has its content type set to "multipart/mixed". This would also need two templates
|
||||
# in place within +app/views/mailer+ called "welcome_email.text.html.erb" and "welcome_email.text.plain.erb"
|
||||
#
|
||||
# class ApplicationMailer < ActionMailer::Base
|
||||
# def signup_notification(recipient)
|
||||
# recipients recipient.email_address_with_name
|
||||
# subject "New account information"
|
||||
# from "system@example.com"
|
||||
# content_type "multipart/mixed"
|
||||
#
|
||||
# part "multipart/alternative" do |alternative|
|
||||
#
|
||||
# alternative.part "text/html" do |html|
|
||||
# html.body = render_message("welcome_email.text.html", :message => "<h1>HTML content</h1>")
|
||||
# end
|
||||
#
|
||||
# alternative.part "text/plain" do |plain|
|
||||
# plain.body = render_message("welcome_email.text.plain", :message => "text content")
|
||||
# end
|
||||
#
|
||||
# end
|
||||
#
|
||||
# attachment :content_type => "image/png",
|
||||
# :body => File.read(File.join(RAILS_ROOT, 'public/images/rails.png'))
|
||||
#
|
||||
# attachment "application/pdf" do |a|
|
||||
# a.body = File.read('/Users/mikel/Code/mail/spec/fixtures/attachments/test.pdf')
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# = Configuration options
|
||||
#
|
||||
@@ -278,7 +311,7 @@ module ActionMailer #:nodoc:
|
||||
@@raise_delivery_errors = true
|
||||
cattr_accessor :raise_delivery_errors
|
||||
|
||||
superclass_delegating_accessor :delivery_method
|
||||
class_attribute :delivery_method
|
||||
self.delivery_method = :smtp
|
||||
|
||||
@@perform_deliveries = true
|
||||
|
||||
@@ -105,7 +105,7 @@ module ActionMailer
|
||||
private
|
||||
# Extend the template class instance with our controller's helper module.
|
||||
def initialize_template_class_with_helper(assigns)
|
||||
returning(template = initialize_template_class_without_helper(assigns)) do
|
||||
initialize_template_class_without_helper(assigns).tap do |template|
|
||||
template.extend self.class.master_helper_module
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ module ActionMailer
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 3
|
||||
TINY = 8
|
||||
TINY = 14
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
*2.3.11 (February 9, 2011)*
|
||||
|
||||
* Two security fixes. CVE-2011-0446, CVE-2011-0447
|
||||
|
||||
*2.3.10 (October 15, 2010)*
|
||||
|
||||
*2.3.9 (September 4, 2010)*
|
||||
|
||||
* Version bump.
|
||||
|
||||
|
||||
*2.3.8 (May 24, 2010)*
|
||||
|
||||
* HTML safety: fix compatibility *without* the optional rails_xss plugin.
|
||||
@@ -1924,7 +1935,7 @@ superclass' view_paths. [Rick Olson]
|
||||
|
||||
* Update documentation for erb trim syntax. #5651 [matt@mattmargolis.net]
|
||||
|
||||
* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com, sebastien@goetzilla.info]
|
||||
* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com]
|
||||
|
||||
* Reset @html_document between requests so assert_tag works. #4810 [Jarkko Laine, easleydp@gmail.com]
|
||||
|
||||
@@ -2521,7 +2532,7 @@ superclass' view_paths. [Rick Olson]
|
||||
|
||||
* Provide support for decimal columns to form helpers. Closes #5672. [Dave Thomas]
|
||||
|
||||
* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com, sebastien@goetzilla.info]
|
||||
* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com]
|
||||
|
||||
* Reset @html_document between requests so assert_tag works. #4810 [Jarkko Laine, easleydp@gmail.com]
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
require 'rubygems'
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
require 'rdoc/task'
|
||||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rubygems/package_task'
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version')
|
||||
|
||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
||||
@@ -45,7 +45,7 @@ end
|
||||
|
||||
# Genereate the RDoc documentation
|
||||
|
||||
Rake::RDocTask.new { |rdoc|
|
||||
RDoc::Task.new { |rdoc|
|
||||
rdoc.rdoc_dir = 'doc'
|
||||
rdoc.title = "Action Pack -- On rails from request to response"
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
@@ -76,14 +76,12 @@ spec = Gem::Specification.new do |s|
|
||||
s.rubyforge_project = "actionpack"
|
||||
s.homepage = "http://www.rubyonrails.org"
|
||||
|
||||
s.has_rdoc = true
|
||||
s.requirements << 'none'
|
||||
|
||||
s.add_dependency('activesupport', '= 2.3.8' + PKG_BUILD)
|
||||
s.add_dependency('rack', '~> 1.1.0')
|
||||
s.add_dependency('activesupport', '= 2.3.14' + PKG_BUILD)
|
||||
s.add_dependency('rack', '~> 1.1')
|
||||
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'action_controller'
|
||||
|
||||
s.files = [ "Rakefile", "install.rb", "README", "RUNNING_UNIT_TESTS", "CHANGELOG", "MIT-LICENSE" ]
|
||||
dist_dirs.each do |dir|
|
||||
@@ -91,7 +89,7 @@ spec = Gem::Specification.new do |s|
|
||||
end
|
||||
end
|
||||
|
||||
Rake::GemPackageTask.new(spec) do |p|
|
||||
Gem::PackageTask.new(spec) do |p|
|
||||
p.gem_spec = spec
|
||||
p.need_tar = true
|
||||
p.need_zip = true
|
||||
|
||||
@@ -31,7 +31,7 @@ rescue LoadError
|
||||
end
|
||||
end
|
||||
|
||||
gem 'rack', '~> 1.1.0'
|
||||
gem 'rack', '~> 1.1'
|
||||
require 'rack'
|
||||
require 'action_controller/cgi_ext'
|
||||
|
||||
|
||||
@@ -1088,6 +1088,9 @@ module ActionController #:nodoc:
|
||||
# redirect_to post_url(@post), :status => 301
|
||||
# redirect_to :action=>'atom', :status => 302
|
||||
#
|
||||
# The status code can either be a standard {HTTP Status code}[http://www.iana.org/assignments/http-status-codes] as an
|
||||
# integer, or a symbol representing the downcased, underscored and symbolized description.
|
||||
#
|
||||
# It is also possible to assign a flash message as part of the redirection. There are two special accessors for commonly used the flash names
|
||||
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
|
||||
#
|
||||
@@ -1097,8 +1100,7 @@ module ActionController #:nodoc:
|
||||
# redirect_to post_url(@post), :status => 301, :flash => { :updated_post_id => @post.id }
|
||||
# redirect_to { :action=>'atom' }, :alert => "Something serious happened"
|
||||
#
|
||||
# When using <tt>redirect_to :back</tt>, if there is no referrer,
|
||||
# RedirectBackError will be raised. You may specify some fallback
|
||||
# When using <tt>redirect_to :back</tt>, if there is no referrer, RedirectBackError will be raised. You may specify some fallback
|
||||
# behavior for this case by rescuing RedirectBackError.
|
||||
def redirect_to(options = {}, response_status = {}) #:doc:
|
||||
raise ActionControllerError.new("Cannot redirect to nil!") if options.nil?
|
||||
|
||||
@@ -65,8 +65,8 @@ module ActionController #:nodoc:
|
||||
def read_fragment(key, options = nil)
|
||||
return unless cache_configured?
|
||||
|
||||
key = fragment_cache_key(key)
|
||||
self.class.benchmark "Cached fragment hit: #{key}" do
|
||||
key = fragment_cache_key(key)
|
||||
result = cache_store.read(key, options)
|
||||
result.respond_to?(:html_safe) ? result.html_safe : result
|
||||
end
|
||||
|
||||
@@ -60,7 +60,7 @@ module ActionController #:nodoc:
|
||||
attr_reader :controller
|
||||
|
||||
def initialize(controller)
|
||||
@controller, @cookies = controller, controller.request.cookies
|
||||
@controller, @cookies, @secure = controller, controller.request.cookies, controller.request.ssl?
|
||||
super()
|
||||
update(@cookies)
|
||||
end
|
||||
@@ -81,7 +81,7 @@ module ActionController #:nodoc:
|
||||
|
||||
options[:path] = "/" unless options.has_key?(:path)
|
||||
super(key.to_s, options[:value])
|
||||
@controller.response.set_cookie(key, options)
|
||||
@controller.response.set_cookie(key, options) if write_cookie?(options)
|
||||
end
|
||||
|
||||
# Removes the cookie on the client machine by setting the value to an empty string
|
||||
@@ -126,6 +126,12 @@ module ActionController #:nodoc:
|
||||
def signed
|
||||
@signed ||= SignedCookieJar.new(self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def write_cookie?(cookie)
|
||||
@secure || !cookie[:secure] || defined?(Rails.env) && Rails.env.development?
|
||||
end
|
||||
end
|
||||
|
||||
class PermanentCookieJar < CookieJar #:nodoc:
|
||||
|
||||
@@ -287,7 +287,6 @@ module ActionController
|
||||
"REMOTE_ADDR" => remote_addr,
|
||||
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
||||
"CONTENT_LENGTH" => data ? data.length.to_s : nil,
|
||||
"HTTP_COOKIE" => encode_cookies,
|
||||
"HTTP_ACCEPT" => accept,
|
||||
|
||||
"rack.version" => [0,1],
|
||||
@@ -298,6 +297,8 @@ module ActionController
|
||||
"rack.run_once" => false
|
||||
)
|
||||
|
||||
env['HTTP_COOKIE'] = encode_cookies if cookies.any?
|
||||
|
||||
(headers || {}).each do |key, value|
|
||||
key = key.to_s.upcase.gsub(/-/, "_")
|
||||
key = "HTTP_#{key}" unless env.has_key?(key) || key =~ /^HTTP_/
|
||||
@@ -414,15 +415,25 @@ module ActionController
|
||||
end
|
||||
|
||||
def multipart_requestify(params, first=true)
|
||||
returning Hash.new do |p|
|
||||
Array.new.tap do |p|
|
||||
params.each do |key, value|
|
||||
k = first ? key.to_s : "[#{key.to_s}]"
|
||||
if Hash === value
|
||||
multipart_requestify(value, false).each do |subkey, subvalue|
|
||||
p[k + subkey] = subvalue
|
||||
p << [k + subkey, subvalue]
|
||||
end
|
||||
elsif Array === value
|
||||
value.each do |element|
|
||||
if Hash === element || Array === element
|
||||
multipart_requestify(element, false).each do |subkey, subvalue|
|
||||
p << ["#{k}[]#{subkey}", subvalue]
|
||||
end
|
||||
else
|
||||
p << ["#{k}[]", element]
|
||||
end
|
||||
end
|
||||
else
|
||||
p[k] = value
|
||||
p << [k, value]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -453,6 +464,7 @@ EOF
|
||||
end
|
||||
end.join("")+"--#{boundary}--\r"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# A module used to extend ActionController::Base, so that integration tests
|
||||
@@ -500,7 +512,7 @@ EOF
|
||||
reset! unless @integration_session
|
||||
# reset the html_document variable, but only for new get/post calls
|
||||
@html_document = nil unless %w(cookies assigns).include?(method)
|
||||
returning @integration_session.__send__(method, *args) do
|
||||
@integration_session.__send__(method, *args).tap do
|
||||
copy_session_variables!
|
||||
end
|
||||
end
|
||||
@@ -524,7 +536,7 @@ EOF
|
||||
if self.class.respond_to?(:fixture_table_names)
|
||||
self.class.fixture_table_names.each do |table_name|
|
||||
name = table_name.tr(".", "_")
|
||||
next unless respond_to?(name)
|
||||
next unless respond_to?(name, true)
|
||||
extras.__send__(:define_method, name) { |*args|
|
||||
delegate.send(name, *args)
|
||||
}
|
||||
@@ -556,7 +568,7 @@ EOF
|
||||
def method_missing(sym, *args, &block)
|
||||
reset! unless @integration_session
|
||||
if @integration_session.respond_to?(sym)
|
||||
returning @integration_session.__send__(sym, *args, &block) do
|
||||
@integration_session.__send__(sym, *args, &block).tap do
|
||||
copy_session_variables!
|
||||
end
|
||||
else
|
||||
|
||||
@@ -446,8 +446,10 @@ EOM
|
||||
end
|
||||
|
||||
def reset_session
|
||||
@env['rack.session.options'].delete(:id)
|
||||
@env['rack.session'] = {}
|
||||
# session may be a hash, if so, we do not want to call destroy
|
||||
# fixes issue 6440
|
||||
session.destroy if session and session.respond_to?(:destroy)
|
||||
self.session = {}
|
||||
end
|
||||
|
||||
def session_options
|
||||
|
||||
@@ -76,7 +76,11 @@ module ActionController #:nodoc:
|
||||
protected
|
||||
# The actual before_filter that is used. Modify this to change how you handle unverified requests.
|
||||
def verify_authenticity_token
|
||||
verified_request? || raise(ActionController::InvalidAuthenticityToken)
|
||||
verified_request? || handle_unverified_request
|
||||
end
|
||||
|
||||
def handle_unverified_request
|
||||
reset_session
|
||||
end
|
||||
|
||||
# Returns true or false if a request is verified. Checks:
|
||||
@@ -85,11 +89,10 @@ module ActionController #:nodoc:
|
||||
# * is it a GET request? Gets should be safe and idempotent
|
||||
# * Does the form_authenticity_token match the given token value from the params?
|
||||
def verified_request?
|
||||
!protect_against_forgery? ||
|
||||
request.method == :get ||
|
||||
request.xhr? ||
|
||||
!verifiable_request_format? ||
|
||||
form_authenticity_token == form_authenticity_param
|
||||
!protect_against_forgery? ||
|
||||
request.get? ||
|
||||
form_authenticity_token == form_authenticity_param ||
|
||||
form_authenticity_token == request.headers['X-CSRF-Token']
|
||||
end
|
||||
|
||||
def form_authenticity_param
|
||||
|
||||
@@ -15,7 +15,7 @@ module ActionController #:nodoc:
|
||||
# behavior is achieved by overriding the <tt>rescue_action_in_public</tt>
|
||||
# and <tt>rescue_action_locally</tt> methods.
|
||||
module Rescue
|
||||
LOCALHOST = ['127.0.0.1', '::1'].freeze
|
||||
LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/].freeze
|
||||
|
||||
DEFAULT_RESCUE_RESPONSE = :internal_server_error
|
||||
DEFAULT_RESCUE_RESPONSES = {
|
||||
@@ -122,7 +122,7 @@ module ActionController #:nodoc:
|
||||
# method if you wish to redefine the meaning of a local request to
|
||||
# include remote IP addresses or other criteria.
|
||||
def local_request? #:doc:
|
||||
LOCALHOST.any?{ |local_ip| request.remote_addr == local_ip && request.remote_ip == local_ip }
|
||||
LOCALHOST.any?{ |local_ip| request.remote_addr =~ local_ip && request.remote_ip =~ local_ip }
|
||||
end
|
||||
|
||||
# Render detailed diagnostics for unhandled exceptions rescued from
|
||||
|
||||
@@ -659,7 +659,7 @@ module ActionController
|
||||
end
|
||||
|
||||
def add_conditions_for(conditions, method)
|
||||
returning({:conditions => conditions.dup}) do |options|
|
||||
({:conditions => conditions.dup}).tap do |options|
|
||||
options[:conditions][:method] = method unless method == :any
|
||||
end
|
||||
end
|
||||
|
||||
@@ -64,12 +64,13 @@ module ActionController # :nodoc:
|
||||
# the character set information will also be included in the content type
|
||||
# information.
|
||||
def content_type=(mime_type)
|
||||
self.headers["Content-Type"] =
|
||||
new_content_type =
|
||||
if mime_type =~ /charset/ || (c = charset).nil?
|
||||
mime_type.to_s
|
||||
else
|
||||
"#{mime_type}; charset=#{c}"
|
||||
end
|
||||
self.headers["Content-Type"] = URI.escape(new_content_type, "\r\n")
|
||||
end
|
||||
|
||||
# Returns the response's content MIME type, or nil if content type has been set.
|
||||
|
||||
@@ -377,7 +377,7 @@ module ActionController
|
||||
ActiveSupport::Inflector.module_eval do
|
||||
# Ensures that routes are reloaded when Rails inflections are updated.
|
||||
def inflections_with_route_reloading(&block)
|
||||
returning(inflections_without_route_reloading(&block)) {
|
||||
(inflections_without_route_reloading(&block)).tap {
|
||||
ActionController::Routing::Routes.reload! if block_given?
|
||||
}
|
||||
end
|
||||
|
||||
@@ -65,7 +65,7 @@ module ActionController
|
||||
# map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
|
||||
#
|
||||
def parameter_shell
|
||||
@parameter_shell ||= returning({}) do |shell|
|
||||
@parameter_shell ||= {}.tap do |shell|
|
||||
requirements.each do |key, requirement|
|
||||
shell[key] = requirement unless requirement.is_a? Regexp
|
||||
end
|
||||
@@ -76,7 +76,7 @@ module ActionController
|
||||
# includes keys that appear inside the path, and keys that have requirements
|
||||
# placed upon them.
|
||||
def significant_keys
|
||||
@significant_keys ||= returning([]) do |sk|
|
||||
@significant_keys ||= [].tap do |sk|
|
||||
segments.each { |segment| sk << segment.key if segment.respond_to? :key }
|
||||
sk.concat requirements.keys
|
||||
sk.uniq!
|
||||
@@ -86,7 +86,7 @@ module ActionController
|
||||
# Return a hash of key/value pairs representing the keys in the route that
|
||||
# have defaults, or which are specified by non-regexp requirements.
|
||||
def defaults
|
||||
@defaults ||= returning({}) do |hash|
|
||||
@defaults ||= {}.tap do |hash|
|
||||
segments.each do |segment|
|
||||
next unless segment.respond_to? :default
|
||||
hash[segment.key] = segment.default unless segment.default.nil?
|
||||
|
||||
@@ -2,13 +2,42 @@ require 'rack/utils'
|
||||
|
||||
module ActionController
|
||||
module Session
|
||||
class AbstractStore
|
||||
class AbstractStore
|
||||
ENV_SESSION_KEY = 'rack.session'.freeze
|
||||
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
|
||||
|
||||
HTTP_COOKIE = 'HTTP_COOKIE'.freeze
|
||||
SET_COOKIE = 'Set-Cookie'.freeze
|
||||
|
||||
# thin wrapper around Hash that allows us to lazily
|
||||
# load session id into session_options
|
||||
class OptionsHash < Hash
|
||||
def initialize(by, env, default_options)
|
||||
@by = by
|
||||
@env = env
|
||||
@session_id_loaded = false
|
||||
merge!(default_options)
|
||||
end
|
||||
|
||||
def [](key)
|
||||
if key == :id
|
||||
load_session_id! unless super(:id) || has_session_id?
|
||||
end
|
||||
super(key)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def has_session_id?
|
||||
@session_id_loaded
|
||||
end
|
||||
|
||||
def load_session_id!
|
||||
self[:id] = @by.send(:extract_session_id, @env)
|
||||
@session_id_loaded = true
|
||||
end
|
||||
end
|
||||
|
||||
class SessionHash < Hash
|
||||
def initialize(by, env)
|
||||
super()
|
||||
@@ -25,21 +54,42 @@ module ActionController
|
||||
end
|
||||
|
||||
def [](key)
|
||||
load! unless @loaded
|
||||
load_for_read!
|
||||
super
|
||||
end
|
||||
|
||||
def has_key?(key)
|
||||
load_for_read!
|
||||
super
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
load! unless @loaded
|
||||
load_for_write!
|
||||
super
|
||||
end
|
||||
|
||||
def clear
|
||||
load_for_write!
|
||||
super
|
||||
end
|
||||
|
||||
def to_hash
|
||||
load_for_read!
|
||||
h = {}.replace(self)
|
||||
h.delete_if { |k,v| v.nil? }
|
||||
h
|
||||
end
|
||||
|
||||
def update(hash)
|
||||
load_for_write!
|
||||
super
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
load_for_write!
|
||||
super
|
||||
end
|
||||
|
||||
def data
|
||||
ActiveSupport::Deprecation.warn(
|
||||
"ActionController::Session::AbstractStore::SessionHash#data " +
|
||||
@@ -48,40 +98,43 @@ module ActionController
|
||||
end
|
||||
|
||||
def inspect
|
||||
load! unless @loaded
|
||||
load_for_read!
|
||||
super
|
||||
end
|
||||
|
||||
def exists?
|
||||
return @exists if instance_variable_defined?(:@exists)
|
||||
@exists = @by.send(:exists?, @env)
|
||||
end
|
||||
|
||||
def loaded?
|
||||
@loaded
|
||||
end
|
||||
|
||||
def destroy
|
||||
clear
|
||||
@by.send(:destroy, @env) if @by
|
||||
@env[ENV_SESSION_OPTIONS_KEY][:id] = nil if @env && @env[ENV_SESSION_OPTIONS_KEY]
|
||||
@loaded = false
|
||||
end
|
||||
|
||||
private
|
||||
def loaded?
|
||||
@loaded
|
||||
|
||||
def load_for_read!
|
||||
load! if !loaded? && exists?
|
||||
end
|
||||
|
||||
def load_for_write!
|
||||
load! unless loaded?
|
||||
end
|
||||
|
||||
def load!
|
||||
stale_session_check! do
|
||||
id, session = @by.send(:load_session, @env)
|
||||
(@env[ENV_SESSION_OPTIONS_KEY] ||= {})[:id] = id
|
||||
replace(session)
|
||||
@loaded = true
|
||||
end
|
||||
id, session = @by.send(:load_session, @env)
|
||||
@env[ENV_SESSION_OPTIONS_KEY][:id] = id
|
||||
replace(session)
|
||||
@loaded = true
|
||||
end
|
||||
|
||||
def stale_session_check!
|
||||
yield
|
||||
rescue ArgumentError => argument_error
|
||||
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
||||
begin
|
||||
# Note that the regexp does not allow $1 to end with a ':'
|
||||
$1.constantize
|
||||
rescue LoadError, NameError => const_error
|
||||
raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn\\'t available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: \#{const_error.message} [\#{const_error.class}])\n"
|
||||
end
|
||||
|
||||
retry
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
DEFAULT_OPTIONS = {
|
||||
@@ -120,18 +173,18 @@ module ActionController
|
||||
end
|
||||
|
||||
def call(env)
|
||||
session = SessionHash.new(self, env)
|
||||
|
||||
env[ENV_SESSION_KEY] = session
|
||||
env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
|
||||
|
||||
prepare!(env)
|
||||
response = @app.call(env)
|
||||
|
||||
session_data = env[ENV_SESSION_KEY]
|
||||
options = env[ENV_SESSION_OPTIONS_KEY]
|
||||
|
||||
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
|
||||
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
|
||||
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after]
|
||||
request = ActionController::Request.new(env)
|
||||
|
||||
return response if (options[:secure] && !request.ssl?)
|
||||
|
||||
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
|
||||
|
||||
sid = options[:id] || generate_sid
|
||||
|
||||
@@ -139,21 +192,12 @@ module ActionController
|
||||
return response
|
||||
end
|
||||
|
||||
cookie = Rack::Utils.escape(@key) + '=' + Rack::Utils.escape(sid)
|
||||
cookie << "; domain=#{options[:domain]}" if options[:domain]
|
||||
cookie << "; path=#{options[:path]}" if options[:path]
|
||||
if options[:expire_after]
|
||||
expiry = Time.now + options[:expire_after]
|
||||
cookie << "; expires=#{expiry.httpdate}"
|
||||
end
|
||||
cookie << "; Secure" if options[:secure]
|
||||
cookie << "; HttpOnly" if options[:httponly]
|
||||
request_cookies = env["rack.request.cookie_hash"]
|
||||
|
||||
headers = response[1]
|
||||
unless headers[SET_COOKIE].blank?
|
||||
headers[SET_COOKIE] << "\n#{cookie}"
|
||||
else
|
||||
headers[SET_COOKIE] = cookie
|
||||
if (request_cookies.nil? || request_cookies[@key] != sid) || options[:expire_after]
|
||||
cookie = {:value => sid}
|
||||
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
|
||||
Rack::Utils.set_cookie_header!(response[1], @key, cookie.merge(options))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -161,18 +205,39 @@ module ActionController
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prepare!(env)
|
||||
env[ENV_SESSION_KEY] = SessionHash.new(self, env)
|
||||
env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
|
||||
end
|
||||
|
||||
def generate_sid
|
||||
ActiveSupport::SecureRandom.hex(16)
|
||||
end
|
||||
|
||||
def load_session(env)
|
||||
request = Rack::Request.new(env)
|
||||
sid = request.cookies[@key]
|
||||
unless @cookie_only
|
||||
sid ||= request.params[@key]
|
||||
stale_session_check! do
|
||||
sid = current_session_id(env)
|
||||
sid, session = get_session(env, sid)
|
||||
[sid, session]
|
||||
end
|
||||
sid, session = get_session(env, sid)
|
||||
[sid, session]
|
||||
end
|
||||
|
||||
def extract_session_id(env)
|
||||
stale_session_check! do
|
||||
request = Rack::Request.new(env)
|
||||
sid = request.cookies[@key]
|
||||
sid ||= request.params[@key] unless @cookie_only
|
||||
sid
|
||||
end
|
||||
end
|
||||
|
||||
def current_session_id(env)
|
||||
env[ENV_SESSION_OPTIONS_KEY][:id]
|
||||
end
|
||||
|
||||
def exists?(env)
|
||||
current_session_id(env).present?
|
||||
end
|
||||
|
||||
def get_session(env, sid)
|
||||
@@ -182,6 +247,30 @@ module ActionController
|
||||
def set_session(env, sid, session_data)
|
||||
raise '#set_session needs to be implemented.'
|
||||
end
|
||||
|
||||
def destroy(env)
|
||||
raise '#destroy needs to be implemented.'
|
||||
end
|
||||
|
||||
module SessionUtils
|
||||
private
|
||||
def stale_session_check!
|
||||
yield
|
||||
rescue ArgumentError => argument_error
|
||||
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
||||
begin
|
||||
# Note that the regexp does not allow $1 to end with a ':'
|
||||
$1.constantize
|
||||
rescue LoadError, NameError => const_error
|
||||
raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn\\'t available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: \#{const_error.message} [\#{const_error.class}])\n"
|
||||
end
|
||||
retry
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
include SessionUtils
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,6 +36,8 @@ module ActionController
|
||||
#
|
||||
# Note that changing digest or secret invalidates all existing sessions!
|
||||
class CookieStore
|
||||
include AbstractStore::SessionUtils
|
||||
|
||||
# Cookies can typically store 4096 bytes.
|
||||
MAX = 4096
|
||||
SECRET_MIN_LENGTH = 30 # characters
|
||||
@@ -50,7 +52,6 @@ module ActionController
|
||||
|
||||
ENV_SESSION_KEY = "rack.session".freeze
|
||||
ENV_SESSION_OPTIONS_KEY = "rack.session.options".freeze
|
||||
HTTP_SET_COOKIE = "Set-Cookie".freeze
|
||||
|
||||
# Raised when storing more than 4K of session data.
|
||||
class CookieOverflow < StandardError; end
|
||||
@@ -93,73 +94,81 @@ module ActionController
|
||||
end
|
||||
|
||||
def call(env)
|
||||
env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env)
|
||||
env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
|
||||
|
||||
prepare!(env)
|
||||
|
||||
status, headers, body = @app.call(env)
|
||||
|
||||
session_data = env[ENV_SESSION_KEY]
|
||||
options = env[ENV_SESSION_OPTIONS_KEY]
|
||||
request = ActionController::Request.new(env)
|
||||
|
||||
if !(options[:secure] && !request.ssl?) && (!session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after])
|
||||
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
|
||||
|
||||
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
|
||||
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
|
||||
persistent_session_id!(session_data)
|
||||
session_data = marshal(session_data.to_hash)
|
||||
|
||||
raise CookieOverflow if session_data.size > MAX
|
||||
|
||||
cookie = Hash.new
|
||||
cookie[:value] = session_data
|
||||
unless options[:expire_after].nil?
|
||||
cookie[:expires] = Time.now + options[:expire_after]
|
||||
end
|
||||
|
||||
cookie = build_cookie(@key, cookie.merge(options))
|
||||
unless headers[HTTP_SET_COOKIE].blank?
|
||||
headers[HTTP_SET_COOKIE] << "\n#{cookie}"
|
||||
else
|
||||
headers[HTTP_SET_COOKIE] = cookie
|
||||
end
|
||||
Rack::Utils.set_cookie_header!(headers, @key, cookie.merge(options))
|
||||
end
|
||||
|
||||
[status, headers, body]
|
||||
end
|
||||
|
||||
private
|
||||
# Should be in Rack::Utils soon
|
||||
def build_cookie(key, value)
|
||||
case value
|
||||
when Hash
|
||||
domain = "; domain=" + value[:domain] if value[:domain]
|
||||
path = "; path=" + value[:path] if value[:path]
|
||||
# According to RFC 2109, we need dashes here.
|
||||
# N.B.: cgi.rb uses spaces...
|
||||
expires = "; expires=" + value[:expires].clone.gmtime.
|
||||
strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
|
||||
secure = "; secure" if value[:secure]
|
||||
httponly = "; HttpOnly" if value[:httponly]
|
||||
value = value[:value]
|
||||
end
|
||||
value = [value] unless Array === value
|
||||
cookie = Rack::Utils.escape(key) + "=" +
|
||||
value.map { |v| Rack::Utils.escape(v) }.join("&") +
|
||||
"#{domain}#{path}#{expires}#{secure}#{httponly}"
|
||||
|
||||
def prepare!(env)
|
||||
env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env)
|
||||
env[ENV_SESSION_OPTIONS_KEY] = AbstractStore::OptionsHash.new(self, env, @default_options)
|
||||
end
|
||||
|
||||
def load_session(env)
|
||||
request = Rack::Request.new(env)
|
||||
session_data = request.cookies[@key]
|
||||
data = unmarshal(session_data) || persistent_session_id!({})
|
||||
data = unpacked_cookie_data(env)
|
||||
data = persistent_session_id!(data)
|
||||
[data[:session_id], data]
|
||||
end
|
||||
|
||||
def extract_session_id(env)
|
||||
if data = unpacked_cookie_data(env)
|
||||
persistent_session_id!(data) unless data.empty?
|
||||
data[:session_id]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def current_session_id(env)
|
||||
env[ENV_SESSION_OPTIONS_KEY][:id]
|
||||
end
|
||||
|
||||
def exists?(env)
|
||||
current_session_id(env).present?
|
||||
end
|
||||
|
||||
def unpacked_cookie_data(env)
|
||||
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
|
||||
stale_session_check! do
|
||||
request = Rack::Request.new(env)
|
||||
session_data = request.cookies[@key]
|
||||
unmarshal(session_data) || {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Marshal a session hash into safe cookie data. Include an integrity hash.
|
||||
def marshal(session)
|
||||
@verifier.generate(persistent_session_id!(session))
|
||||
@verifier.generate(session)
|
||||
end
|
||||
|
||||
# Unmarshal cookie data to a hash and verify its integrity.
|
||||
def unmarshal(cookie)
|
||||
persistent_session_id!(@verifier.verify(cookie)) if cookie
|
||||
@verifier.verify(cookie) if cookie
|
||||
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
||||
nil
|
||||
end
|
||||
@@ -207,6 +216,10 @@ module ActionController
|
||||
ActiveSupport::SecureRandom.hex(16)
|
||||
end
|
||||
|
||||
def destroy(env)
|
||||
# session data is stored on client; nothing to do here
|
||||
end
|
||||
|
||||
def persistent_session_id!(data)
|
||||
(data ||= {}).merge!(inject_persistent_session_id(data))
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
begin
|
||||
require_library_or_gem 'memcache'
|
||||
|
||||
require 'thread'
|
||||
module ActionController
|
||||
module Session
|
||||
class MemCacheStore < AbstractStore
|
||||
@@ -43,6 +43,15 @@ begin
|
||||
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
||||
return false
|
||||
end
|
||||
|
||||
def destroy(env)
|
||||
if sid = current_session_id(env)
|
||||
@pool.delete(sid)
|
||||
end
|
||||
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -450,7 +450,7 @@ module ActionController #:nodoc:
|
||||
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
|
||||
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
|
||||
@request.env['HTTP_ACCEPT'] = [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
|
||||
returning __send__(request_method, action, parameters, session, flash) do
|
||||
__send__(request_method, action, parameters, session, flash).tap do
|
||||
@request.env.delete 'HTTP_X_REQUESTED_WITH'
|
||||
@request.env.delete 'HTTP_ACCEPT'
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
require 'uri'
|
||||
|
||||
module ActionController
|
||||
# In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
|
||||
# is also possible: an URL can be generated from one of your routing definitions.
|
||||
@@ -92,6 +94,14 @@ module ActionController
|
||||
# end
|
||||
# end
|
||||
module UrlWriter
|
||||
RESERVED_PCHAR = ':@&=+$,;%'
|
||||
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
|
||||
if RUBY_VERSION >= '1.9'
|
||||
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze
|
||||
else
|
||||
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
|
||||
end
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
ActionController::Routing::Routes.install_helpers(base)
|
||||
base.mattr_accessor :default_url_options
|
||||
@@ -142,7 +152,7 @@ module ActionController
|
||||
end
|
||||
trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
|
||||
url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor]
|
||||
anchor = "##{URI.escape(options.delete(:anchor).to_param.to_s, UNSAFE_PCHAR)}" if options[:anchor]
|
||||
generated = Routing::Routes.generate(options, {})
|
||||
url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
|
||||
url << anchor if anchor
|
||||
|
||||
@@ -162,7 +162,7 @@ module HTML #:nodoc:
|
||||
end
|
||||
|
||||
closing = ( scanner.scan(/\//) ? :close : nil )
|
||||
return Text.new(parent, line, pos, content) unless name = scanner.scan(/[-:\w\x00-\x09\x0b-\x0c\x0e-\x1f]+/)
|
||||
return Text.new(parent, line, pos, content) unless name = scanner.scan(/[^\s!>\/]+/)
|
||||
name.downcase!
|
||||
|
||||
unless closing
|
||||
|
||||
@@ -2,7 +2,7 @@ module ActionPack #:nodoc:
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 3
|
||||
TINY = 8
|
||||
TINY = 14
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
||||
@@ -6,6 +6,7 @@ module ActionView #:nodoc:
|
||||
autoload :BenchmarkHelper, 'action_view/helpers/benchmark_helper'
|
||||
autoload :CacheHelper, 'action_view/helpers/cache_helper'
|
||||
autoload :CaptureHelper, 'action_view/helpers/capture_helper'
|
||||
autoload :CsrfHelper, 'action_view/helpers/csrf_helper'
|
||||
autoload :DateHelper, 'action_view/helpers/date_helper'
|
||||
autoload :DebugHelper, 'action_view/helpers/debug_helper'
|
||||
autoload :FormHelper, 'action_view/helpers/form_helper'
|
||||
@@ -38,6 +39,7 @@ module ActionView #:nodoc:
|
||||
include BenchmarkHelper
|
||||
include CacheHelper
|
||||
include CaptureHelper
|
||||
include CsrfHelper
|
||||
include DateHelper
|
||||
include DebugHelper
|
||||
include FormHelper
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
require 'cgi'
|
||||
require 'action_view/helpers/url_helper'
|
||||
require 'action_view/helpers/tag_helper'
|
||||
require 'thread'
|
||||
|
||||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
|
||||
14
actionpack/lib/action_view/helpers/csrf_helper.rb
Normal file
14
actionpack/lib/action_view/helpers/csrf_helper.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
module ActionView
|
||||
# = Action View CSRF Helper
|
||||
module Helpers
|
||||
module CsrfHelper
|
||||
# Returns a meta tag with the cross-site request forgery protection token
|
||||
# for forms to use. Place this in your head.
|
||||
def csrf_meta_tag
|
||||
if protect_against_forgery?
|
||||
%(<meta name="csrf-param" content="#{h(request_forgery_protection_token)}"/>\n<meta name="csrf-token" content="#{h(form_authenticity_token)}"/>).html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -665,7 +665,7 @@ module ActionView
|
||||
#
|
||||
# The HTML specification says unchecked check boxes are not successful, and
|
||||
# thus web browsers do not send them. Unfortunately this introduces a gotcha:
|
||||
# if an Invoice model has a +paid+ flag, and in the form that edits a paid
|
||||
# if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
|
||||
# invoice the user unchecks its check box, no +paid+ parameter is sent. So,
|
||||
# any mass-assignment idiom like
|
||||
#
|
||||
@@ -673,12 +673,15 @@ module ActionView
|
||||
#
|
||||
# wouldn't update the flag.
|
||||
#
|
||||
# To prevent this the helper generates a hidden field with the same name as
|
||||
# the checkbox after the very check box. So, the client either sends only the
|
||||
# hidden field (representing the check box is unchecked), or both fields.
|
||||
# Since the HTML specification says key/value pairs have to be sent in the
|
||||
# same order they appear in the form and Rails parameters extraction always
|
||||
# gets the first occurrence of any given key, that works in ordinary forms.
|
||||
# To prevent this the helper generates an auxiliary hidden field before
|
||||
# the very check box. The hidden field has the same name and its
|
||||
# attributes mimick an unchecked check box.
|
||||
#
|
||||
# This way, the client either sends only the hidden field (representing
|
||||
# the check box is unchecked), or both fields. Since the HTML specification
|
||||
# says key/value pairs have to be sent in the same order they appear in the
|
||||
# form, and parameters extraction gets the last occurrence of any repeated
|
||||
# key in the query string, that works for ordinary forms.
|
||||
#
|
||||
# Unfortunately that workaround does not work when the check box goes
|
||||
# within an array-like parameter, as in
|
||||
@@ -689,22 +692,26 @@ module ActionView
|
||||
# <% end %>
|
||||
#
|
||||
# because parameter name repetition is precisely what Rails seeks to distinguish
|
||||
# the elements of the array.
|
||||
# the elements of the array. For each item with a checked check box you
|
||||
# get an extra ghost item with only that attribute, assigned to "0".
|
||||
#
|
||||
# In that case it is preferable to either use +check_box_tag+ or to use
|
||||
# hashes instead of arrays.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Let's say that @post.validated? is 1:
|
||||
# check_box("post", "validated")
|
||||
# # => <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
|
||||
# # <input name="post[validated]" type="hidden" value="0" />
|
||||
# # => <input name="post[validated]" type="hidden" value="0" />
|
||||
# # <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
|
||||
#
|
||||
# # Let's say that @puppy.gooddog is "no":
|
||||
# check_box("puppy", "gooddog", {}, "yes", "no")
|
||||
# # => <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
|
||||
# # <input name="puppy[gooddog]" type="hidden" value="no" />
|
||||
# # => <input name="puppy[gooddog]" type="hidden" value="no" />
|
||||
# # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
|
||||
#
|
||||
# check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
|
||||
# # => <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
|
||||
# # <input name="eula[accepted]" type="hidden" value="no" />
|
||||
# # => <input name="eula[accepted]" type="hidden" value="no" />
|
||||
# # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
|
||||
#
|
||||
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
|
||||
InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
|
||||
|
||||
@@ -481,7 +481,7 @@ module ActionView
|
||||
end
|
||||
|
||||
zone_options += options_for_select(convert_zones[zones], selected)
|
||||
zone_options
|
||||
zone_options.html_safe
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -440,7 +440,7 @@ module ActionView
|
||||
|
||||
private
|
||||
def html_options_for_form(url_for_options, options, *parameters_for_url)
|
||||
returning options.stringify_keys do |html_options|
|
||||
options.stringify_keys.tap do |html_options|
|
||||
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
|
||||
html_options["action"] = url_for(url_for_options, *parameters_for_url)
|
||||
end
|
||||
|
||||
@@ -653,7 +653,7 @@ module ActionView
|
||||
# <script> tag.
|
||||
module GeneratorMethods
|
||||
def to_s #:nodoc:
|
||||
returning javascript = @lines * $/ do
|
||||
(@lines * $/).tap do |javascript|
|
||||
if ActionView::Base.debug_rjs
|
||||
source = javascript.dup
|
||||
javascript.replace "try {\n#{source}\n} catch (e) "
|
||||
@@ -981,8 +981,8 @@ module ActionView
|
||||
end
|
||||
|
||||
def record(line)
|
||||
returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do
|
||||
self << line
|
||||
"#{line.to_s.chomp.gsub(/\;\z/, '')};".tap do |_line|
|
||||
self << _line
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -532,9 +532,14 @@ module ActionView
|
||||
end
|
||||
|
||||
AUTO_LINK_RE = %r{
|
||||
( https?:// | www\. )
|
||||
(?: ([\w+.:-]+:)// | www\. )
|
||||
[^\s<]+
|
||||
}x unless const_defined?(:AUTO_LINK_RE)
|
||||
}x
|
||||
|
||||
# regexps for determining context, used high-volume
|
||||
AUTO_LINK_CRE = [/<[^>]+$/, /^[^>]*>/, /<a\b.*?>/i, /<\/a>/i]
|
||||
|
||||
AUTO_EMAIL_RE = /[\w.!#\$%+-]+@[\w-]+(?:\.[\w-]+)+/
|
||||
|
||||
BRACKETS = { ']' => '[', ')' => '(', '}' => '{' }
|
||||
|
||||
@@ -543,26 +548,26 @@ module ActionView
|
||||
def auto_link_urls(text, html_options = {})
|
||||
link_attributes = html_options.stringify_keys
|
||||
text.gsub(AUTO_LINK_RE) do
|
||||
href = $&
|
||||
punctuation = ''
|
||||
left, right = $`, $'
|
||||
# detect already linked URLs and URLs in the middle of a tag
|
||||
if left =~ /<[^>]+$/ && right =~ /^[^>]*>/
|
||||
scheme, href = $1, $&
|
||||
punctuation = []
|
||||
|
||||
if auto_linked?($`, $')
|
||||
# do not change string; URL is already linked
|
||||
href
|
||||
else
|
||||
# don't include trailing punctuation character as part of the URL
|
||||
if href.sub!(/[^\w\/-]$/, '') and punctuation = $& and opening = BRACKETS[punctuation]
|
||||
if href.scan(opening).size > href.scan(punctuation).size
|
||||
href << punctuation
|
||||
punctuation = ''
|
||||
while href.sub!(/[^\w\/-]$/, '')
|
||||
punctuation.push $&
|
||||
if opening = BRACKETS[punctuation.last] and href.scan(opening).size > href.scan(punctuation.last).size
|
||||
href << punctuation.pop
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
link_text = block_given?? yield(href) : href
|
||||
href = 'http://' + href unless href =~ %r{^[a-z]+://}i
|
||||
href = 'http://' + href unless scheme
|
||||
|
||||
content_tag(:a, h(link_text), link_attributes.merge('href' => href)) + punctuation
|
||||
content_tag(:a, h(link_text), link_attributes.merge('href' => href)) + punctuation.reverse.join('')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -570,11 +575,10 @@ module ActionView
|
||||
# Turns all email addresses into clickable links. If a block is given,
|
||||
# each email is yielded and the result is used as the link text.
|
||||
def auto_link_email_addresses(text, html_options = {})
|
||||
body = text.dup
|
||||
text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
|
||||
text = $1
|
||||
text.gsub(AUTO_EMAIL_RE) do
|
||||
text = $&
|
||||
|
||||
if body.match(/<a\b[^>]*>(.*)(#{Regexp.escape(text)})(.*)<\/a>/)
|
||||
if auto_linked?($`, $')
|
||||
text
|
||||
else
|
||||
display_text = (block_given?) ? yield(text) : text
|
||||
@@ -582,6 +586,12 @@ module ActionView
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Detects already linked context or position in the middle of a tag
|
||||
def auto_linked?(left, right)
|
||||
(left =~ AUTO_LINK_CRE[0] and right =~ AUTO_LINK_CRE[1]) or
|
||||
(left.rindex(AUTO_LINK_CRE[2]) and $' !~ AUTO_LINK_CRE[3])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -471,7 +471,8 @@ module ActionView
|
||||
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")
|
||||
|
||||
if encode == "javascript"
|
||||
"document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
|
||||
html = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:"+html_escape(email_address)+extras }))
|
||||
"document.write('#{escape_javascript(html)}');".each_byte do |c|
|
||||
string << sprintf("%%%x", c)
|
||||
end
|
||||
"<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>"
|
||||
|
||||
@@ -63,37 +63,37 @@
|
||||
half_a_minute: "half a minute"
|
||||
less_than_x_seconds:
|
||||
one: "less than 1 second"
|
||||
other: "less than {{count}} seconds"
|
||||
other: "less than %{count} seconds"
|
||||
x_seconds:
|
||||
one: "1 second"
|
||||
other: "{{count}} seconds"
|
||||
other: "%{count} seconds"
|
||||
less_than_x_minutes:
|
||||
one: "less than a minute"
|
||||
other: "less than {{count}} minutes"
|
||||
other: "less than %{count} minutes"
|
||||
x_minutes:
|
||||
one: "1 minute"
|
||||
other: "{{count}} minutes"
|
||||
other: "%{count} minutes"
|
||||
about_x_hours:
|
||||
one: "about 1 hour"
|
||||
other: "about {{count}} hours"
|
||||
other: "about %{count} hours"
|
||||
x_days:
|
||||
one: "1 day"
|
||||
other: "{{count}} days"
|
||||
other: "%{count} days"
|
||||
about_x_months:
|
||||
one: "about 1 month"
|
||||
other: "about {{count}} months"
|
||||
other: "about %{count} months"
|
||||
x_months:
|
||||
one: "1 month"
|
||||
other: "{{count}} months"
|
||||
other: "%{count} months"
|
||||
about_x_years:
|
||||
one: "about 1 year"
|
||||
other: "about {{count}} years"
|
||||
other: "about %{count} years"
|
||||
over_x_years:
|
||||
one: "over 1 year"
|
||||
other: "over {{count}} years"
|
||||
other: "over %{count} years"
|
||||
almost_x_years:
|
||||
one: "almost 1 year"
|
||||
other: "almost {{count}} years"
|
||||
other: "almost %{count} years"
|
||||
prompts:
|
||||
year: "Year"
|
||||
month: "Month"
|
||||
@@ -106,12 +106,12 @@
|
||||
errors:
|
||||
template:
|
||||
header:
|
||||
one: "1 error prohibited this {{model}} from being saved"
|
||||
other: "{{count}} errors prohibited this {{model}} from being saved"
|
||||
one: "1 error prohibited this %{model} from being saved"
|
||||
other: "%{count} errors prohibited this %{model} from being saved"
|
||||
# The variable :count is also available
|
||||
body: "There were problems with the following fields:"
|
||||
|
||||
support:
|
||||
select:
|
||||
# default value for :prompt => true in FormOptionsHelper
|
||||
prompt: "Please select"
|
||||
prompt: "Please select"
|
||||
|
||||
@@ -27,7 +27,7 @@ module ActionView
|
||||
def render_partial(view, object = nil, local_assigns = {}, as = nil)
|
||||
object ||= local_assigns[:object] || local_assigns[variable_name]
|
||||
|
||||
if object.nil? && view.respond_to?(:controller)
|
||||
if object.nil? && !local_assigns_key?(local_assigns) && view.respond_to?(:controller)
|
||||
ivar = :"@#{variable_name}"
|
||||
object =
|
||||
if view.controller.instance_variable_defined?(ivar)
|
||||
@@ -43,5 +43,11 @@ module ActionView
|
||||
|
||||
render_template(view, local_assigns)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def local_assigns_key?(local_assigns)
|
||||
local_assigns.key?(:object) || local_assigns.key?(variable_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,8 +45,8 @@ module ActionView #:nodoc:
|
||||
end
|
||||
|
||||
def self.new_and_loaded(path)
|
||||
returning new(path) do |path|
|
||||
path.load!
|
||||
new(path).tap do |_path|
|
||||
_path.load!
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -58,4 +58,21 @@ class DummyMutex
|
||||
end
|
||||
end
|
||||
|
||||
class ActionController::IntegrationTest < ActiveSupport::TestCase
|
||||
def with_autoload_path(path)
|
||||
path = File.join(File.dirname(__FILE__), "fixtures", path)
|
||||
if ActiveSupport::Dependencies.autoload_paths.include?(path)
|
||||
yield
|
||||
else
|
||||
begin
|
||||
ActiveSupport::Dependencies.autoload_paths << path
|
||||
yield
|
||||
ensure
|
||||
ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path}
|
||||
ActiveSupport::Dependencies.clear
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ActionController::Reloader.default_lock = DummyMutex.new
|
||||
|
||||
@@ -22,7 +22,6 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
|
||||
end
|
||||
|
||||
def get_session_id
|
||||
session[:foo]
|
||||
render :text => "#{request.session_options[:id]}"
|
||||
end
|
||||
|
||||
@@ -45,23 +44,27 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
|
||||
ActiveRecord::SessionStore.session_class.drop_table!
|
||||
end
|
||||
|
||||
def test_setting_and_getting_session_value
|
||||
with_test_route_set do
|
||||
get '/set_session_value'
|
||||
assert_response :success
|
||||
assert cookies['_session_id']
|
||||
%w{ session sql_bypass }.each do |class_name|
|
||||
define_method("test_setting_and_getting_session_value_with_#{class_name}_store") do
|
||||
with_store class_name do
|
||||
with_test_route_set do
|
||||
get '/set_session_value'
|
||||
assert_response :success
|
||||
assert cookies['_session_id']
|
||||
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: "bar"', response.body
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: "bar"', response.body
|
||||
|
||||
get '/set_session_value', :foo => "baz"
|
||||
assert_response :success
|
||||
assert cookies['_session_id']
|
||||
get '/set_session_value', :foo => "baz"
|
||||
assert_response :success
|
||||
assert cookies['_session_id']
|
||||
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: "baz"', response.body
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: "baz"', response.body
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -107,6 +110,38 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
|
||||
end
|
||||
end
|
||||
|
||||
def test_getting_session_value
|
||||
with_test_route_set do
|
||||
get '/set_session_value'
|
||||
assert_response :success
|
||||
assert cookies['_session_id']
|
||||
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
|
||||
session_id = cookies["_session_id"]
|
||||
|
||||
get '/call_reset_session'
|
||||
assert_response :success
|
||||
assert_not_equal [], headers['Set-Cookie']
|
||||
|
||||
cookies["_session_id"] = session_id # replace our new session_id with our old, pre-reset session_id
|
||||
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from the database"
|
||||
end
|
||||
end
|
||||
|
||||
def test_getting_from_nonexistent_session
|
||||
with_test_route_set do
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: nil', response.body
|
||||
assert_nil cookies['_session_id'], "should only create session on write, not read"
|
||||
end
|
||||
end
|
||||
|
||||
def test_prevents_session_fixation
|
||||
with_test_route_set do
|
||||
get '/set_session_value'
|
||||
@@ -171,4 +206,16 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def with_store(class_name)
|
||||
begin
|
||||
session_class = ActiveRecord::SessionStore.session_class
|
||||
ActiveRecord::SessionStore.session_class = "ActiveRecord::SessionStore::#{class_name.camelize}".constantize
|
||||
yield
|
||||
rescue
|
||||
ActiveRecord::SessionStore.session_class = session_class
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -46,6 +46,11 @@ class ContentTypeController < ActionController::Base
|
||||
format.rss { render :text => "hello world!", :content_type => Mime::XML }
|
||||
end
|
||||
end
|
||||
|
||||
def render_content_type_from_user_input
|
||||
response.content_type= params[:hello]
|
||||
render :text=>"hello"
|
||||
end
|
||||
|
||||
def rescue_action(e) raise end
|
||||
end
|
||||
@@ -129,6 +134,11 @@ class ContentTypeTest < ActionController::TestCase
|
||||
assert_equal Mime::HTML, @response.content_type
|
||||
assert_equal "utf-8", @response.charset
|
||||
end
|
||||
|
||||
def test_user_supplied_value
|
||||
get :render_content_type_from_user_input, :hello=>"hello/world\r\nAttack: true"
|
||||
assert_equal "hello/world%0D%0AAttack: true", @response.content_type
|
||||
end
|
||||
end
|
||||
|
||||
class AcceptBasedContentTypeTest < ActionController::TestCase
|
||||
|
||||
@@ -42,6 +42,10 @@ class CookieTest < ActionController::TestCase
|
||||
cookies["user_name"] = { :value => "david", :httponly => true }
|
||||
end
|
||||
|
||||
def authenticate_with_secure
|
||||
cookies["user_name"] = { :value => "david", :secure => true }
|
||||
end
|
||||
|
||||
def set_permanent_cookie
|
||||
cookies.permanent[:user_name] = "Jamie"
|
||||
end
|
||||
@@ -94,6 +98,27 @@ class CookieTest < ActionController::TestCase
|
||||
assert_equal ["user_name=david; path=/; HttpOnly"], @response.headers["Set-Cookie"]
|
||||
assert_equal({"user_name" => "david"}, @response.cookies)
|
||||
end
|
||||
|
||||
def test_setting_cookie_with_secure
|
||||
@request.env["HTTPS"] = "on"
|
||||
get :authenticate_with_secure
|
||||
assert_equal ["user_name=david; path=/; secure"], @response.headers["Set-Cookie"]
|
||||
assert_equal({"user_name" => "david"}, @response.cookies)
|
||||
end
|
||||
|
||||
def test_setting_cookie_with_secure_in_development
|
||||
with_environment(:development) do
|
||||
get :authenticate_with_secure
|
||||
assert_equal ["user_name=david; path=/; secure"], @response.headers["Set-Cookie"]
|
||||
assert_equal({"user_name" => "david"}, @response.cookies)
|
||||
end
|
||||
end
|
||||
|
||||
def test_not_setting_cookie_with_secure
|
||||
get :authenticate_with_secure
|
||||
assert_not_equal ["user_name=david; path=/; secure"], @response.headers["Set-Cookie"]
|
||||
assert_not_equal({"user_name" => "david"}, @response.cookies)
|
||||
end
|
||||
|
||||
def test_multiple_cookies
|
||||
get :set_multiple_cookies
|
||||
@@ -167,4 +192,17 @@ class CookieTest < ActionController::TestCase
|
||||
assert_match %r(#{20.years.from_now.year}), @response.headers["Set-Cookie"].first
|
||||
assert_equal 100, @controller.send(:cookies).signed[:remember_me]
|
||||
end
|
||||
|
||||
private
|
||||
def with_environment(enviroment)
|
||||
old_rails = Object.const_get(:Rails) rescue nil
|
||||
mod = Object.const_set(:Rails, Module.new)
|
||||
(class << mod; self; end).instance_eval do
|
||||
define_method(:env) { @_env ||= ActiveSupport::StringInquirer.new(enviroment.to_s) }
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
Object.module_eval { remove_const(:Rails) } if defined?(Rails)
|
||||
Object.const_set(:Rails, old_rails) if old_rails
|
||||
end
|
||||
end
|
||||
@@ -5,6 +5,13 @@ class SanitizerTest < ActionController::TestCase
|
||||
@sanitizer = nil # used by assert_sanitizer
|
||||
end
|
||||
|
||||
def test_strip_tags_with_quote
|
||||
sanitizer = HTML::FullSanitizer.new
|
||||
string = '<" <img src="trollface.gif" onload="alert(1)"> hi'
|
||||
|
||||
assert_equal ' hi', sanitizer.sanitize(string)
|
||||
end
|
||||
|
||||
def test_strip_tags
|
||||
sanitizer = HTML::FullSanitizer.new
|
||||
assert_equal("<<<bad html", sanitizer.sanitize("<<<bad html"))
|
||||
|
||||
@@ -227,6 +227,24 @@ class IntegrationTestTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
require 'active_record_unit'
|
||||
# Tests that fixtures are accessible in the integration test sessions
|
||||
class IntegrationTestWithFixtures < ActiveRecordTestCase
|
||||
include ActionController::Integration::Runner
|
||||
|
||||
fixtures :companies
|
||||
|
||||
def test_fixtures_in_new_session
|
||||
sym = :thirty_seven_signals
|
||||
# fixtures are accessible in main session
|
||||
assert_not_nil companies(sym)
|
||||
|
||||
# create a new session and the fixtures should be accessible in it as well
|
||||
session1 = open_session { |sess| }
|
||||
assert_not_nil session1.companies(sym)
|
||||
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
|
||||
@@ -266,6 +284,14 @@ class IntegrationProcessTest < ActionController::IntegrationTest
|
||||
render :text => "foo(1i): #{params[:"foo(1i)"]}, foo(2i): #{params[:"foo(2i)"]}, filesize: #{params[:file].size}", :status => 200
|
||||
end
|
||||
|
||||
def multipart_post_with_nested_params
|
||||
render :text => "foo: #{params[:foo][0]}, #{params[:foo][1]}; [filesize: #{params[:file_list][0][:content].size}, filesize: #{params[:file_list][1][:content].size}]", :status => 200
|
||||
end
|
||||
|
||||
def multipart_post_with_multiparameter_complex_params
|
||||
render :text => "foo(1i): #{params[:"foo(1i)"]}, foo(2i): #{params[:"foo(2i)"]}, [filesize: #{params[:file_list][0][:content].size}, filesize: #{params[:file_list][1][:content].size}]", :status => 200
|
||||
end
|
||||
|
||||
def post
|
||||
render :text => "Created", :status => 201
|
||||
end
|
||||
@@ -405,6 +431,24 @@ class IntegrationProcessTest < ActionController::IntegrationTest
|
||||
end
|
||||
end
|
||||
|
||||
def test_multipart_post_with_nested_params
|
||||
with_test_route_set do
|
||||
post '/multipart_post_with_nested_params', :"foo" => ['a', 'b'], :file_list => [{:content => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")}, {:content => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")}]
|
||||
|
||||
assert_equal 200, status
|
||||
assert_equal "foo: a, b; [filesize: 159528, filesize: 159528]", response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_multipart_post_with_multiparameter_complex_attribute_parameters
|
||||
with_test_route_set do
|
||||
post '/multipart_post_with_multiparameter_complex_params', :"foo(1i)" => "bar", :"foo(2i)" => "baz", :file_list => [{:content => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")}, {:content => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")}]
|
||||
|
||||
assert_equal 200, status
|
||||
assert_equal "foo(1i): bar, foo(2i): baz, [filesize: 159528, filesize: 159528]", response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_head
|
||||
with_test_route_set do
|
||||
head '/get'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
require 'abstract_unit'
|
||||
require 'thread'
|
||||
|
||||
class ReloaderTests < ActiveSupport::TestCase
|
||||
Reloader = ActionController::Reloader
|
||||
|
||||
@@ -716,6 +716,11 @@ class TestController < ActionController::Base
|
||||
render :partial => "customer"
|
||||
end
|
||||
|
||||
def partial_with_implicit_local_assignment_and_nil_local
|
||||
@customer = Customer.new("Marcel")
|
||||
render :partial => "customer", :locals => { :customer => nil }
|
||||
end
|
||||
|
||||
def render_call_to_partial_with_layout
|
||||
render :action => "calling_partial_with_layout"
|
||||
end
|
||||
@@ -1543,6 +1548,13 @@ class RenderTest < ActionController::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def test_partial_with_implicit_local_assignment_and_nil_local
|
||||
assert_not_deprecated do
|
||||
get :partial_with_implicit_local_assignment_and_nil_local
|
||||
assert_equal "Hello: Anonymous", @response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_render_missing_partial_template
|
||||
assert_raise(ActionView::MissingTemplate) do
|
||||
get :missing_partial
|
||||
|
||||
@@ -14,6 +14,10 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest
|
||||
def read
|
||||
render :text => "File: #{params[:uploaded_data].read}"
|
||||
end
|
||||
|
||||
def read_complex
|
||||
render :text => "File: #{params[:level0][:level1][0][:file_data].read}"
|
||||
end
|
||||
end
|
||||
|
||||
FIXTURE_PATH = File.dirname(__FILE__) + '/../../fixtures/multipart'
|
||||
@@ -133,6 +137,17 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest
|
||||
end
|
||||
end
|
||||
|
||||
test "uploads and reads file in complex parameter" do
|
||||
with_test_routing do
|
||||
post '/read_complex',
|
||||
:level0 => {
|
||||
:level1 => [ { :file_data => fixture_file_upload(FIXTURE_PATH + "/hello.txt", "text/plain") }
|
||||
]
|
||||
}
|
||||
assert_equal "File: Hello", response.body
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def fixture(name)
|
||||
File.open(File.join(FIXTURE_PATH, name), 'rb') do |file|
|
||||
|
||||
@@ -23,6 +23,10 @@ module RequestForgeryProtectionActions
|
||||
render :text => 'pwn'
|
||||
end
|
||||
|
||||
def meta
|
||||
render :inline => "<%= csrf_meta_tag %>"
|
||||
end
|
||||
|
||||
def rescue_action(e) raise e end
|
||||
end
|
||||
|
||||
@@ -32,6 +36,16 @@ class RequestForgeryProtectionController < ActionController::Base
|
||||
protect_from_forgery :only => :index
|
||||
end
|
||||
|
||||
class RequestForgeryProtectionControllerUsingOldBehaviour < ActionController::Base
|
||||
include RequestForgeryProtectionActions
|
||||
protect_from_forgery :only => %w(index meta)
|
||||
|
||||
def handle_unverified_request
|
||||
raise(ActionController::InvalidAuthenticityToken)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class FreeCookieController < RequestForgeryProtectionController
|
||||
self.allow_forgery_protection = false
|
||||
|
||||
@@ -54,158 +68,92 @@ end
|
||||
# common test methods
|
||||
|
||||
module RequestForgeryProtectionTests
|
||||
def teardown
|
||||
ActionController::Base.request_forgery_protection_token = nil
|
||||
def setup
|
||||
@token = "cf50faa3fe97702ca1ae"
|
||||
|
||||
ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
|
||||
ActionController::Base.request_forgery_protection_token = :authenticity_token
|
||||
end
|
||||
|
||||
|
||||
|
||||
def test_should_render_form_with_token_tag
|
||||
get :index
|
||||
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
|
||||
end
|
||||
|
||||
def test_should_render_button_to_with_token_tag
|
||||
get :show_button
|
||||
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
|
||||
end
|
||||
|
||||
def test_should_render_remote_form_with_only_one_token_parameter
|
||||
get :remote_form
|
||||
assert_equal 1, @response.body.scan(@token).size
|
||||
end
|
||||
|
||||
def test_should_allow_get
|
||||
get :index
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def test_should_allow_post_without_token_on_unsafe_action
|
||||
post :unsafe
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def test_should_not_allow_html_post_without_token
|
||||
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
|
||||
assert_raise(ActionController::InvalidAuthenticityToken) { post :index, :format => :html }
|
||||
end
|
||||
|
||||
def test_should_not_allow_html_put_without_token
|
||||
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
|
||||
assert_raise(ActionController::InvalidAuthenticityToken) { put :index, :format => :html }
|
||||
end
|
||||
|
||||
def test_should_not_allow_html_delete_without_token
|
||||
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
|
||||
assert_raise(ActionController::InvalidAuthenticityToken) { delete :index, :format => :html }
|
||||
end
|
||||
|
||||
def test_should_allow_api_formatted_post_without_token
|
||||
assert_nothing_raised do
|
||||
post :index, :format => 'xml'
|
||||
assert_not_blocked do
|
||||
get :index
|
||||
end
|
||||
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
|
||||
end
|
||||
|
||||
def test_should_not_allow_api_formatted_put_without_token
|
||||
assert_nothing_raised do
|
||||
put :index, :format => 'xml'
|
||||
def test_should_render_button_to_with_token_tag
|
||||
assert_not_blocked do
|
||||
get :show_button
|
||||
end
|
||||
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
|
||||
end
|
||||
|
||||
def test_should_allow_api_formatted_delete_without_token
|
||||
assert_nothing_raised do
|
||||
delete :index, :format => 'xml'
|
||||
end
|
||||
def test_should_allow_get
|
||||
assert_not_blocked { get :index }
|
||||
end
|
||||
|
||||
def test_should_not_allow_api_formatted_post_sent_as_url_encoded_form_without_token
|
||||
assert_raise(ActionController::InvalidAuthenticityToken) do
|
||||
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
|
||||
post :index, :format => 'xml'
|
||||
end
|
||||
def test_should_allow_post_without_token_on_unsafe_action
|
||||
assert_not_blocked { post :unsafe }
|
||||
end
|
||||
|
||||
def test_should_not_allow_api_formatted_put_sent_as_url_encoded_form_without_token
|
||||
assert_raise(ActionController::InvalidAuthenticityToken) do
|
||||
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
|
||||
put :index, :format => 'xml'
|
||||
end
|
||||
def test_should_not_allow_post_without_token
|
||||
assert_blocked { post :index }
|
||||
end
|
||||
|
||||
def test_should_not_allow_api_formatted_delete_sent_as_url_encoded_form_without_token
|
||||
assert_raise(ActionController::InvalidAuthenticityToken) do
|
||||
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
|
||||
delete :index, :format => 'xml'
|
||||
end
|
||||
def test_should_not_allow_post_without_token_irrespective_of_format
|
||||
assert_blocked { post :index, :format=>'xml' }
|
||||
end
|
||||
|
||||
def test_should_not_allow_api_formatted_post_sent_as_multipart_form_without_token
|
||||
assert_raise(ActionController::InvalidAuthenticityToken) do
|
||||
@request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
|
||||
post :index, :format => 'xml'
|
||||
end
|
||||
def test_should_not_allow_put_without_token
|
||||
assert_blocked { put :index }
|
||||
end
|
||||
|
||||
def test_should_not_allow_api_formatted_put_sent_as_multipart_form_without_token
|
||||
assert_raise(ActionController::InvalidAuthenticityToken) do
|
||||
@request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
|
||||
put :index, :format => 'xml'
|
||||
end
|
||||
def test_should_not_allow_delete_without_token
|
||||
assert_blocked { delete :index }
|
||||
end
|
||||
|
||||
def test_should_not_allow_api_formatted_delete_sent_as_multipart_form_without_token
|
||||
assert_raise(ActionController::InvalidAuthenticityToken) do
|
||||
@request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
|
||||
delete :index, :format => 'xml'
|
||||
end
|
||||
def test_should_not_allow_xhr_post_without_token
|
||||
assert_blocked { xhr :post, :index }
|
||||
end
|
||||
|
||||
def test_should_allow_xhr_post_without_token
|
||||
assert_nothing_raised { xhr :post, :index }
|
||||
end
|
||||
|
||||
def test_should_allow_xhr_put_without_token
|
||||
assert_nothing_raised { xhr :put, :index }
|
||||
end
|
||||
|
||||
def test_should_allow_xhr_delete_without_token
|
||||
assert_nothing_raised { xhr :delete, :index }
|
||||
end
|
||||
|
||||
def test_should_allow_xhr_post_with_encoded_form_content_type_without_token
|
||||
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
|
||||
assert_nothing_raised { xhr :post, :index }
|
||||
end
|
||||
|
||||
|
||||
def test_should_allow_post_with_token
|
||||
post :index, :authenticity_token => @token
|
||||
assert_response :success
|
||||
assert_not_blocked { post :index, :authenticity_token => @token }
|
||||
end
|
||||
|
||||
def test_should_allow_put_with_token
|
||||
put :index, :authenticity_token => @token
|
||||
assert_response :success
|
||||
assert_not_blocked { put :index, :authenticity_token => @token }
|
||||
end
|
||||
|
||||
def test_should_allow_delete_with_token
|
||||
delete :index, :authenticity_token => @token
|
||||
assert_not_blocked { delete :index, :authenticity_token => @token }
|
||||
end
|
||||
|
||||
def test_should_allow_post_with_token_in_header
|
||||
@request.env['HTTP_X_CSRF_TOKEN'] = @token
|
||||
assert_not_blocked { post :index }
|
||||
end
|
||||
|
||||
def test_should_allow_delete_with_token_in_header
|
||||
@request.env['HTTP_X_CSRF_TOKEN'] = @token
|
||||
assert_not_blocked { delete :index }
|
||||
end
|
||||
|
||||
def test_should_allow_put_with_token_in_header
|
||||
@request.env['HTTP_X_CSRF_TOKEN'] = @token
|
||||
assert_not_blocked { put :index }
|
||||
end
|
||||
|
||||
def assert_blocked
|
||||
session[:something_like_user_id] = 1
|
||||
yield
|
||||
assert_nil session[:something_like_user_id], "session values are still present"
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def test_should_allow_post_with_xml
|
||||
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
|
||||
post :index, :format => 'xml'
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def test_should_allow_put_with_xml
|
||||
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
|
||||
put :index, :format => 'xml'
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def test_should_allow_delete_with_xml
|
||||
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
|
||||
delete :index, :format => 'xml'
|
||||
def assert_not_blocked
|
||||
assert_nothing_raised { yield }
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
@@ -214,15 +162,20 @@ end
|
||||
|
||||
class RequestForgeryProtectionControllerTest < ActionController::TestCase
|
||||
include RequestForgeryProtectionTests
|
||||
def setup
|
||||
@controller = RequestForgeryProtectionController.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@request.format = :html
|
||||
@response = ActionController::TestResponse.new
|
||||
@token = "cf50faa3fe97702ca1ae"
|
||||
|
||||
ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
|
||||
ActionController::Base.request_forgery_protection_token = :authenticity_token
|
||||
test 'should emit a csrf-token meta tag' do
|
||||
ActiveSupport::SecureRandom.stubs(:base64).returns(@token + '<=?')
|
||||
get :meta
|
||||
assert_equal %(<meta name="csrf-param" content="authenticity_token"/>\n<meta name="csrf-token" content="cf50faa3fe97702ca1ae<=?"/>), @response.body
|
||||
end
|
||||
end
|
||||
|
||||
class RequestForgeryProtectionControllerUsingOldBehaviourTest < ActionController::TestCase
|
||||
include RequestForgeryProtectionTests
|
||||
def assert_blocked
|
||||
assert_raises(ActionController::InvalidAuthenticityToken) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -251,15 +204,30 @@ class FreeCookieControllerTest < ActionController::TestCase
|
||||
assert_nothing_raised { send(method, :index)}
|
||||
end
|
||||
end
|
||||
|
||||
test 'should not emit a csrf-token meta tag' do
|
||||
get :meta
|
||||
assert_blank @response.body
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class CustomAuthenticityParamControllerTest < ActionController::TestCase
|
||||
def setup
|
||||
ActionController::Base.request_forgery_protection_token = :custom_token_name
|
||||
super
|
||||
end
|
||||
|
||||
def teardown
|
||||
ActionController::Base.request_forgery_protection_token = :authenticity_token
|
||||
super
|
||||
end
|
||||
|
||||
def test_should_allow_custom_token
|
||||
post :index, :authenticity_token => 'foobar'
|
||||
post :index, :custom_token_name => 'foobar'
|
||||
assert_response :ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -281,12 +281,11 @@ class RescueControllerTest < ActionController::TestCase
|
||||
end
|
||||
|
||||
def test_local_request_when_remote_addr_is_localhost
|
||||
@controller.expects(:request).returns(@request).at_least(4)
|
||||
with_remote_addr '127.0.0.1' do
|
||||
assert @controller.send(:local_request?)
|
||||
end
|
||||
with_remote_addr '::1' do
|
||||
assert @controller.send(:local_request?)
|
||||
@controller.expects(:request).returns(@request).at_least(10)
|
||||
['127.0.0.1', '127.0.0.127', '::1', '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1%0'].each do |ip_address|
|
||||
with_remote_addr ip_address do
|
||||
assert @controller.send(:local_request?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
64
actionpack/test/controller/session/abstract_store_test.rb
Normal file
64
actionpack/test/controller/session/abstract_store_test.rb
Normal file
@@ -0,0 +1,64 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
# You need to start a memcached server inorder to run these tests
|
||||
class AbstractStoreTest < ActionController::IntegrationTest
|
||||
SessionKey = '_myapp_session'
|
||||
DispatcherApp = ActionController::Dispatcher.new
|
||||
|
||||
class TestController < ActionController::Base
|
||||
def get_session
|
||||
session[:test] = 'test'
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
def test_expiry_after
|
||||
with_test_route_set(:expire_after => 5 * 60) do
|
||||
get 'get_session'
|
||||
assert_response :success
|
||||
assert_match /expires=\S+/, headers['Set-Cookie']
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def with_test_route_set(options = {})
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.with_options :controller => "abstract_store_test/test" do |c|
|
||||
c.connect "/:action"
|
||||
end
|
||||
end
|
||||
|
||||
options = { :key => SessionKey, :secret => 'SessionSecret' }.merge!(options)
|
||||
@integration_session = open_session(TestStore.new(DispatcherApp, options))
|
||||
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
class TestStore < ActionController::Session::AbstractStore
|
||||
def initialize(app, options = {})
|
||||
super
|
||||
@_store = Hash.new({})
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_session(env, sid)
|
||||
sid ||= generate_sid
|
||||
session = @_store[sid]
|
||||
[sid, session]
|
||||
end
|
||||
|
||||
def set_session(env, sid, session_data)
|
||||
@_store[sid] = session_data
|
||||
end
|
||||
|
||||
def destroy(env)
|
||||
@_store.delete(sid)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -6,7 +6,6 @@ class CookieStoreTest < ActionController::IntegrationTest
|
||||
SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
|
||||
|
||||
DispatcherApp = ActionController::Dispatcher.new
|
||||
CookieStoreApp = ActionController::Session::CookieStore.new(DispatcherApp, :key => SessionKey, :secret => SessionSecret)
|
||||
|
||||
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1')
|
||||
|
||||
@@ -34,6 +33,21 @@ class CookieStoreTest < ActionController::IntegrationTest
|
||||
render :text => "foo: #{session[:foo].inspect}; id: #{request.session_options[:id]}"
|
||||
end
|
||||
|
||||
def get_session_id_only
|
||||
render :text => "id: #{request.session_options[:id]}"
|
||||
end
|
||||
|
||||
def call_session_clear
|
||||
session.clear
|
||||
head :ok
|
||||
end
|
||||
|
||||
def call_reset_session_twice
|
||||
reset_session
|
||||
reset_session
|
||||
head :ok
|
||||
end
|
||||
|
||||
def call_reset_session
|
||||
reset_session
|
||||
head :ok
|
||||
@@ -44,11 +58,13 @@ class CookieStoreTest < ActionController::IntegrationTest
|
||||
head :ok
|
||||
end
|
||||
|
||||
def rescue_action(e) raise end
|
||||
end
|
||||
def set_session_value_and_cookie
|
||||
cookies["foo"] = "bar"
|
||||
session[:foo] = "bar"
|
||||
render :text => Rack::Utils.escape(Verifier.generate(session.to_hash))
|
||||
end
|
||||
|
||||
def setup
|
||||
@integration_session = open_session(CookieStoreApp)
|
||||
def rescue_action(e) raise end
|
||||
end
|
||||
|
||||
def test_raises_argument_error_if_missing_session_key
|
||||
@@ -121,6 +137,10 @@ class CookieStoreTest < ActionController::IntegrationTest
|
||||
get '/get_session_id'
|
||||
assert_response :success
|
||||
assert_equal "foo: \"bar\"; id: #{session_id}", response.body
|
||||
|
||||
get '/get_session_id_only'
|
||||
assert_response :success
|
||||
assert_equal "id: #{session_id}", response.body, "should be able to read session id without accessing the session hash"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -133,6 +153,23 @@ class CookieStoreTest < ActionController::IntegrationTest
|
||||
end
|
||||
end
|
||||
|
||||
def test_does_not_set_secure_cookies_over_http
|
||||
with_test_route_set(:secure => true) do
|
||||
get '/set_session_value'
|
||||
assert_response :success
|
||||
assert_equal nil, headers['Set-Cookie']
|
||||
end
|
||||
end
|
||||
|
||||
def test_does_set_secure_cookies_over_https
|
||||
with_test_route_set(:secure => true) do
|
||||
get '/set_session_value', nil, 'HTTPS' => 'on'
|
||||
assert_response :success
|
||||
assert_equal "_myapp_session=#{response.body}; path=/; secure; HttpOnly",
|
||||
headers['Set-Cookie']
|
||||
end
|
||||
end
|
||||
|
||||
def test_close_raises_when_data_overflows
|
||||
with_test_route_set do
|
||||
assert_raise(ActionController::Session::CookieStore::CookieOverflow) {
|
||||
@@ -159,6 +196,25 @@ class CookieStoreTest < ActionController::IntegrationTest
|
||||
end
|
||||
end
|
||||
|
||||
def test_calling_session_reset_twice
|
||||
with_test_route_set do
|
||||
get '/set_session_value'
|
||||
assert_response :success
|
||||
session_payload = response.body
|
||||
assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
|
||||
headers['Set-Cookie']
|
||||
|
||||
get '/call_reset_session_twice'
|
||||
assert_response :success
|
||||
assert_not_equal "", headers['Set-Cookie']
|
||||
assert_not_equal session_payload, cookies[SessionKey]
|
||||
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: nil', response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_setting_session_value_after_session_reset
|
||||
with_test_route_set do
|
||||
get '/set_session_value'
|
||||
@@ -169,7 +225,7 @@ class CookieStoreTest < ActionController::IntegrationTest
|
||||
|
||||
get '/call_reset_session'
|
||||
assert_response :success
|
||||
assert_not_equal [], headers['Set-Cookie']
|
||||
assert_not_equal "", headers['Set-Cookie']
|
||||
assert_not_equal session_payload, cookies[SessionKey]
|
||||
|
||||
get '/get_session_value'
|
||||
@@ -178,6 +234,76 @@ class CookieStoreTest < ActionController::IntegrationTest
|
||||
end
|
||||
end
|
||||
|
||||
def test_setting_session_value_after_session_reset
|
||||
with_test_route_set do
|
||||
get '/set_session_value'
|
||||
assert_response :success
|
||||
session_payload = response.body
|
||||
assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
|
||||
headers['Set-Cookie']
|
||||
|
||||
get '/call_reset_session'
|
||||
assert_response :success
|
||||
assert_not_equal "", headers['Set-Cookie']
|
||||
assert_not_equal session_payload, cookies[SessionKey]
|
||||
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: nil', response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_setting_session_value_after_session_clear
|
||||
with_test_route_set do
|
||||
get '/set_session_value'
|
||||
assert_response :success
|
||||
session_payload = response.body
|
||||
assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
|
||||
headers['Set-Cookie']
|
||||
|
||||
get '/call_session_clear'
|
||||
assert_response :success
|
||||
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: nil', response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_getting_from_nonexistent_session
|
||||
with_test_route_set do
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: nil', response.body
|
||||
assert_nil headers['Set-Cookie'], "should only create session on write, not read"
|
||||
end
|
||||
end
|
||||
|
||||
# {:foo=>#<SessionAutoloadTest::Foo bar:"baz">, :session_id=>"ce8b0752a6ab7c7af3cdb8a80e6b9e46"}
|
||||
SignedSerializedCookie = "BAh7BzoIZm9vbzodU2Vzc2lvbkF1dG9sb2FkVGVzdDo6Rm9vBjoJQGJhciIIYmF6Og9zZXNzaW9uX2lkIiVjZThiMDc1MmE2YWI3YzdhZjNjZGI4YTgwZTZiOWU0Ng==--2bf3af1ae8bd4e52b9ac2099258ace0c380e601c"
|
||||
|
||||
def test_deserializes_unloaded_classes_on_get_id
|
||||
with_test_route_set do
|
||||
with_autoload_path "session_autoload_test" do
|
||||
cookies[SessionKey] = SignedSerializedCookie
|
||||
get '/get_session_id_only'
|
||||
assert_response :success
|
||||
assert_equal 'id: ce8b0752a6ab7c7af3cdb8a80e6b9e46', response.body, "should auto-load unloaded class"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_deserializes_unloaded_classes_on_get_value
|
||||
with_test_route_set do
|
||||
with_autoload_path "session_autoload_test" do
|
||||
cookies[SessionKey] = SignedSerializedCookie
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_persistent_session_id
|
||||
with_test_route_set do
|
||||
cookies[SessionKey] = SignedBar
|
||||
@@ -193,14 +319,26 @@ class CookieStoreTest < ActionController::IntegrationTest
|
||||
end
|
||||
end
|
||||
|
||||
def test_setting_session_value_and_cookie
|
||||
with_test_route_set do
|
||||
get '/set_session_value_and_cookie'
|
||||
assert_response :success
|
||||
assert_equal({"_myapp_session" => response.body, "foo" => "bar"}, cookies)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def with_test_route_set
|
||||
def with_test_route_set(options = {})
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.with_options :controller => "cookie_store_test/test" do |c|
|
||||
c.connect "/:action"
|
||||
end
|
||||
end
|
||||
|
||||
options = { :key => SessionKey, :secret => SessionSecret }.merge!(options)
|
||||
@integration_session = open_session(ActionController::Session::CookieStore.new(DispatcherApp, options))
|
||||
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,12 +12,16 @@ class MemCacheStoreTest < ActionController::IntegrationTest
|
||||
head :ok
|
||||
end
|
||||
|
||||
def set_serialized_session_value
|
||||
session[:foo] = SessionAutoloadTest::Foo.new
|
||||
head :ok
|
||||
end
|
||||
|
||||
def get_session_value
|
||||
render :text => "foo: #{session[:foo].inspect}"
|
||||
end
|
||||
|
||||
def get_session_id
|
||||
session[:foo]
|
||||
render :text => "#{request.session_options[:id]}"
|
||||
end
|
||||
|
||||
@@ -33,13 +37,6 @@ class MemCacheStoreTest < ActionController::IntegrationTest
|
||||
|
||||
begin
|
||||
DispatcherApp = ActionController::Dispatcher.new
|
||||
MemCacheStoreApp = ActionController::Session::MemCacheStore.new(
|
||||
DispatcherApp, :key => '_session_id')
|
||||
|
||||
|
||||
def setup
|
||||
@integration_session = open_session(MemCacheStoreApp)
|
||||
end
|
||||
|
||||
def test_setting_and_getting_session_value
|
||||
with_test_route_set do
|
||||
@@ -82,6 +79,34 @@ class MemCacheStoreTest < ActionController::IntegrationTest
|
||||
end
|
||||
end
|
||||
|
||||
def test_getting_session_value_after_session_reset
|
||||
with_test_route_set do
|
||||
get '/set_session_value'
|
||||
assert_response :success
|
||||
assert cookies['_session_id']
|
||||
session_id = cookies["_session_id"]
|
||||
|
||||
get '/call_reset_session'
|
||||
assert_response :success
|
||||
assert_not_equal [], headers['Set-Cookie']
|
||||
|
||||
cookies["_session_id"] = session_id # replace our new session_id with our old, pre-reset session_id
|
||||
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from memcached"
|
||||
end
|
||||
end
|
||||
|
||||
def test_getting_from_nonexistent_session
|
||||
with_test_route_set do
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: nil', response.body
|
||||
assert_nil cookies['_session_id'], "should only create session on write, not read"
|
||||
end
|
||||
end
|
||||
|
||||
def test_getting_session_id
|
||||
with_test_route_set do
|
||||
get '/set_session_value'
|
||||
@@ -91,7 +116,38 @@ class MemCacheStoreTest < ActionController::IntegrationTest
|
||||
|
||||
get '/get_session_id'
|
||||
assert_response :success
|
||||
assert_equal session_id, response.body
|
||||
assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
|
||||
end
|
||||
end
|
||||
|
||||
def test_doesnt_write_session_cookie_if_session_id_is_already_exists
|
||||
with_test_route_set do
|
||||
get '/set_session_value'
|
||||
assert_response :success
|
||||
assert cookies['_session_id']
|
||||
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
|
||||
end
|
||||
end
|
||||
|
||||
def test_deserializes_unloaded_class
|
||||
with_test_route_set do
|
||||
with_autoload_path "session_autoload_test" do
|
||||
get '/set_serialized_session_value'
|
||||
assert_response :success
|
||||
assert cookies['_session_id']
|
||||
end
|
||||
with_autoload_path "session_autoload_test" do
|
||||
get '/get_session_id'
|
||||
assert_response :success
|
||||
end
|
||||
with_autoload_path "session_autoload_test" do
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -114,14 +170,18 @@ class MemCacheStoreTest < ActionController::IntegrationTest
|
||||
end
|
||||
|
||||
private
|
||||
def with_test_route_set
|
||||
def with_test_route_set(options = {})
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.with_options :controller => "mem_cache_store_test/test" do |c|
|
||||
c.connect "/:action"
|
||||
end
|
||||
end
|
||||
|
||||
options = { :key => '_session_id' }.merge!(options)
|
||||
@integration_session = open_session(ActionController::Session::MemCacheStore.new(DispatcherApp, options))
|
||||
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -134,9 +134,15 @@ class UrlWriterTests < ActionController::TestCase
|
||||
)
|
||||
end
|
||||
|
||||
def test_anchor_should_be_cgi_escaped
|
||||
assert_equal('/c/a#anc%2Fhor',
|
||||
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anc/hor'))
|
||||
def test_anchor_should_escape_unsafe_pchar
|
||||
assert_equal('/c/a#%23anchor',
|
||||
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('#anchor'))
|
||||
)
|
||||
end
|
||||
|
||||
def test_anchor_should_not_escape_safe_pchar
|
||||
assert_equal('/c/a#name=user&email=user@domain.com',
|
||||
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('name=user&email=user@domain.com'))
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
10
actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb
vendored
Normal file
10
actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
module SessionAutoloadTest
|
||||
class Foo
|
||||
def initialize(bar='baz')
|
||||
@bar = bar
|
||||
end
|
||||
def inspect
|
||||
"#<#{self.class} bar:#{@bar.inspect}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -280,6 +280,10 @@ class FormOptionsHelperTest < ActionView::TestCase
|
||||
opts
|
||||
end
|
||||
|
||||
def test_time_zone_options_returns_html_safe_string
|
||||
assert time_zone_options_for_select.html_safe?
|
||||
end
|
||||
|
||||
def test_select
|
||||
@post = Post.new
|
||||
@post.category = "<mus>"
|
||||
|
||||
@@ -296,6 +296,7 @@ class TextHelperTest < ActionView::TestCase
|
||||
assert_equal %(<p>Link #{link_result_with_options}</p>), auto_link("<p>Link #{link_raw}</p>", :all, {:target => "_blank"})
|
||||
assert_equal %(Go to #{link_result}.), auto_link(%(Go to #{link_raw}.))
|
||||
assert_equal %(<p>Go to #{link_result}, then say hello to #{email_result}.</p>), auto_link(%(<p>Go to #{link_raw}, then say hello to #{email_raw}.</p>))
|
||||
assert_equal %(#{link_result} #{link_result}), auto_link(%(#{link_result} #{link_raw}))
|
||||
|
||||
email2_raw = '+david@loudthinking.com'
|
||||
email2_result = %{<a href="mailto:#{email2_raw}">#{email2_raw}</a>}
|
||||
@@ -368,24 +369,38 @@ class TextHelperTest < ActionView::TestCase
|
||||
end
|
||||
|
||||
def test_auto_link_other_protocols
|
||||
silence_warnings do
|
||||
begin
|
||||
old_re_value = ActionView::Helpers::TextHelper::AUTO_LINK_RE
|
||||
ActionView::Helpers::TextHelper.const_set :AUTO_LINK_RE, %r{(ftp://)[^\s<]+}
|
||||
link_raw = 'ftp://example.com/file.txt'
|
||||
link_result = generate_result(link_raw)
|
||||
assert_equal %(Download #{link_result}), auto_link("Download #{link_raw}")
|
||||
ensure
|
||||
ActionView::Helpers::TextHelper.const_set :AUTO_LINK_RE, old_re_value
|
||||
end
|
||||
end
|
||||
ftp_raw = 'ftp://example.com/file.txt'
|
||||
assert_equal %(Download #{generate_result(ftp_raw)}), auto_link("Download #{ftp_raw}")
|
||||
|
||||
file_scheme = 'file:///home/username/RomeoAndJuliet.pdf'
|
||||
z39_scheme = 'z39.50r://host:696/db'
|
||||
chrome_scheme = 'chrome://package/section/path'
|
||||
view_source = 'view-source:http://en.wikipedia.org/wiki/URI_scheme'
|
||||
assert_equal generate_result(z39_scheme), auto_link(z39_scheme)
|
||||
assert_equal generate_result(chrome_scheme), auto_link(chrome_scheme)
|
||||
assert_equal generate_result(view_source), auto_link(view_source)
|
||||
end
|
||||
|
||||
def test_auto_link_already_linked
|
||||
linked1 = generate_result('Ruby On Rails', 'http://www.rubyonrails.com')
|
||||
linked2 = generate_result('www.rubyonrails.com', 'http://www.rubyonrails.com')
|
||||
linked2 = %('<a href="http://www.example.com">www.example.com</a>')
|
||||
linked3 = %('<a href="http://www.example.com" rel="nofollow">www.example.com</a>')
|
||||
linked4 = %('<a href="http://www.example.com"><b>www.example.com</b></a>')
|
||||
linked5 = %('<a href="#close">close</a> <a href="http://www.example.com"><b>www.example.com</b></a>')
|
||||
assert_equal linked1, auto_link(linked1)
|
||||
assert_equal linked2, auto_link(linked2)
|
||||
assert_equal linked3, auto_link(linked3)
|
||||
assert_equal linked4, auto_link(linked4)
|
||||
assert_equal linked5, auto_link(linked5)
|
||||
|
||||
linked_email = %Q(<a href="mailto:david@loudthinking.com">Mail me</a>)
|
||||
assert_equal linked_email, auto_link(linked_email)
|
||||
end
|
||||
|
||||
def test_auto_link_within_tags
|
||||
link_raw = 'http://www.rubyonrails.org/images/rails.png'
|
||||
link_result = %Q(<img src="#{link_raw}" />)
|
||||
assert_equal link_result, auto_link(link_result)
|
||||
end
|
||||
|
||||
def test_auto_link_with_brackets
|
||||
@@ -405,12 +420,6 @@ class TextHelperTest < ActionView::TestCase
|
||||
assert_equal "{link: #{link3_result}}", auto_link("{link: #{link3_raw}}")
|
||||
end
|
||||
|
||||
def test_auto_link_in_tags
|
||||
link_raw = 'http://www.rubyonrails.org/images/rails.png'
|
||||
link_result = %Q(<img src="#{link_raw}" />)
|
||||
assert_equal link_result, auto_link(link_result)
|
||||
end
|
||||
|
||||
def test_auto_link_at_eol
|
||||
url1 = "http://api.rubyonrails.com/Foo.html"
|
||||
url2 = "http://www.ruby-doc.org/core/Bar.html"
|
||||
@@ -424,12 +433,32 @@ class TextHelperTest < ActionView::TestCase
|
||||
|
||||
assert_equal %(<p><a href="#{url}">#{url[0...7]}...</a><br /><a href="mailto:#{email}">#{email[0...7]}...</a><br /></p>), auto_link("<p>#{url}<br />#{email}<br /></p>") { |url| truncate(url, :length => 10) }
|
||||
end
|
||||
|
||||
def test_auto_link_with_block_with_html
|
||||
pic = "http://example.com/pic.png"
|
||||
url = "http://example.com/album?a&b=c"
|
||||
|
||||
assert_equal %(My pic: <a href="#{pic}"><img src="#{pic}" width="160px"></a> -- full album here #{generate_result(url)}), auto_link("My pic: #{pic} -- full album here #{url}") { |link|
|
||||
if link =~ /\.(jpg|gif|png|bmp|tif)$/i
|
||||
raw %(<img src="#{link}" width="160px">)
|
||||
else
|
||||
link
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def test_auto_link_with_options_hash
|
||||
assert_dom_equal 'Welcome to my new blog at <a href="http://www.myblog.com/" class="menu" target="_blank">http://www.myblog.com/</a>. Please e-mail me at <a href="mailto:me@email.com" class="menu" target="_blank">me@email.com</a>.',
|
||||
auto_link("Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.",
|
||||
:link => :all, :html => { :class => "menu", :target => "_blank" })
|
||||
end
|
||||
|
||||
def test_auto_link_with_multiple_trailing_punctuations
|
||||
url = "http://youtube.com"
|
||||
url_result = generate_result(url)
|
||||
assert_equal url_result, auto_link(url)
|
||||
assert_equal "(link: #{url_result}).", auto_link("(link: #{url}).")
|
||||
end
|
||||
|
||||
def test_cycle_class
|
||||
value = Cycle.new("one", 2, "3")
|
||||
|
||||
@@ -333,11 +333,11 @@ class UrlHelperTest < ActionView::TestCase
|
||||
end
|
||||
|
||||
def test_mail_to_with_javascript
|
||||
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript")
|
||||
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript")
|
||||
end
|
||||
|
||||
def test_mail_to_with_javascript_unicode
|
||||
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%22%3e%c3%ba%6e%69%63%6f%64%65%3c%2f%61%3e%27%29%3b'))</script>", mail_to("unicode@example.com", "únicode", :encode => "javascript")
|
||||
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%5c%22%3e%c3%ba%6e%69%63%6f%64%65%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("unicode@example.com", "únicode", :encode => "javascript")
|
||||
end
|
||||
|
||||
def test_mail_with_options
|
||||
@@ -361,8 +361,8 @@ class UrlHelperTest < ActionView::TestCase
|
||||
assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me(at)domain.com</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)")
|
||||
assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)")
|
||||
assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me(at)domain(dot)com</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)")
|
||||
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
|
||||
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
|
||||
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
|
||||
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
|
||||
end
|
||||
|
||||
def protect_against_forgery?
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env ruby
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
require 'rdoc/task'
|
||||
|
||||
task :default => :test
|
||||
|
||||
@@ -13,7 +13,7 @@ Rake::TestTask.new do |t|
|
||||
end
|
||||
|
||||
# Generate the RDoc documentation
|
||||
Rake::RDocTask.new do |rdoc|
|
||||
RDoc::Task.new do |rdoc|
|
||||
rdoc.rdoc_dir = 'doc'
|
||||
rdoc.title = "Active Model"
|
||||
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
*2.3.11 (February 9, 2011)*
|
||||
|
||||
*2.3.10 (October 15, 2010)*
|
||||
|
||||
* Security Release to fix CVE-2010-3933
|
||||
|
||||
*2.3.9 (September 4, 2010)*
|
||||
*2.3.8 (May 24, 2010)*
|
||||
|
||||
* Version bump.
|
||||
|
||||
|
||||
*2.3.7 (May 24, 2010)*
|
||||
|
||||
* Version bump.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
require 'rubygems'
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
require 'rdoc/task'
|
||||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rubygems/package_task'
|
||||
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'active_record', 'version')
|
||||
require File.expand_path(File.dirname(__FILE__)) + "/test/config"
|
||||
@@ -157,7 +157,7 @@ task :rebuild_frontbase_databases => 'frontbase:rebuild_databases'
|
||||
|
||||
# Generate the RDoc documentation
|
||||
|
||||
Rake::RDocTask.new { |rdoc|
|
||||
RDoc::Task.new { |rdoc|
|
||||
rdoc.rdoc_dir = 'doc'
|
||||
rdoc.title = "Active Record -- Object-relation mapping put on rails"
|
||||
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
||||
@@ -192,16 +192,14 @@ spec = Gem::Specification.new do |s|
|
||||
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
||||
end
|
||||
|
||||
s.add_dependency('activesupport', '= 2.3.8' + PKG_BUILD)
|
||||
s.add_dependency('activesupport', '= 2.3.14' + PKG_BUILD)
|
||||
|
||||
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
|
||||
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
|
||||
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite3"
|
||||
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite3"
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'active_record'
|
||||
|
||||
s.has_rdoc = true
|
||||
s.extra_rdoc_files = %w( README )
|
||||
s.rdoc_options.concat ['--main', 'README']
|
||||
|
||||
@@ -211,7 +209,7 @@ spec = Gem::Specification.new do |s|
|
||||
s.rubyforge_project = "activerecord"
|
||||
end
|
||||
|
||||
Rake::GemPackageTask.new(spec) do |p|
|
||||
Gem::PackageTask.new(spec) do |p|
|
||||
p.gem_spec = spec
|
||||
p.need_tar = true
|
||||
p.need_zip = true
|
||||
|
||||
@@ -12,6 +12,8 @@ require 'rbench'
|
||||
|
||||
__DIR__ = File.dirname(__FILE__)
|
||||
$:.unshift "#{__DIR__}/../lib"
|
||||
$:.unshift "#{__DIR__}/../../activesupport/lib"
|
||||
|
||||
require 'active_record'
|
||||
|
||||
conn = { :adapter => 'mysql',
|
||||
@@ -25,7 +27,7 @@ conn[:socket] = Pathname.glob(%w[
|
||||
/tmp/mysql.sock
|
||||
/var/mysql/mysql.sock
|
||||
/var/run/mysqld/mysqld.sock
|
||||
]).find { |path| path.socket? }
|
||||
]).find { |path| path.socket? }.to_s
|
||||
|
||||
ActiveRecord::Base.establish_connection(conn)
|
||||
|
||||
@@ -58,7 +60,7 @@ end
|
||||
sqlfile = "#{__DIR__}/performance.sql"
|
||||
|
||||
if File.exists?(sqlfile)
|
||||
mysql_bin = %w[mysql mysql5].select { |bin| `which #{bin}`.length > 0 }
|
||||
mysql_bin = %w[mysql mysql5].detect { |bin| `which #{bin}`.length > 0 }
|
||||
`#{mysql_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} < #{sqlfile}`
|
||||
else
|
||||
puts 'Generating data...'
|
||||
@@ -88,7 +90,7 @@ else
|
||||
)
|
||||
end
|
||||
|
||||
mysqldump_bin = %w[mysqldump mysqldump5].select { |bin| `which #{bin}`.length > 0 }
|
||||
mysqldump_bin = %w[mysqldump mysqldump5].detect { |bin| `which #{bin}`.length > 0 }
|
||||
`#{mysqldump_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} exhibits users > #{sqlfile}`
|
||||
end
|
||||
|
||||
@@ -155,6 +157,40 @@ RBench.run(TIMES) do
|
||||
ar { Exhibit.transaction { Exhibit.new } }
|
||||
end
|
||||
|
||||
report 'Model.find(id)' do
|
||||
id = Exhibit.first.id
|
||||
ar { Exhibit.find(id) }
|
||||
end
|
||||
|
||||
report 'Model.find_by_sql' do
|
||||
ar { Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first }
|
||||
end
|
||||
|
||||
report 'Model.log', (TIMES * 10) do
|
||||
ar { Exhibit.connection.send(:log, "hello", "world") {} }
|
||||
end
|
||||
|
||||
report 'AR.execute(query)', (TIMES / 2) do
|
||||
ar { ActiveRecord::Base.connection.execute("Select * from exhibits where id = #{(rand * 1000 + 1).to_i}") }
|
||||
end
|
||||
|
||||
report 'Model.find(id)' do
|
||||
id = Exhibit.first.id
|
||||
ar { Exhibit.find(id) }
|
||||
end
|
||||
|
||||
report 'Model.find_by_sql' do
|
||||
ar { Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first }
|
||||
end
|
||||
|
||||
report 'Model.log', (TIMES * 10) do
|
||||
ar { Exhibit.connection.send(:log, "hello", "world") {} }
|
||||
end
|
||||
|
||||
report 'AR.execute(query)', (TIMES / 2) do
|
||||
ar { ActiveRecord::Base.connection.execute("Select * from exhibits where id = #{(rand * 1000 + 1).to_i}") }
|
||||
end
|
||||
|
||||
summary 'Total'
|
||||
end
|
||||
|
||||
|
||||
@@ -282,7 +282,11 @@ module ActiveRecord
|
||||
end
|
||||
through_records.flatten!
|
||||
else
|
||||
records.first.class.preload_associations(records, through_association)
|
||||
options = {}
|
||||
options[:include] = reflection.options[:include] || reflection.options[:source] if reflection.options[:conditions] || reflection.options[:order]
|
||||
options[:order] = reflection.options[:order]
|
||||
options[:conditions] = reflection.options[:conditions]
|
||||
records.first.class.preload_associations(records, through_association, options)
|
||||
through_records = records.map {|record| record.send(through_association)}.flatten
|
||||
end
|
||||
through_records.compact!
|
||||
@@ -357,7 +361,13 @@ module ActiveRecord
|
||||
table_name = reflection.klass.quoted_table_name
|
||||
|
||||
if interface = reflection.options[:as]
|
||||
conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'"
|
||||
parent_type = if reflection.active_record.abstract_class?
|
||||
self.base_class.sti_name
|
||||
else
|
||||
reflection.active_record.sti_name
|
||||
end
|
||||
|
||||
conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{parent_type}'"
|
||||
else
|
||||
foreign_key = reflection.primary_key_name
|
||||
conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} #{in_or_equals_for_ids(ids)}"
|
||||
|
||||
@@ -1782,7 +1782,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def using_limitable_reflections?(reflections)
|
||||
reflections.collect(&:collection?).length.zero?
|
||||
reflections.none?(&:collection?)
|
||||
end
|
||||
|
||||
def column_aliases(join_dependency)
|
||||
|
||||
@@ -234,8 +234,9 @@ module ActiveRecord
|
||||
# See destroy for more info.
|
||||
def destroy_all
|
||||
load_target
|
||||
destroy(@target)
|
||||
reset_target!
|
||||
destroy(@target).tap do
|
||||
reset_target!
|
||||
end
|
||||
end
|
||||
|
||||
def create(attrs = {})
|
||||
@@ -331,6 +332,7 @@ module ActiveRecord
|
||||
|
||||
def include?(record)
|
||||
return false unless record.is_a?(@reflection.klass)
|
||||
return include_in_memory?(record) if record.new_record?
|
||||
load_target if @reflection.options[:finder_sql] && !loaded?
|
||||
return @target.include?(record) if loaded?
|
||||
exists?(record)
|
||||
@@ -349,7 +351,16 @@ module ActiveRecord
|
||||
begin
|
||||
if !loaded?
|
||||
if @target.is_a?(Array) && @target.any?
|
||||
@target = find_target + @target.find_all {|t| t.new_record? }
|
||||
@target = find_target.map do |f|
|
||||
i = @target.index(f)
|
||||
t = @target.delete_at(i) if i
|
||||
if t && t.changed?
|
||||
t
|
||||
else
|
||||
f.mark_for_destruction if t && t.marked_for_destruction?
|
||||
f
|
||||
end
|
||||
end + @target.find_all {|t| t.new_record?}
|
||||
else
|
||||
@target = find_target
|
||||
end
|
||||
@@ -363,7 +374,19 @@ module ActiveRecord
|
||||
target
|
||||
end
|
||||
|
||||
def method_missing(method, *args)
|
||||
def method_missing(method, *args, &block)
|
||||
case method.to_s
|
||||
when 'find_or_create'
|
||||
return find(:first, :conditions => args.first) || create(args.first)
|
||||
when /^find_or_create_by_(.*)$/
|
||||
rest = $1
|
||||
find_args = pull_finder_args_from(DynamicFinderMatch.match(method).attribute_names, *args)
|
||||
return send("find_by_#{rest}", *find_args) ||
|
||||
method_missing("create_by_#{rest}", *args, &block)
|
||||
when /^create_by_(.*)$/
|
||||
return create($1.split('_and_').zip(args).inject({}) { |h,kv| k,v=kv ; h[k] = v ; h }, &block)
|
||||
end
|
||||
|
||||
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
||||
if block_given?
|
||||
super { |*block_args| yield(*block_args) }
|
||||
@@ -418,6 +441,25 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
private
|
||||
# Separate the "finder" args from the "create" args given to a
|
||||
# find_or_create_by_ call. Returns an array with the
|
||||
# parameter values in the same order as the keys in the
|
||||
# "names" array. This code was based on code in base.rb's
|
||||
# method_missing method.
|
||||
def pull_finder_args_from(names, *args)
|
||||
attributes = names.collect { |name| name.intern }
|
||||
attribute_hash = {}
|
||||
args.each_with_index do |arg, i|
|
||||
if arg.is_a?(Hash)
|
||||
attribute_hash.merge! arg
|
||||
else
|
||||
attribute_hash[attributes[i]] = arg
|
||||
end
|
||||
end
|
||||
attribute_hash = attribute_hash.with_indifferent_access
|
||||
attributes.collect { |attr| attribute_hash[attr] }
|
||||
end
|
||||
|
||||
def create_record(attrs)
|
||||
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
|
||||
ensure_owner_is_not_new
|
||||
@@ -462,8 +504,8 @@ module ActiveRecord
|
||||
def callbacks_for(callback_name)
|
||||
full_callback_name = "#{callback_name}_for_#{@reflection.name}"
|
||||
@owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def ensure_owner_is_not_new
|
||||
if @owner.new_record?
|
||||
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
|
||||
@@ -474,6 +516,18 @@ module ActiveRecord
|
||||
args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] ||
|
||||
@target.any? { |record| record.new_record? } || args.first.kind_of?(Integer))
|
||||
end
|
||||
|
||||
def include_in_memory?(record)
|
||||
if @reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
|
||||
@owner.send(proxy_reflection.through_reflection.name.to_sym).each do |source|
|
||||
source_reflection_target = source.send(proxy_reflection.source_reflection.name)
|
||||
return true if source_reflection_target.respond_to?(:include?) ? source_reflection_target.include?(record) : source_reflection_target == record
|
||||
end
|
||||
false
|
||||
else
|
||||
@target.include?(record)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -230,10 +230,6 @@ module ActiveRecord
|
||||
# It's also possible to instantiate related objects, so a Client class belonging to the clients
|
||||
# table with a +master_id+ foreign key can instantiate master through Client#master.
|
||||
def method_missing(method_id, *args, &block)
|
||||
if method_id == :to_ary || method_id == :to_str
|
||||
raise NoMethodError, "undefined method `#{method_id}' for #{inspect}:#{self.class}"
|
||||
end
|
||||
|
||||
method_name = method_id.to_s
|
||||
|
||||
if self.class.private_method_defined?(method_name)
|
||||
|
||||
@@ -146,12 +146,12 @@ module ActiveRecord
|
||||
# add_autosave_association_callbacks(reflect_on_association(name))
|
||||
# end
|
||||
ASSOCIATION_TYPES.each do |type|
|
||||
module_eval %{
|
||||
module_eval <<-CODE, __FILE__, __LINE__ + 1
|
||||
def #{type}(name, options = {})
|
||||
super
|
||||
add_autosave_association_callbacks(reflect_on_association(name))
|
||||
end
|
||||
}
|
||||
CODE
|
||||
end
|
||||
|
||||
# Adds a validate and save callback for the association as specified by
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
require 'yaml'
|
||||
require 'set'
|
||||
require 'active_support/core_ext/class/attribute'
|
||||
|
||||
module ActiveRecord #:nodoc:
|
||||
# Generic Active Record exception class.
|
||||
@@ -515,7 +516,7 @@ module ActiveRecord #:nodoc:
|
||||
@@timestamped_migrations = true
|
||||
|
||||
# Determine whether to store the full constant name including namespace when using STI
|
||||
superclass_delegating_accessor :store_full_sti_class
|
||||
class_attribute :store_full_sti_class
|
||||
self.store_full_sti_class = false
|
||||
|
||||
# Stores the default scope for the class
|
||||
@@ -935,11 +936,18 @@ module ActiveRecord #:nodoc:
|
||||
def reset_counters(id, *counters)
|
||||
object = find(id)
|
||||
counters.each do |association|
|
||||
child_class = reflect_on_association(association).klass
|
||||
counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column
|
||||
child_class = reflect_on_association(association.to_sym).klass
|
||||
belongs_name = self.name.demodulize.underscore.to_sym
|
||||
counter_name = child_class.reflect_on_association(belongs_name).counter_cache_column
|
||||
value = object.send(association).count
|
||||
|
||||
connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE")
|
||||
connection.update(<<-CMD, "#{name} UPDATE")
|
||||
UPDATE #{quoted_table_name}
|
||||
SET #{connection.quote_column_name(counter_name)} = #{value}
|
||||
WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}
|
||||
CMD
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
# A generic "counter updater" implementation, intended primarily to be
|
||||
@@ -972,19 +980,13 @@ module ActiveRecord #:nodoc:
|
||||
# # SET comment_count = comment_count + 1,
|
||||
# # WHERE id IN (10, 15)
|
||||
def update_counters(id, counters)
|
||||
updates = counters.inject([]) { |list, (counter_name, increment)|
|
||||
sign = increment < 0 ? "-" : "+"
|
||||
list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
|
||||
}.join(", ")
|
||||
|
||||
if id.is_a?(Array)
|
||||
ids_list = id.map {|i| quote_value(i)}.join(', ')
|
||||
condition = "IN (#{ids_list})"
|
||||
else
|
||||
condition = "= #{quote_value(id)}"
|
||||
updates = counters.map do |counter_name, value|
|
||||
operator = value < 0 ? '-' : '+'
|
||||
quoted_column = connection.quote_column_name(counter_name)
|
||||
"#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
|
||||
end
|
||||
|
||||
update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}")
|
||||
update_all(updates.join(', '), primary_key => id )
|
||||
end
|
||||
|
||||
# Increment a number field by one, usually representing a count.
|
||||
@@ -1284,6 +1286,8 @@ module ActiveRecord #:nodoc:
|
||||
|
||||
# Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
|
||||
def class_name(table_name = table_name) # :nodoc:
|
||||
ActiveSupport::Deprecation.warn("ActiveRecord::Base#class_name is deprecated and will be removed in Rails 3.", caller)
|
||||
|
||||
# remove any prefix and/or suffix from the table name
|
||||
class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
|
||||
class_name = class_name.singularize if pluralize_table_names
|
||||
@@ -2642,7 +2646,7 @@ module ActiveRecord #:nodoc:
|
||||
# Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
|
||||
# instance will affect the other.
|
||||
def becomes(klass)
|
||||
returning klass.new do |became|
|
||||
klass.new.tap do |became|
|
||||
became.instance_variable_set("@attributes", @attributes)
|
||||
became.instance_variable_set("@attributes_cache", @attributes_cache)
|
||||
became.instance_variable_set("@new_record", new_record?)
|
||||
@@ -2660,12 +2664,20 @@ module ActiveRecord #:nodoc:
|
||||
# Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
|
||||
# fail and false will be returned.
|
||||
def update_attributes(attributes)
|
||||
with_transaction_returning_status(:update_attributes_inside_transaction, attributes)
|
||||
end
|
||||
|
||||
def update_attributes_inside_transaction(attributes) #:nodoc:
|
||||
self.attributes = attributes
|
||||
save
|
||||
end
|
||||
|
||||
# Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
|
||||
def update_attributes!(attributes)
|
||||
with_transaction_returning_status(:update_attributes_inside_transaction!, attributes)
|
||||
end
|
||||
|
||||
def update_attributes_inside_transaction!(attributes) #:nodoc:
|
||||
self.attributes = attributes
|
||||
save!
|
||||
end
|
||||
|
||||
@@ -187,7 +187,7 @@ module ActiveRecord
|
||||
# A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
|
||||
sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround
|
||||
|
||||
sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
|
||||
options[:group_fields].each_index{|i| sql << ", #{options[:group_fields][i]} AS #{options[:group_aliases][i]}" } if options[:group]
|
||||
if options[:from]
|
||||
sql << " FROM #{options[:from]} "
|
||||
elsif scope && scope[:from] && !use_workaround
|
||||
@@ -211,8 +211,8 @@ module ActiveRecord
|
||||
add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
||||
|
||||
if options[:group]
|
||||
group_key = connection.adapter_name == 'FrontBase' ? :group_alias : :group_field
|
||||
sql << " GROUP BY #{options[group_key]} "
|
||||
group_key = connection.adapter_name == 'FrontBase' ? :group_aliases : :group_fields
|
||||
sql << " GROUP BY #{options[group_key].join(',')} "
|
||||
end
|
||||
|
||||
if options[:group] && options[:having]
|
||||
@@ -239,24 +239,31 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def execute_grouped_calculation(operation, column_name, column, options) #:nodoc:
|
||||
group_attr = options[:group].to_s
|
||||
association = reflect_on_association(group_attr.to_sym)
|
||||
associated = association && association.macro == :belongs_to # only count belongs_to associations
|
||||
group_field = associated ? association.primary_key_name : group_attr
|
||||
group_alias = column_alias_for(group_field)
|
||||
group_column = column_for group_field
|
||||
sql = construct_calculation_sql(operation, column_name, options.merge(:group_field => group_field, :group_alias => group_alias))
|
||||
group_attr = options[:group]
|
||||
association = reflect_on_association(group_attr.to_s.to_sym)
|
||||
associated = association && association.macro == :belongs_to # only count belongs_to associations
|
||||
group_fields = Array(associated ? association.primary_key_name : group_attr)
|
||||
group_aliases = []
|
||||
group_columns = {}
|
||||
|
||||
group_fields.each do |field|
|
||||
group_aliases << column_alias_for(field)
|
||||
group_columns[column_alias_for(field)] = column_for(field)
|
||||
end
|
||||
|
||||
sql = construct_calculation_sql(operation, column_name, options.merge(:group_fields => group_fields, :group_aliases => group_aliases))
|
||||
calculated_data = connection.select_all(sql)
|
||||
aggregate_alias = column_alias_for(operation, column_name)
|
||||
|
||||
if association
|
||||
key_ids = calculated_data.collect { |row| row[group_alias] }
|
||||
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
||||
key_records = association.klass.base_class.find(key_ids)
|
||||
key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
|
||||
end
|
||||
|
||||
calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
|
||||
key = type_cast_calculated_value(row[group_alias], group_column)
|
||||
key = group_aliases.map{|group_alias| type_cast_calculated_value(row[group_alias], group_columns[group_alias])}
|
||||
key = key.first if key.size == 1
|
||||
key = key_records[key] if associated
|
||||
value = row[aggregate_alias]
|
||||
all[key] = type_cast_calculated_value(value, column, operation)
|
||||
|
||||
@@ -10,7 +10,7 @@ module ActiveRecord
|
||||
##
|
||||
# :singleton-method:
|
||||
# The connection handler
|
||||
superclass_delegating_accessor :connection_handler
|
||||
class_attribute :connection_handler
|
||||
self.connection_handler = ConnectionAdapters::ConnectionHandler.new
|
||||
|
||||
# Returns the connection currently associated with the class. This can
|
||||
|
||||
@@ -195,6 +195,7 @@ module ActiveRecord
|
||||
# remove_column(:suppliers, :qualification)
|
||||
# remove_columns(:suppliers, :qualification, :experience)
|
||||
def remove_column(table_name, *column_names)
|
||||
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
|
||||
column_names.flatten.each do |column_name|
|
||||
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
|
||||
end
|
||||
@@ -273,7 +274,7 @@ module ActiveRecord
|
||||
|
||||
if Hash === options # legacy support, since this param was a string
|
||||
index_type = options[:unique] ? "UNIQUE" : ""
|
||||
index_name = options[:name] || index_name
|
||||
index_name = options[:name].to_s if options[:name]
|
||||
else
|
||||
index_type = options
|
||||
end
|
||||
@@ -346,6 +347,7 @@ module ActiveRecord
|
||||
# as there's no way to determine the correct answer in that case.
|
||||
def index_exists?(table_name, index_name, default)
|
||||
return default unless respond_to?(:indexes)
|
||||
index_name = index_name.to_s
|
||||
indexes(table_name).detect { |i| i.name == index_name }
|
||||
end
|
||||
|
||||
|
||||
@@ -211,6 +211,12 @@ module ActiveRecord
|
||||
log_info(sql, name, 0)
|
||||
nil
|
||||
end
|
||||
rescue SystemExit, SignalException, NoMemoryError => e
|
||||
# Don't re-wrap these exceptions. They are probably not being caused by invalid
|
||||
# sql, but rather some external stimulus beyond the responsibilty of this code.
|
||||
# Additionaly, wrapping these exceptions with StatementInvalid would lead to
|
||||
# meaningful loss of data, such as losing SystemExit#status.
|
||||
raise e
|
||||
rescue Exception => e
|
||||
# Log message and raise exception.
|
||||
# Set last_verification to 0, so that connection gets verified
|
||||
|
||||
@@ -238,7 +238,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def quote_column_name(name) #:nodoc:
|
||||
@quoted_column_names[name] ||= "`#{name}`"
|
||||
@quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
|
||||
end
|
||||
|
||||
def quote_table_name(name) #:nodoc:
|
||||
@@ -315,6 +315,7 @@ module ActiveRecord
|
||||
rows = []
|
||||
result.each { |row| rows << row }
|
||||
result.free
|
||||
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
|
||||
rows
|
||||
end
|
||||
|
||||
@@ -638,6 +639,7 @@ module ActiveRecord
|
||||
result = execute(sql, name)
|
||||
rows = result.all_hashes
|
||||
result.free
|
||||
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
|
||||
rows
|
||||
end
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ module ActiveRecord
|
||||
module ConnectionAdapters #:nodoc:
|
||||
class SQLite3Adapter < SQLiteAdapter # :nodoc:
|
||||
def table_structure(table_name)
|
||||
returning structure = @connection.table_info(quote_table_name(table_name)) do
|
||||
@connection.table_info(quote_table_name(table_name)).tap do |structure|
|
||||
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -162,7 +162,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def quote_column_name(name) #:nodoc:
|
||||
%Q("#{name}")
|
||||
%Q("#{name.to_s.gsub('"', '""')}")
|
||||
end
|
||||
|
||||
|
||||
@@ -269,6 +269,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def remove_column(table_name, *column_names) #:nodoc:
|
||||
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
|
||||
column_names.flatten.each do |column_name|
|
||||
alter_table(table_name) do |definition|
|
||||
definition.columns.delete(definition[column_name])
|
||||
@@ -329,7 +330,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def table_structure(table_name)
|
||||
returning structure = execute("PRAGMA table_info(#{quote_table_name(table_name)})") do
|
||||
execute("PRAGMA table_info(#{quote_table_name(table_name)})").tap do |structure|
|
||||
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -44,7 +44,7 @@ module ActiveRecord
|
||||
base.alias_method_chain :update, :dirty
|
||||
base.alias_method_chain :reload, :dirty
|
||||
|
||||
base.superclass_delegating_accessor :partial_updates
|
||||
base.class_attribute :partial_updates
|
||||
base.partial_updates = true
|
||||
|
||||
base.send(:extend, ClassMethods)
|
||||
|
||||
@@ -11,23 +11,23 @@ en:
|
||||
accepted: "must be accepted"
|
||||
empty: "can't be empty"
|
||||
blank: "can't be blank"
|
||||
too_long: "is too long (maximum is {{count}} characters)"
|
||||
too_short: "is too short (minimum is {{count}} characters)"
|
||||
wrong_length: "is the wrong length (should be {{count}} characters)"
|
||||
too_long: "is too long (maximum is %{count} characters)"
|
||||
too_short: "is too short (minimum is %{count} characters)"
|
||||
wrong_length: "is the wrong length (should be %{count} characters)"
|
||||
taken: "has already been taken"
|
||||
not_a_number: "is not a number"
|
||||
greater_than: "must be greater than {{count}}"
|
||||
greater_than_or_equal_to: "must be greater than or equal to {{count}}"
|
||||
equal_to: "must be equal to {{count}}"
|
||||
less_than: "must be less than {{count}}"
|
||||
less_than_or_equal_to: "must be less than or equal to {{count}}"
|
||||
greater_than: "must be greater than %{count}"
|
||||
greater_than_or_equal_to: "must be greater than or equal to %{count}"
|
||||
equal_to: "must be equal to %{count}"
|
||||
less_than: "must be less than %{count}"
|
||||
less_than_or_equal_to: "must be less than or equal to %{count}"
|
||||
odd: "must be odd"
|
||||
even: "must be even"
|
||||
record_invalid: "Validation failed: {{errors}}"
|
||||
record_invalid: "Validation failed: %{errors}"
|
||||
# Append your own errors here or at the model/attributes scope.
|
||||
|
||||
full_messages:
|
||||
format: "{{attribute}} {{message}}"
|
||||
format: "%{attribute} %{message}"
|
||||
|
||||
# You can define own errors for models or model attributes.
|
||||
# The values :model, :attribute and :value are always available for interpolation.
|
||||
@@ -35,7 +35,7 @@ en:
|
||||
# For example,
|
||||
# models:
|
||||
# user:
|
||||
# blank: "This is a custom blank message for {{model}}: {{attribute}}"
|
||||
# blank: "This is a custom blank message for %{model}: %{attribute}"
|
||||
# attributes:
|
||||
# login:
|
||||
# blank: "This is a custom blank message for User login"
|
||||
|
||||
@@ -128,6 +128,7 @@ module ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
@destroyed = true
|
||||
freeze
|
||||
end
|
||||
|
||||
|
||||
@@ -516,7 +516,7 @@ module ActiveRecord
|
||||
raise DuplicateMigrationNameError.new(name.camelize)
|
||||
end
|
||||
|
||||
klasses << returning(MigrationProxy.new) do |migration|
|
||||
klasses << (MigrationProxy.new).tap do |migration|
|
||||
migration.name = name.camelize
|
||||
migration.version = version
|
||||
migration.filename = file
|
||||
|
||||
@@ -8,10 +8,7 @@ module ActiveRecord
|
||||
#
|
||||
# You can define a scope that applies to all finders using ActiveRecord::Base.default_scope.
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
extend ClassMethods
|
||||
named_scope :scoped, lambda { |scope| scope }
|
||||
end
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
@@ -19,6 +16,10 @@ module ActiveRecord
|
||||
read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
|
||||
end
|
||||
|
||||
def scoped(scope, &block)
|
||||
Scope.new(self, scope, &block)
|
||||
end
|
||||
|
||||
# Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
|
||||
# such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
|
||||
#
|
||||
@@ -84,18 +85,22 @@ module ActiveRecord
|
||||
# assert_equal expected_options, Shirt.colored('red').proxy_options
|
||||
def named_scope(name, options = {}, &block)
|
||||
name = name.to_sym
|
||||
|
||||
scopes[name] = lambda do |parent_scope, *args|
|
||||
Scope.new(parent_scope, case options
|
||||
when Hash
|
||||
options
|
||||
when Proc
|
||||
options.call(*args)
|
||||
if self.model_name != parent_scope.model_name
|
||||
options.bind(parent_scope).call(*args)
|
||||
else
|
||||
options.call(*args)
|
||||
end
|
||||
end, &block)
|
||||
end
|
||||
(class << self; self end).instance_eval do
|
||||
define_method name do |*args|
|
||||
scopes[name].call(self, *args)
|
||||
end
|
||||
|
||||
singleton_class.send :define_method, name do |*args|
|
||||
scopes[name].call(self, *args)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -115,7 +120,7 @@ module ActiveRecord
|
||||
options ||= {}
|
||||
[options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
|
||||
extend Module.new(&block) if block_given?
|
||||
unless Scope === proxy_scope
|
||||
unless (Scope === proxy_scope || ActiveRecord::Associations::AssociationCollection === proxy_scope)
|
||||
@current_scoped_methods_when_defined = proxy_scope.send(:current_scoped_methods)
|
||||
end
|
||||
@proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
|
||||
|
||||
@@ -74,7 +74,7 @@ module ActiveRecord #:nodoc:
|
||||
end
|
||||
|
||||
def serializable_record
|
||||
returning(serializable_record = {}) do
|
||||
{}.tap do |serializable_record|
|
||||
serializable_names.each { |name| serializable_record[name] = @record.send(name) }
|
||||
add_includes do |association, records, opts|
|
||||
if records.is_a?(Enumerable)
|
||||
|
||||
@@ -184,7 +184,7 @@ module ActiveRecord
|
||||
|
||||
# Look up a session by id and unmarshal its data if found.
|
||||
def find_by_session_id(session_id)
|
||||
if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}")
|
||||
if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id)}")
|
||||
new(:session_id => session_id, :marshaled_data => record['data'])
|
||||
end
|
||||
end
|
||||
@@ -310,6 +310,14 @@ module ActiveRecord
|
||||
return true
|
||||
end
|
||||
|
||||
def destroy(env)
|
||||
if sid = current_session_id(env)
|
||||
Base.silence do
|
||||
get_session_model(env, sid).destroy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_session_model(env, sid)
|
||||
if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
|
||||
env[SESSION_RECORD_KEY] = find_session(sid)
|
||||
|
||||
@@ -80,7 +80,7 @@ module ActiveRecord
|
||||
|
||||
# Wraps an error message into a full_message format.
|
||||
#
|
||||
# The default full_message format for any locale is <tt>"{{attribute}} {{message}}"</tt>.
|
||||
# The default full_message format for any locale is <tt>"%{attribute} %{message}"</tt>.
|
||||
# One can specify locale specific default full_message format by storing it as a
|
||||
# translation for the key <tt>:"activerecord.errors.full_messages.format"</tt>.
|
||||
#
|
||||
@@ -109,7 +109,7 @@ module ActiveRecord
|
||||
keys = [
|
||||
:"full_messages.#{@message}",
|
||||
:'full_messages.format',
|
||||
'{{attribute}} {{message}}'
|
||||
'%{attribute} %{message}'
|
||||
]
|
||||
|
||||
options.merge!(:default => keys, :message => self.message)
|
||||
@@ -333,7 +333,6 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def generate_message(attribute, message = :invalid, options = {})
|
||||
ActiveSupport::Deprecation.warn("ActiveRecord::Errors#generate_message has been deprecated. Please use ActiveRecord::Error.new().to_s.")
|
||||
Error.new(@base, attribute, message, options).to_s
|
||||
end
|
||||
end
|
||||
@@ -604,13 +603,13 @@ module ActiveRecord
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# validates_length_of :first_name, :maximum=>30
|
||||
# validates_length_of :last_name, :maximum=>30, :message=>"less than {{count}} if you don't mind"
|
||||
# validates_length_of :last_name, :maximum=>30, :message=>"less than %{count} if you don't mind"
|
||||
# validates_length_of :fax, :in => 7..32, :allow_nil => true
|
||||
# validates_length_of :phone, :in => 7..32, :allow_blank => true
|
||||
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
|
||||
# validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least {{count}} character"
|
||||
# validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with {{count}} characters... don't play me."
|
||||
# validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least {{count}} words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
|
||||
# validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least %{count} character"
|
||||
# validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with %{count} characters... don't play me."
|
||||
# validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %{count} words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
|
||||
# end
|
||||
#
|
||||
# Configuration options:
|
||||
@@ -621,9 +620,9 @@ module ActiveRecord
|
||||
# * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
|
||||
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
|
||||
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
|
||||
# * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is {{count}} characters)").
|
||||
# * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is {{count}} characters)").
|
||||
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be {{count}} characters)").
|
||||
# * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %{count} characters)").
|
||||
# * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %{count} characters)").
|
||||
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be %{count} characters)").
|
||||
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
|
||||
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
|
||||
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
||||
@@ -825,7 +824,7 @@ module ActiveRecord
|
||||
if scope = configuration[:scope]
|
||||
Array(scope).map do |scope_item|
|
||||
scope_value = record.send(scope_item)
|
||||
condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{scope_item}", scope_value)
|
||||
condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{connection.quote_column_name(scope_item)}", scope_value)
|
||||
condition_params << scope_value
|
||||
end
|
||||
end
|
||||
@@ -885,7 +884,7 @@ module ActiveRecord
|
||||
# class Person < ActiveRecord::Base
|
||||
# validates_inclusion_of :gender, :in => %w( m f )
|
||||
# validates_inclusion_of :age, :in => 0..99
|
||||
# validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension {{value}} is not included in the list"
|
||||
# validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %{value} is not included in the list"
|
||||
# end
|
||||
#
|
||||
# Configuration options:
|
||||
@@ -919,7 +918,7 @@ module ActiveRecord
|
||||
# class Person < ActiveRecord::Base
|
||||
# validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
|
||||
# validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
|
||||
# validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension {{value}} is not allowed"
|
||||
# validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %{value} is not allowed"
|
||||
# end
|
||||
#
|
||||
# Configuration options:
|
||||
|
||||
@@ -2,7 +2,7 @@ module ActiveRecord
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 3
|
||||
TINY = 8
|
||||
TINY = 14
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
||||
@@ -65,15 +65,14 @@ class AdapterTest < ActiveRecord::TestCase
|
||||
end
|
||||
|
||||
def test_not_specifying_database_name_for_cross_database_selects
|
||||
assert_nothing_raised do
|
||||
ActiveRecord::Base.establish_connection({
|
||||
:adapter => 'mysql',
|
||||
:username => 'rails'
|
||||
})
|
||||
ActiveRecord::Base.connection.execute "SELECT activerecord_unittest.pirates.*, activerecord_unittest2.courses.* FROM activerecord_unittest.pirates, activerecord_unittest2.courses"
|
||||
begin
|
||||
assert_nothing_raised do
|
||||
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['arunit'].except(:database))
|
||||
ActiveRecord::Base.connection.execute "SELECT activerecord_unittest.pirates.*, activerecord_unittest2.courses.* FROM activerecord_unittest.pirates, activerecord_unittest2.courses"
|
||||
end
|
||||
ensure
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
end
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ module Remembered
|
||||
|
||||
module ClassMethods
|
||||
def remembered; @@remembered ||= []; end
|
||||
def random_element; @@remembered.random_element; end
|
||||
def sample; @@remembered.sample; end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -82,14 +82,14 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase
|
||||
[Circle, Square, Triangle, NonPolyOne, NonPolyTwo].map(&:create!)
|
||||
end
|
||||
1.upto(NUM_SIMPLE_OBJS) do
|
||||
PaintColor.create!(:non_poly_one_id => NonPolyOne.random_element.id)
|
||||
PaintTexture.create!(:non_poly_two_id => NonPolyTwo.random_element.id)
|
||||
PaintColor.create!(:non_poly_one_id => NonPolyOne.sample.id)
|
||||
PaintTexture.create!(:non_poly_two_id => NonPolyTwo.sample.id)
|
||||
end
|
||||
1.upto(NUM_SHAPE_EXPRESSIONS) do
|
||||
shape_type = [Circle, Square, Triangle].random_element
|
||||
paint_type = [PaintColor, PaintTexture].random_element
|
||||
ShapeExpression.create!(:shape_type => shape_type.to_s, :shape_id => shape_type.random_element.id,
|
||||
:paint_type => paint_type.to_s, :paint_id => paint_type.random_element.id)
|
||||
shape_type = [Circle, Square, Triangle].sample
|
||||
paint_type = [PaintColor, PaintTexture].sample
|
||||
ShapeExpression.create!(:shape_type => shape_type.to_s, :shape_id => shape_type.sample.id,
|
||||
:paint_type => paint_type.to_s, :paint_id => paint_type.sample.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
require 'cases/helper'
|
||||
require 'models/tee'
|
||||
require 'models/tie'
|
||||
require 'models/polymorphic_design'
|
||||
require 'models/polymorphic_price'
|
||||
|
||||
class EagerLoadNestedPolymorphicIncludeTest < ActiveRecord::TestCase
|
||||
fixtures :tees, :ties, :polymorphic_designs, :polymorphic_prices
|
||||
|
||||
def test_eager_load_polymorphic_has_one_nested_under_polymorphic_belongs_to
|
||||
designs = PolymorphicDesign.scoped(:include => {:designable => :polymorphic_price})
|
||||
|
||||
associated_price_ids = designs.map{|design| design.designable.polymorphic_price.id}
|
||||
expected_price_ids = [1, 2, 3, 4]
|
||||
|
||||
assert expected_price_ids.all?{|expected_id| associated_price_ids.include?(expected_id)},
|
||||
"Expected associated prices to be #{expected_price_ids.inspect} but they were #{associated_price_ids.sort.inspect}"
|
||||
end
|
||||
end
|
||||
@@ -357,6 +357,18 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
||||
assert_equal comments(:more_greetings), Author.find(authors(:david).id, :include => :comments_with_order_and_conditions).comments_with_order_and_conditions.first
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_through_with_conditions_join_model_with_include
|
||||
post_tags = Post.find(posts(:welcome).id).misc_tags
|
||||
eager_post_tags = Post.find(1, :include => :misc_tags).misc_tags
|
||||
assert_equal post_tags, eager_post_tags
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_through_association_with_order
|
||||
author_comments = Author.find(authors(:david).id).comments_desc
|
||||
eager_author_comments = Author.find(authors(:david).id, :include => :comments_desc).comments_desc
|
||||
assert_equal eager_author_comments, author_comments
|
||||
end
|
||||
|
||||
def test_eager_with_has_many_through_join_model_with_include
|
||||
author_comments = Author.find(authors(:david).id, :include => :comments_with_include).comments_with_include.to_a
|
||||
assert_no_queries do
|
||||
|
||||
@@ -819,4 +819,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
|
||||
assert_queries(0) { david.projects.columns; david.projects.columns }
|
||||
end
|
||||
|
||||
def test_include_method_in_has_and_belongs_to_many_association_should_return_true_for_instance_added_with_build
|
||||
project = Project.new
|
||||
developer = project.developers.build
|
||||
assert project.developers.include?(developer)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,6 +21,100 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||
Client.destroyed_client_ids.clear
|
||||
end
|
||||
|
||||
def test_create_by
|
||||
person = Person.create! :first_name => 'tenderlove'
|
||||
post = Post.find :first
|
||||
|
||||
assert_equal [], person.readers
|
||||
assert_nil person.readers.find_by_post_id(post.id)
|
||||
|
||||
reader = person.readers.create_by_post_id post.id
|
||||
|
||||
assert_equal 1, person.readers.count
|
||||
assert_equal 1, person.readers.length
|
||||
assert_equal post, person.readers.first.post
|
||||
assert_equal person, person.readers.first.person
|
||||
end
|
||||
|
||||
def test_create_by_multi
|
||||
person = Person.create! :first_name => 'tenderlove'
|
||||
post = Post.find :first
|
||||
|
||||
assert_equal [], person.readers
|
||||
|
||||
reader = person.readers.create_by_post_id_and_skimmer post.id, false
|
||||
|
||||
assert_equal 1, person.readers.count
|
||||
assert_equal 1, person.readers.length
|
||||
assert_equal post, person.readers.first.post
|
||||
assert_equal person, person.readers.first.person
|
||||
end
|
||||
|
||||
def test_find_or_create_by
|
||||
person = Person.create! :first_name => 'tenderlove'
|
||||
post = Post.find :first
|
||||
|
||||
assert_equal [], person.readers
|
||||
assert_nil person.readers.find_by_post_id(post.id)
|
||||
|
||||
reader = person.readers.find_or_create_by_post_id post.id
|
||||
|
||||
assert_equal 1, person.readers.count
|
||||
assert_equal 1, person.readers.length
|
||||
assert_equal post, person.readers.first.post
|
||||
assert_equal person, person.readers.first.person
|
||||
end
|
||||
|
||||
def test_find_or_create_by_with_additional_parameters
|
||||
post = Post.create! :title => 'test_find_or_create_by_with_additional_parameters', :body => 'this is the body'
|
||||
comment = post.comments.create! :body => 'test comment body', :type => 'test'
|
||||
|
||||
assert_equal comment, post.comments.find_or_create_by_body('test comment body')
|
||||
|
||||
post.comments.find_or_create_by_body(:body => 'other test comment body', :type => 'test')
|
||||
assert_equal 2, post.comments.count
|
||||
assert_equal 2, post.comments.length
|
||||
post.comments.find_or_create_by_body('other other test comment body', :type => 'test')
|
||||
assert_equal 3, post.comments.count
|
||||
assert_equal 3, post.comments.length
|
||||
post.comments.find_or_create_by_body_and_type('3rd test comment body', 'test')
|
||||
assert_equal 4, post.comments.count
|
||||
assert_equal 4, post.comments.length
|
||||
end
|
||||
|
||||
def test_find_or_create_by_with_same_parameters_creates_a_single_record
|
||||
author = Author.first
|
||||
assert_difference "Post.count", +1 do
|
||||
2.times do
|
||||
author.posts.find_or_create_by_body_and_title('one', 'two')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_find_or_create_by_with_block
|
||||
post = Post.create! :title => 'test_find_or_create_by_with_additional_parameters', :body => 'this is the body'
|
||||
comment = post.comments.find_or_create_by_body('other test comment body') { |comment| comment.type = 'test' }
|
||||
assert_equal 'test', comment.type
|
||||
end
|
||||
|
||||
def test_find_or_create
|
||||
person = Person.create! :first_name => 'tenderlove'
|
||||
post = Post.find :first
|
||||
|
||||
assert_equal [], person.readers
|
||||
assert_nil person.readers.find(:first, :conditions => {
|
||||
:post_id => post.id
|
||||
})
|
||||
|
||||
reader = person.readers.find_or_create :post_id => post.id
|
||||
|
||||
assert_equal 1, person.readers.count
|
||||
assert_equal 1, person.readers.length
|
||||
assert_equal post, person.readers.first.post
|
||||
assert_equal person, person.readers.first.person
|
||||
end
|
||||
|
||||
|
||||
def force_signal37_to_load_all_clients_of_firm
|
||||
companies(:first_firm).clients_of_firm.each {|f| }
|
||||
end
|
||||
@@ -486,7 +580,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||
assert the_client.new_record?
|
||||
end
|
||||
|
||||
def test_find_or_create
|
||||
def test_find_or_create_updates_size
|
||||
number_of_clients = companies(:first_firm).clients.size
|
||||
the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client")
|
||||
assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
|
||||
@@ -772,12 +866,26 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||
|
||||
def test_destroy_all
|
||||
force_signal37_to_load_all_clients_of_firm
|
||||
assert !companies(:first_firm).clients_of_firm.empty?, "37signals has clients after load"
|
||||
companies(:first_firm).clients_of_firm.destroy_all
|
||||
clients = companies(:first_firm).clients_of_firm.to_a
|
||||
assert !clients.empty?, "37signals has clients after load"
|
||||
destroyed = companies(:first_firm).clients_of_firm.destroy_all
|
||||
assert_equal clients.sort_by(&:id), destroyed.sort_by(&:id)
|
||||
assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen"
|
||||
assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all"
|
||||
assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh"
|
||||
end
|
||||
|
||||
def test_destroy_all_with_creates_and_scope_that_doesnt_match_created_records
|
||||
company = companies(:first_firm)
|
||||
unloaded_client_matching_scope = companies(:second_client)
|
||||
created_client_matching_scope = company.clients_of_firm.create!(:name => "Somesoft")
|
||||
created_client_not_matching_scope = company.clients_of_firm.create!(:name => "OtherCo")
|
||||
destroyed = company.clients_of_firm.with_oft_in_name.destroy_all
|
||||
assert destroyed.include?(unloaded_client_matching_scope), "unloaded clients matching the scope destroy_all on should have been destroyed"
|
||||
assert destroyed.include?(created_client_matching_scope), "loaded clients matching the scope destroy_all on should have been destroyed"
|
||||
assert !destroyed.include?(created_client_not_matching_scope), "loaded clients not matching the scope destroy_all on should not have been destroyed"
|
||||
end
|
||||
|
||||
def test_dependence
|
||||
firm = companies(:first_firm)
|
||||
assert_equal 2, firm.clients.size
|
||||
@@ -1156,5 +1264,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||
end
|
||||
EOF
|
||||
end
|
||||
end
|
||||
|
||||
def test_include_method_in_has_many_association_should_return_true_for_instance_added_with_build
|
||||
post = Post.new
|
||||
comment = post.comments.build
|
||||
assert post.comments.include?(comment)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -343,4 +343,18 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
|
||||
lambda { authors(:david).very_special_comments.delete(authors(:david).very_special_comments.first) },
|
||||
].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
|
||||
end
|
||||
|
||||
def test_include_method_in_association_through_should_return_true_for_instance_added_with_build
|
||||
person = Person.new
|
||||
reference = person.references.build
|
||||
job = reference.build_job
|
||||
assert person.jobs.include?(job)
|
||||
end
|
||||
|
||||
def test_include_method_in_association_through_should_return_true_for_instance_added_with_nested_builds
|
||||
author = Author.new
|
||||
post = author.posts.build
|
||||
comment = post.comments.build
|
||||
assert author.comments.include?(comment)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -74,6 +74,16 @@ class AssociationsTest < ActiveRecord::TestCase
|
||||
assert_queries(1) { assert_not_nil firm.clients(true).each {} }
|
||||
end
|
||||
end
|
||||
|
||||
def test_using_limitable_reflections_helper
|
||||
using_limitable_reflections = lambda { |reflections| ActiveRecord::Base.send :using_limitable_reflections?, reflections }
|
||||
belongs_to_reflections = [Tagging.reflect_on_association(:tag), Tagging.reflect_on_association(:super_tag)]
|
||||
has_many_reflections = [Tag.reflect_on_association(:taggings), Developer.reflect_on_association(:projects)]
|
||||
mixed_reflections = (belongs_to_reflections + has_many_reflections).uniq
|
||||
assert using_limitable_reflections.call(belongs_to_reflections), "Belong to associations are limitable"
|
||||
assert !using_limitable_reflections.call(has_many_reflections), "All has many style associations are not limitable"
|
||||
assert !using_limitable_reflections.call(mixed_reflections), "No collection associations (has many style) should pass"
|
||||
end
|
||||
|
||||
def test_storing_in_pstore
|
||||
require "tmpdir"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
require "cases/helper"
|
||||
require 'models/post'
|
||||
require 'models/author'
|
||||
require 'models/event_author'
|
||||
require 'models/topic'
|
||||
require 'models/reply'
|
||||
@@ -80,6 +79,23 @@ end
|
||||
class BasicsTest < ActiveRecord::TestCase
|
||||
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts
|
||||
|
||||
def test_column_names_are_escaped
|
||||
conn = ActiveRecord::Base.connection
|
||||
classname = conn.class.name[/[^:]*$/]
|
||||
badchar = {
|
||||
'SQLite3Adapter' => '"',
|
||||
'MysqlAdapter' => '`',
|
||||
'Mysql2Adapter' => '`',
|
||||
'PostgreSQLAdapter' => '"',
|
||||
'OracleAdapter' => '"',
|
||||
}.fetch(classname) {
|
||||
raise "need a bad char for #{classname}"
|
||||
}
|
||||
|
||||
quoted = conn.quote_column_name "foo#{badchar}bar"
|
||||
assert_equal("#{badchar}foo#{badchar * 2}bar#{badchar}", quoted)
|
||||
end
|
||||
|
||||
def test_table_exists
|
||||
assert !NonExistentTable.table_exists?
|
||||
assert Topic.table_exists?
|
||||
@@ -588,17 +604,25 @@ class BasicsTest < ActiveRecord::TestCase
|
||||
end
|
||||
|
||||
def test_destroy_all
|
||||
original_count = Topic.count
|
||||
topics_by_mary = Topic.count(:conditions => mary = "author_name = 'Mary'")
|
||||
conditions = "author_name = 'Mary'"
|
||||
topics_by_mary = Topic.all(:conditions => conditions, :order => 'id')
|
||||
assert ! topics_by_mary.empty?
|
||||
|
||||
Topic.destroy_all mary
|
||||
assert_equal original_count - topics_by_mary, Topic.count
|
||||
assert_difference('Topic.count', -topics_by_mary.size) do
|
||||
destroyed = Topic.destroy_all(conditions).sort_by(&:id)
|
||||
assert_equal topics_by_mary, destroyed
|
||||
assert destroyed.all? { |topic| topic.frozen? }
|
||||
end
|
||||
end
|
||||
|
||||
def test_destroy_many
|
||||
assert_equal 3, Client.count
|
||||
Client.destroy([2, 3])
|
||||
assert_equal 1, Client.count
|
||||
clients = Client.find([2, 3], :order => 'id')
|
||||
|
||||
assert_difference('Client.count', -2) do
|
||||
destroyed = Client.destroy([2, 3]).sort_by(&:id)
|
||||
assert_equal clients, destroyed
|
||||
assert destroyed.all? { |client| client.frozen? }
|
||||
end
|
||||
end
|
||||
|
||||
def test_delete_many
|
||||
@@ -612,55 +636,6 @@ class BasicsTest < ActiveRecord::TestCase
|
||||
assert Topic.find(2).approved?
|
||||
end
|
||||
|
||||
def test_increment_counter
|
||||
Topic.increment_counter("replies_count", 1)
|
||||
assert_equal 2, Topic.find(1).replies_count
|
||||
|
||||
Topic.increment_counter("replies_count", 1)
|
||||
assert_equal 3, Topic.find(1).replies_count
|
||||
end
|
||||
|
||||
def test_decrement_counter
|
||||
Topic.decrement_counter("replies_count", 2)
|
||||
assert_equal -1, Topic.find(2).replies_count
|
||||
|
||||
Topic.decrement_counter("replies_count", 2)
|
||||
assert_equal -2, Topic.find(2).replies_count
|
||||
end
|
||||
|
||||
def test_reset_counters
|
||||
assert_equal 1, Topic.find(1).replies_count
|
||||
|
||||
Topic.increment_counter("replies_count", 1)
|
||||
assert_equal 2, Topic.find(1).replies_count
|
||||
|
||||
Topic.reset_counters(1, :replies)
|
||||
assert_equal 1, Topic.find(1).replies_count
|
||||
end
|
||||
|
||||
def test_update_counter
|
||||
category = categories(:general)
|
||||
assert_nil category.categorizations_count
|
||||
assert_equal 2, category.categorizations.count
|
||||
|
||||
Category.update_counters(category.id, "categorizations_count" => category.categorizations.count)
|
||||
category.reload
|
||||
assert_not_nil category.categorizations_count
|
||||
assert_equal 2, category.categorizations_count
|
||||
|
||||
Category.update_counters(category.id, "categorizations_count" => category.categorizations.count)
|
||||
category.reload
|
||||
assert_not_nil category.categorizations_count
|
||||
assert_equal 4, category.categorizations_count
|
||||
|
||||
category_2 = categories(:technology)
|
||||
count_1, count_2 = (category.categorizations_count || 0), (category_2.categorizations_count || 0)
|
||||
Category.update_counters([category.id, category_2.id], "categorizations_count" => 2)
|
||||
category.reload; category_2.reload
|
||||
assert_equal count_1 + 2, category.categorizations_count
|
||||
assert_equal count_2 + 2, category_2.categorizations_count
|
||||
end
|
||||
|
||||
def test_update_all
|
||||
assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
|
||||
assert_equal "bulk updated!", Topic.find(1).content
|
||||
@@ -749,22 +724,24 @@ class BasicsTest < ActiveRecord::TestCase
|
||||
end
|
||||
|
||||
def test_class_name
|
||||
assert_equal "Firm", ActiveRecord::Base.class_name("firms")
|
||||
assert_equal "Category", ActiveRecord::Base.class_name("categories")
|
||||
assert_equal "AccountHolder", ActiveRecord::Base.class_name("account_holder")
|
||||
ActiveSupport::Deprecation.silence do
|
||||
assert_equal "Firm", ActiveRecord::Base.class_name("firms")
|
||||
assert_equal "Category", ActiveRecord::Base.class_name("categories")
|
||||
assert_equal "AccountHolder", ActiveRecord::Base.class_name("account_holder")
|
||||
|
||||
ActiveRecord::Base.pluralize_table_names = false
|
||||
assert_equal "Firms", ActiveRecord::Base.class_name( "firms" )
|
||||
ActiveRecord::Base.pluralize_table_names = true
|
||||
ActiveRecord::Base.pluralize_table_names = false
|
||||
assert_equal "Firms", ActiveRecord::Base.class_name( "firms" )
|
||||
ActiveRecord::Base.pluralize_table_names = true
|
||||
|
||||
ActiveRecord::Base.table_name_prefix = "test_"
|
||||
assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms" )
|
||||
ActiveRecord::Base.table_name_suffix = "_tests"
|
||||
assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms_tests" )
|
||||
ActiveRecord::Base.table_name_prefix = ""
|
||||
assert_equal "Firm", ActiveRecord::Base.class_name( "firms_tests" )
|
||||
ActiveRecord::Base.table_name_suffix = ""
|
||||
assert_equal "Firm", ActiveRecord::Base.class_name( "firms" )
|
||||
ActiveRecord::Base.table_name_prefix = "test_"
|
||||
assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms" )
|
||||
ActiveRecord::Base.table_name_suffix = "_tests"
|
||||
assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms_tests" )
|
||||
ActiveRecord::Base.table_name_prefix = ""
|
||||
assert_equal "Firm", ActiveRecord::Base.class_name( "firms_tests" )
|
||||
ActiveRecord::Base.table_name_suffix = ""
|
||||
assert_equal "Firm", ActiveRecord::Base.class_name( "firms" )
|
||||
end
|
||||
end
|
||||
|
||||
def test_null_fields
|
||||
@@ -2090,7 +2067,7 @@ class BasicsTest < ActiveRecord::TestCase
|
||||
|
||||
def test_inspect_instance
|
||||
topic = topics(:first)
|
||||
assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil>), topic.inspect
|
||||
assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil>), topic.inspect
|
||||
end
|
||||
|
||||
def test_inspect_new_instance
|
||||
|
||||
@@ -58,6 +58,19 @@ class CalculationsTest < ActiveRecord::TestCase
|
||||
[1,6,2].each { |firm_id| assert c.keys.include?(firm_id) }
|
||||
end
|
||||
|
||||
def test_should_group_by_multiple_fields
|
||||
c = Account.count(:all, :group => ['firm_id', :credit_limit])
|
||||
[ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) }
|
||||
end
|
||||
|
||||
def test_should_group_by_multiple_fields_having_functions
|
||||
c = Topic.count(:all, :group => [:author_name, 'COALESCE(type, title)'])
|
||||
assert_equal 1, c[["Nick", "The Third Topic of the day"]]
|
||||
assert_equal 1, c[["Mary", "Reply"]]
|
||||
assert_equal 1, c[["David", "The First Topic"]]
|
||||
assert_equal 1, c[["Carl", "Reply"]]
|
||||
end
|
||||
|
||||
def test_should_group_by_summed_field
|
||||
c = Account.sum(:credit_limit, :group => :firm_id)
|
||||
assert_equal 50, c[1]
|
||||
|
||||
@@ -48,6 +48,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
|
||||
def test_multi_results
|
||||
rows = ActiveRecord::Base.connection.select_rows('CALL ten();')
|
||||
assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}"
|
||||
assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
84
activerecord/test/cases/counter_cache_test.rb
Executable file
84
activerecord/test/cases/counter_cache_test.rb
Executable file
@@ -0,0 +1,84 @@
|
||||
require 'cases/helper'
|
||||
require 'models/topic'
|
||||
require 'models/reply'
|
||||
require 'models/category'
|
||||
require 'models/categorization'
|
||||
|
||||
class CounterCacheTest < ActiveRecord::TestCase
|
||||
fixtures :topics, :categories, :categorizations
|
||||
|
||||
class SpecialTopic < ::Topic
|
||||
has_many :special_replies, :foreign_key => 'parent_id'
|
||||
end
|
||||
|
||||
class SpecialReply < ::Reply
|
||||
belongs_to :special_topic, :foreign_key => 'parent_id', :counter_cache => 'replies_count'
|
||||
end
|
||||
|
||||
test "increment counter" do
|
||||
topic = Topic.find(1)
|
||||
assert_difference 'topic.reload.replies_count' do
|
||||
Topic.increment_counter(:replies_count, topic.id)
|
||||
end
|
||||
end
|
||||
|
||||
test "decrement counter" do
|
||||
topic = Topic.find(1)
|
||||
assert_difference 'topic.reload.replies_count', -1 do
|
||||
Topic.decrement_counter(:replies_count, topic.id)
|
||||
end
|
||||
end
|
||||
|
||||
test "reset counters" do
|
||||
topic = Topic.find(1)
|
||||
# throw the count off by 1
|
||||
Topic.increment_counter(:replies_count, topic.id)
|
||||
|
||||
# check that it gets reset
|
||||
assert_difference 'topic.reload.replies_count', -1 do
|
||||
Topic.reset_counters(topic.id, :replies)
|
||||
end
|
||||
end
|
||||
|
||||
test "reset counters with string argument" do
|
||||
topic = Topic.find(1)
|
||||
Topic.increment_counter('replies_count', topic.id)
|
||||
|
||||
assert_difference 'topic.reload.replies_count', -1 do
|
||||
Topic.reset_counters(topic.id, 'replies')
|
||||
end
|
||||
end
|
||||
|
||||
test "reset counters with modularized and camelized classnames" do
|
||||
special = SpecialTopic.create!(:title => 'Special')
|
||||
SpecialTopic.increment_counter(:replies_count, special.id)
|
||||
|
||||
assert_difference 'special.reload.replies_count', -1 do
|
||||
SpecialTopic.reset_counters(special.id, :special_replies)
|
||||
end
|
||||
end
|
||||
|
||||
test "update counter with initial null value" do
|
||||
category = categories(:general)
|
||||
assert_equal 2, category.categorizations.count
|
||||
assert_nil category.categorizations_count
|
||||
|
||||
Category.update_counters(category.id, :categorizations_count => category.categorizations.count)
|
||||
assert_equal 2, category.reload.categorizations_count
|
||||
end
|
||||
|
||||
test "update counter for decrement" do
|
||||
topic = Topic.find(1)
|
||||
assert_difference 'topic.reload.replies_count', -3 do
|
||||
Topic.update_counters(topic.id, :replies_count => -3)
|
||||
end
|
||||
end
|
||||
|
||||
test "update counters of multiple records" do
|
||||
t1, t2 = topics(:first, :second)
|
||||
|
||||
assert_difference ['t1.reload.replies_count', 't2.reload.replies_count'], 2 do
|
||||
Topic.update_counters([t1.id, t2.id], :replies_count => 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -53,7 +53,8 @@ class OptimisticLockingTest < ActiveRecord::TestCase
|
||||
assert_raises(ActiveRecord::StaleObjectError) { p2.destroy }
|
||||
|
||||
assert p1.destroy
|
||||
assert_equal true, p1.frozen?
|
||||
assert p1.frozen?
|
||||
assert p1.destroyed?
|
||||
assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) }
|
||||
end
|
||||
|
||||
|
||||
@@ -119,6 +119,13 @@ if ActiveRecord::Base.connection.supports_migrations?
|
||||
end
|
||||
end
|
||||
|
||||
def test_index_symbol_names
|
||||
assert_nothing_raised { Person.connection.add_index :people, :primary_contact_id, :name => :symbol_index_name }
|
||||
assert Person.connection.index_exists?(:people, :symbol_index_name, true)
|
||||
assert_nothing_raised { Person.connection.remove_index :people, :name => :symbol_index_name }
|
||||
assert_equal true, !Person.connection.index_exists?(:people, :symbol_index_name, false)
|
||||
end
|
||||
|
||||
def test_add_index_length_limit
|
||||
good_index_name = 'x' * Person.connection.index_name_length
|
||||
too_long_index_name = good_index_name + 'x'
|
||||
@@ -739,6 +746,10 @@ if ActiveRecord::Base.connection.supports_migrations?
|
||||
ActiveRecord::Base.connection.drop_table(:hats)
|
||||
end
|
||||
|
||||
def test_remove_column_no_second_parameter_raises_exception
|
||||
assert_raise(ArgumentError) { Person.connection.remove_column("funny") }
|
||||
end
|
||||
|
||||
def test_change_type_of_not_null_column
|
||||
assert_nothing_raised do
|
||||
Topic.connection.change_column "topics", "written_on", :datetime, :null => false
|
||||
|
||||
@@ -9,6 +9,11 @@ require 'models/developer'
|
||||
class NamedScopeTest < ActiveRecord::TestCase
|
||||
fixtures :posts, :authors, :topics, :comments, :author_addresses
|
||||
|
||||
def test_named_scope_with_STI
|
||||
assert_equal 5,Post.with_type_self.count
|
||||
assert_equal 1,SpecialPost.with_type_self.count
|
||||
end
|
||||
|
||||
def test_implements_enumerable
|
||||
assert !Topic.find(:all).empty?
|
||||
|
||||
@@ -141,6 +146,12 @@ class NamedScopeTest < ActiveRecord::TestCase
|
||||
assert_equal authors(:david).posts & Post.containing_the_letter_a, authors(:david).posts.containing_the_letter_a
|
||||
end
|
||||
|
||||
def test_nested_named_scopes_doesnt_duplicate_conditions_on_child_scopes
|
||||
comments_scope = posts(:welcome).comments.send(:construct_sql)
|
||||
named_scope_sql_conditions = posts(:welcome).comments.containing_the_letter_e.send(:current_scoped_methods)[:find][:conditions]
|
||||
assert_no_match /#{comments_scope}.*#{comments_scope}/i, named_scope_sql_conditions
|
||||
end
|
||||
|
||||
def test_has_many_through_associations_have_access_to_named_scopes
|
||||
assert_not_equal Comment.containing_the_letter_e, authors(:david).comments
|
||||
assert !Comment.containing_the_letter_e.empty?
|
||||
@@ -265,7 +276,7 @@ class NamedScopeTest < ActiveRecord::TestCase
|
||||
end
|
||||
|
||||
def test_rand_should_select_a_random_object_from_proxy
|
||||
assert Topic.approved.random_element.is_a?(Topic)
|
||||
assert Topic.approved.sample.is_a?(Topic)
|
||||
end
|
||||
|
||||
def test_should_use_where_in_query_for_named_scope
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user