Instiki 0.17.2: Security Release
This release upgrades Instiki to Rails 2.3.4, which patches two security holes in Rails. See http://weblog.rubyonrails.org/2009/9/4/ruby-on-rails-2-3-4 There are also some new features, and the usual boatload of bugfixes. See the CHANGELOG for details.
This commit is contained in:
parent
34c4306867
commit
4bdf703ab2
31
CHANGELOG
31
CHANGELOG
|
@ -1,3 +1,34 @@
|
||||||
|
* 0.17.2
|
||||||
|
|
||||||
|
Security: Updated to Rails 2.3.4
|
||||||
|
* Fixes Timing Weakness in Rails MessageVerifier and the Cookie Store
|
||||||
|
http://weblog.rubyonrails.org/2009/9/4/timing-weakness-in-ruby-on-rails
|
||||||
|
* Fixes XSS Vulnerability in Rails
|
||||||
|
http://weblog.rubyonrails.org/2009/9/4/xss-vulnerability-in-ruby-on-rails
|
||||||
|
|
||||||
|
New Features:
|
||||||
|
* Syntax colouring (`ruby` and `html`) for code blocks.
|
||||||
|
* Updated for itex2MML 1.3.10 (supports \rlap{} and \underline{}). You should upgrade that, too.
|
||||||
|
* Add a "Create New Page" Link to the Search Page. (Based on an idea by nowa)
|
||||||
|
* Updated to Rails 2.3.4
|
||||||
|
|
||||||
|
Bugs Fixed:
|
||||||
|
* Wikilinks to published webs should be to the published action. This didn't work
|
||||||
|
right for inter-web links. (Reported by Mike Shulman)
|
||||||
|
* Use .size, rather than .length for ActiveRecord associations. A huge memory saving
|
||||||
|
in building the recently_revised page.
|
||||||
|
* Refactor the upgrade_instiki rake task, to make it database-agnostic. (Many thanks to James Herdman)
|
||||||
|
* Web#files_path and Web#blatex_pngs_path now return Pathname objects. (Thanks, again, to James Herdman)
|
||||||
|
* Workaround for Mozilla Bug 449396. (Reported by Andrew Stacey)
|
||||||
|
* Correctly Set noindex,nofollow On /diff Pages.
|
||||||
|
* Page-renaming javascript deals correctly with page names containing ampersands, slashes, and other garbage.
|
||||||
|
* List of Wanted Pages should not include redirected pages.
|
||||||
|
* The Regexp, used in Maruku to detect "email" headers (used, e.g., for S5 slideshow metadata) could, for some inputs, interact badly with Instiki's Chunk Handler. Fixed.
|
||||||
|
* Ensure "rollback" locks page for editing.
|
||||||
|
* Generate relative URLs, when possible. (Patch by Dennis Knauf)
|
||||||
|
* Expire revisions of an edited page. Use a `before_save` hook to deal with the situation where a page's name has been changed.
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
* 0.17
|
* 0.17
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
|
|
|
@ -258,7 +258,7 @@ module Instiki
|
||||||
module VERSION #:nodoc:
|
module VERSION #:nodoc:
|
||||||
MAJOR = 0
|
MAJOR = 0
|
||||||
MINOR = 17
|
MINOR = 17
|
||||||
TINY = 0
|
TINY = 2
|
||||||
SUFFIX = '(MML+)'
|
SUFFIX = '(MML+)'
|
||||||
PRERELEASE = false
|
PRERELEASE = false
|
||||||
if PRERELEASE
|
if PRERELEASE
|
||||||
|
|
4
vendor/rails/actionmailer/CHANGELOG
vendored
4
vendor/rails/actionmailer/CHANGELOG
vendored
|
@ -1,3 +1,7 @@
|
||||||
|
*2.3.4 (September 4, 2009)*
|
||||||
|
|
||||||
|
* Minor bug fixes.
|
||||||
|
|
||||||
*2.3.3 (July 12, 2009)*
|
*2.3.3 (July 12, 2009)*
|
||||||
|
|
||||||
* No changes, just a version bump.
|
* No changes, just a version bump.
|
||||||
|
|
2
vendor/rails/actionmailer/Rakefile
vendored
2
vendor/rails/actionmailer/Rakefile
vendored
|
@ -54,7 +54,7 @@ spec = Gem::Specification.new do |s|
|
||||||
s.rubyforge_project = "actionmailer"
|
s.rubyforge_project = "actionmailer"
|
||||||
s.homepage = "http://www.rubyonrails.org"
|
s.homepage = "http://www.rubyonrails.org"
|
||||||
|
|
||||||
s.add_dependency('actionpack', '= 2.3.3' + PKG_BUILD)
|
s.add_dependency('actionpack', '= 2.3.4' + PKG_BUILD)
|
||||||
|
|
||||||
s.has_rdoc = true
|
s.has_rdoc = true
|
||||||
s.requirements << 'none'
|
s.requirements << 'none'
|
||||||
|
|
|
@ -543,6 +543,7 @@ module ActionMailer #:nodoc:
|
||||||
@headers ||= {}
|
@headers ||= {}
|
||||||
@body ||= {}
|
@body ||= {}
|
||||||
@mime_version = @@default_mime_version.dup if @@default_mime_version
|
@mime_version = @@default_mime_version.dup if @@default_mime_version
|
||||||
|
@sent_on ||= Time.now
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_message(method_name, body)
|
def render_message(method_name, body)
|
||||||
|
|
|
@ -2,7 +2,7 @@ module ActionMailer
|
||||||
module VERSION #:nodoc:
|
module VERSION #:nodoc:
|
||||||
MAJOR = 2
|
MAJOR = 2
|
||||||
MINOR = 3
|
MINOR = 3
|
||||||
TINY = 3
|
TINY = 4
|
||||||
|
|
||||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,7 +18,6 @@ class TestMailer < ActionMailer::Base
|
||||||
@recipients = recipient
|
@recipients = recipient
|
||||||
@subject = "[Signed up] Welcome #{recipient}"
|
@subject = "[Signed up] Welcome #{recipient}"
|
||||||
@from = "system@loudthinking.com"
|
@from = "system@loudthinking.com"
|
||||||
@sent_on = Time.local(2004, 12, 12)
|
|
||||||
@body["recipient"] = recipient
|
@body["recipient"] = recipient
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -356,12 +355,14 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_signed_up
|
def test_signed_up
|
||||||
|
Time.stubs(:now => Time.now)
|
||||||
|
|
||||||
expected = new_mail
|
expected = new_mail
|
||||||
expected.to = @recipient
|
expected.to = @recipient
|
||||||
expected.subject = "[Signed up] Welcome #{@recipient}"
|
expected.subject = "[Signed up] Welcome #{@recipient}"
|
||||||
expected.body = "Hello there, \n\nMr. #{@recipient}"
|
expected.body = "Hello there, \n\nMr. #{@recipient}"
|
||||||
expected.from = "system@loudthinking.com"
|
expected.from = "system@loudthinking.com"
|
||||||
expected.date = Time.local(2004, 12, 12)
|
expected.date = Time.now
|
||||||
|
|
||||||
created = nil
|
created = nil
|
||||||
assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) }
|
assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) }
|
||||||
|
|
11
vendor/rails/actionpack/CHANGELOG
vendored
11
vendor/rails/actionpack/CHANGELOG
vendored
|
@ -1,3 +1,12 @@
|
||||||
|
*2.3.4 (September 4, 2009)*
|
||||||
|
|
||||||
|
* Sanitize multibyte strings before escaping them with escape_once. CVE-2009-3009
|
||||||
|
|
||||||
|
* Introduce grouped_collection_select helper. #1249 [Dan Codeape, Erik Ostrom]
|
||||||
|
|
||||||
|
* Ruby 1.9: fix Content-Length for multibyte send_data streaming. #2661 [Sava Chankov]
|
||||||
|
|
||||||
|
|
||||||
*2.3.3 (July 12, 2009)*
|
*2.3.3 (July 12, 2009)*
|
||||||
|
|
||||||
* Fixed that TestResponse.cookies was returning cookies unescaped #1867 [Doug McInnes]
|
* Fixed that TestResponse.cookies was returning cookies unescaped #1867 [Doug McInnes]
|
||||||
|
@ -7,6 +16,8 @@
|
||||||
|
|
||||||
* Fixed that redirection would just log the options, not the final url (which lead to "Redirected to #<Post:0x23150b8>") [DHH]
|
* Fixed that redirection would just log the options, not the final url (which lead to "Redirected to #<Post:0x23150b8>") [DHH]
|
||||||
|
|
||||||
|
* Don't check authenticity tokens for any AJAX requests [Ross Kaffenberger/Bryan Helmkamp]
|
||||||
|
|
||||||
* Added ability to pass in :public => true to fresh_when, stale?, and expires_in to make the request proxy cachable #2095 [Gregg Pollack]
|
* Added ability to pass in :public => true to fresh_when, stale?, and expires_in to make the request proxy cachable #2095 [Gregg Pollack]
|
||||||
|
|
||||||
* Fixed that passing a custom form builder would be forwarded to nested fields_for calls #2023 [Eloy Duran/Nate Wiger]
|
* Fixed that passing a custom form builder would be forwarded to nested fields_for calls #2023 [Eloy Duran/Nate Wiger]
|
||||||
|
|
4
vendor/rails/actionpack/Rakefile
vendored
4
vendor/rails/actionpack/Rakefile
vendored
|
@ -29,7 +29,7 @@ Rake::TestTask.new(:test_action_pack) do |t|
|
||||||
|
|
||||||
# make sure we include the tests in alphabetical order as on some systems
|
# make sure we include the tests in alphabetical order as on some systems
|
||||||
# this will not happen automatically and the tests (as a whole) will error
|
# this will not happen automatically and the tests (as a whole) will error
|
||||||
t.test_files = Dir.glob( "test/[cft]*/**/*_test.rb" ).sort
|
t.test_files = Dir.glob( "test/[cftv]*/**/*_test.rb" ).sort
|
||||||
|
|
||||||
t.verbose = true
|
t.verbose = true
|
||||||
#t.warning = true
|
#t.warning = true
|
||||||
|
@ -79,7 +79,7 @@ spec = Gem::Specification.new do |s|
|
||||||
s.has_rdoc = true
|
s.has_rdoc = true
|
||||||
s.requirements << 'none'
|
s.requirements << 'none'
|
||||||
|
|
||||||
s.add_dependency('activesupport', '= 2.3.3' + PKG_BUILD)
|
s.add_dependency('activesupport', '= 2.3.4' + PKG_BUILD)
|
||||||
s.add_dependency('rack', '~> 1.0.0')
|
s.add_dependency('rack', '~> 1.0.0')
|
||||||
|
|
||||||
s.require_path = 'lib'
|
s.require_path = 'lib'
|
||||||
|
|
87
vendor/rails/actionpack/examples/minimal.rb
vendored
Normal file
87
vendor/rails/actionpack/examples/minimal.rb
vendored
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
$:.push File.join(File.dirname(__FILE__), "..", "lib")
|
||||||
|
$:.push File.join(File.dirname(__FILE__), "..", "..", "activesupport", "lib")
|
||||||
|
require "action_controller"
|
||||||
|
|
||||||
|
class Runner
|
||||||
|
def initialize(app, output)
|
||||||
|
@app, @output = app, output
|
||||||
|
end
|
||||||
|
|
||||||
|
def puts(*)
|
||||||
|
super if @output
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
env['n'].to_i.times { @app.call(env) }
|
||||||
|
@app.call(env).tap { |response| report(env, response) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def report(env, response)
|
||||||
|
if ENV["DEBUG"]
|
||||||
|
out = env['rack.errors']
|
||||||
|
p response.headers
|
||||||
|
out.puts response.status, response.headers.to_yaml, '---'
|
||||||
|
response.body.each { |part| out.puts part }
|
||||||
|
out.puts '---'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.puts(*)
|
||||||
|
super if @output
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.run(app, n, label = nil, uri = "/", output = true)
|
||||||
|
@output = output
|
||||||
|
puts label, '=' * label.size if label
|
||||||
|
env = Rack::MockRequest.env_for(uri).merge('n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout)
|
||||||
|
t = Benchmark.realtime { new(app, output).call(env) }
|
||||||
|
puts "%d ms / %d req = %.1f usec/req" % [10**3 * t, n, 10**6 * t / n]
|
||||||
|
puts
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
N = (ENV['N'] || 1000).to_i
|
||||||
|
|
||||||
|
class BasePostController < ActionController::Base
|
||||||
|
append_view_path "#{File.dirname(__FILE__)}/views"
|
||||||
|
|
||||||
|
def index
|
||||||
|
render :text => 'Hello'
|
||||||
|
end
|
||||||
|
|
||||||
|
def partial
|
||||||
|
render :partial => "/partial"
|
||||||
|
end
|
||||||
|
|
||||||
|
def many_partials
|
||||||
|
render :partial => "/many_partials"
|
||||||
|
end
|
||||||
|
|
||||||
|
def partial_collection
|
||||||
|
render :partial => "/collection", :collection => [1,2,3,4,5,6,7,8,9,10]
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_template
|
||||||
|
render :template => "template"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# p BasePostController.call(Rack::MockRequest.env_for("/?action=index").merge("REQUEST_URI" => "/")).body
|
||||||
|
|
||||||
|
Runner.run(BasePostController, N, 'index', "/?action=index", false)
|
||||||
|
Runner.run(BasePostController, N, 'partial', "/?action=partial", false)
|
||||||
|
Runner.run(BasePostController, N, 'many partials', "/?action=many_partials", false)
|
||||||
|
Runner.run(BasePostController, N, 'collection', "/?action=partial_collection", false)
|
||||||
|
Runner.run(BasePostController, N, 'template', "/?action=show_template", false)
|
||||||
|
|
||||||
|
(ENV["M"] || 1).to_i.times do
|
||||||
|
Runner.run(BasePostController, N, 'index', "/?action=index")
|
||||||
|
Runner.run(BasePostController, N, 'partial', "/?action=partial")
|
||||||
|
Runner.run(BasePostController, N, 'many partials', "/?action=many_partials")
|
||||||
|
Runner.run(BasePostController, N, 'collection', "/?action=partial_collection")
|
||||||
|
Runner.run(BasePostController, N, 'template', "/?action=show_template")
|
||||||
|
end
|
||||||
|
# Runner.run(BasePostController.action(:many_partials), N, 'index')
|
||||||
|
# Runner.run(BasePostController.action(:many_partials), N, 'many_partials')
|
||||||
|
# Runner.run(BasePostController.action(:partial_collection), N, 'collection')
|
||||||
|
# Runner.run(BasePostController.action(:show_template), N, 'template')
|
1
vendor/rails/actionpack/examples/views/_collection.erb
vendored
Normal file
1
vendor/rails/actionpack/examples/views/_collection.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<%= collection %>
|
1
vendor/rails/actionpack/examples/views/_hello.erb
vendored
Normal file
1
vendor/rails/actionpack/examples/views/_hello.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Hello
|
10
vendor/rails/actionpack/examples/views/_many_partials.erb
vendored
Normal file
10
vendor/rails/actionpack/examples/views/_many_partials.erb
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<%= render :partial => '/hello' %>
|
||||||
|
<%= render :partial => '/hello' %>
|
||||||
|
<%= render :partial => '/hello' %>
|
||||||
|
<%= render :partial => '/hello' %>
|
||||||
|
<%= render :partial => '/hello' %>
|
||||||
|
<%= render :partial => '/hello' %>
|
||||||
|
<%= render :partial => '/hello' %>
|
||||||
|
<%= render :partial => '/hello' %>
|
||||||
|
<%= render :partial => '/hello' %>
|
||||||
|
<%= render :partial => '/hello' %>
|
10
vendor/rails/actionpack/examples/views/_partial.erb
vendored
Normal file
10
vendor/rails/actionpack/examples/views/_partial.erb
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<%= "Hello" %>
|
||||||
|
<%= "Hello" %>
|
||||||
|
<%= "Hello" %>
|
||||||
|
<%= "Hello" %>
|
||||||
|
<%= "Hello" %>
|
||||||
|
<%= "Hello" %>
|
||||||
|
<%= "Hello" %>
|
||||||
|
<%= "Hello" %>
|
||||||
|
<%= "Hello" %>
|
||||||
|
<%= "Hello" %>
|
1
vendor/rails/actionpack/examples/views/layouts/alt.html.erb
vendored
Normal file
1
vendor/rails/actionpack/examples/views/layouts/alt.html.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
+ <%= yield %> +
|
1
vendor/rails/actionpack/examples/views/layouts/kaigi.html.erb
vendored
Normal file
1
vendor/rails/actionpack/examples/views/layouts/kaigi.html.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Hello <%= yield %> Goodbye
|
1
vendor/rails/actionpack/examples/views/template.html.erb
vendored
Normal file
1
vendor/rails/actionpack/examples/views/template.html.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Hello
|
|
@ -31,12 +31,8 @@ rescue LoadError
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
gem 'rack', '~> 1.0.0'
|
||||||
gem 'rack', '~> 1.1.0'
|
require 'rack'
|
||||||
require 'rack'
|
|
||||||
rescue Gem::LoadError
|
|
||||||
require 'action_controller/vendor/rack-1.1.pre/rack'
|
|
||||||
end
|
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# TODO: Review explicit to see if they will automatically be handled by
|
# TODO: Review explicit to see if they will automatically be handled by
|
||||||
|
|
|
@ -64,7 +64,9 @@ module ActionController
|
||||||
# Support partial arguments for hash redirections
|
# Support partial arguments for hash redirections
|
||||||
if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
|
if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
|
||||||
if options.all? {|(key, value)| @response.redirected_to[key] == value}
|
if options.all? {|(key, value)| @response.redirected_to[key] == value}
|
||||||
::ActiveSupport::Deprecation.warn("Using assert_redirected_to with partial hash arguments is deprecated. Specify the full set arguments instead", caller)
|
callstack = caller.dup
|
||||||
|
callstack.slice!(0, 2)
|
||||||
|
::ActiveSupport::Deprecation.warn("Using assert_redirected_to with partial hash arguments is deprecated. Specify the full set arguments instead", callstack)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -493,7 +493,12 @@ module ActionController #:nodoc:
|
||||||
filtered_parameters[key] = filter_parameters(value)
|
filtered_parameters[key] = filter_parameters(value)
|
||||||
elsif value.is_a?(Array)
|
elsif value.is_a?(Array)
|
||||||
filtered_parameters[key] = value.collect do |item|
|
filtered_parameters[key] = value.collect do |item|
|
||||||
filter_parameters(item)
|
case item
|
||||||
|
when Hash, Array
|
||||||
|
filter_parameters(item)
|
||||||
|
else
|
||||||
|
item
|
||||||
|
end
|
||||||
end
|
end
|
||||||
elsif block_given?
|
elsif block_given?
|
||||||
key = key.dup
|
key = key.dup
|
||||||
|
@ -814,7 +819,6 @@ module ActionController #:nodoc:
|
||||||
# render :text => proc { |response, output|
|
# render :text => proc { |response, output|
|
||||||
# 10_000_000.times do |i|
|
# 10_000_000.times do |i|
|
||||||
# output.write("This is line #{i}\n")
|
# output.write("This is line #{i}\n")
|
||||||
# output.flush
|
|
||||||
# end
|
# end
|
||||||
# }
|
# }
|
||||||
#
|
#
|
||||||
|
|
|
@ -51,7 +51,7 @@ module ActionController #:nodoc:
|
||||||
protected
|
protected
|
||||||
# Returns the cookie container, which operates as described above.
|
# Returns the cookie container, which operates as described above.
|
||||||
def cookies
|
def cookies
|
||||||
CookieJar.new(self)
|
@cookies ||= CookieJar.new(self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,12 @@ module ActionController
|
||||||
# Dispatches requests to the appropriate controller and takes care of
|
# Dispatches requests to the appropriate controller and takes care of
|
||||||
# reloading the app after each request when Dependencies.load? is true.
|
# reloading the app after each request when Dependencies.load? is true.
|
||||||
class Dispatcher
|
class Dispatcher
|
||||||
|
@@cache_classes = true
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def define_dispatcher_callbacks(cache_classes)
|
def define_dispatcher_callbacks(cache_classes)
|
||||||
|
@@cache_classes = cache_classes
|
||||||
unless cache_classes
|
unless cache_classes
|
||||||
unless self.middleware.include?(Reloader)
|
|
||||||
self.middleware.insert_after(Failsafe, Reloader)
|
|
||||||
end
|
|
||||||
|
|
||||||
ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
|
ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -79,7 +78,7 @@ module ActionController
|
||||||
# DEPRECATE: Remove arguments, since they are only used by CGI
|
# DEPRECATE: Remove arguments, since they are only used by CGI
|
||||||
def initialize(output = $stdout, request = nil, response = nil)
|
def initialize(output = $stdout, request = nil, response = nil)
|
||||||
@output = output
|
@output = output
|
||||||
@app = @@middleware.build(lambda { |env| self.dup._call(env) })
|
build_middleware_stack if @@cache_classes
|
||||||
end
|
end
|
||||||
|
|
||||||
def dispatch
|
def dispatch
|
||||||
|
@ -103,7 +102,18 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(env)
|
def call(env)
|
||||||
@app.call(env)
|
if @@cache_classes
|
||||||
|
@app.call(env)
|
||||||
|
else
|
||||||
|
Reloader.run do
|
||||||
|
# When class reloading is turned on, we will want to rebuild the
|
||||||
|
# middleware stack every time we process a request. If we don't
|
||||||
|
# rebuild the middleware stack, then the stack may contain references
|
||||||
|
# to old classes metal classes, which will b0rk class reloading.
|
||||||
|
build_middleware_stack
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def _call(env)
|
def _call(env)
|
||||||
|
@ -114,5 +124,10 @@ module ActionController
|
||||||
def flush_logger
|
def flush_logger
|
||||||
Base.logger.flush
|
Base.logger.flush
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def build_middleware_stack
|
||||||
|
@app = @@middleware.build(lambda { |env| self.dup._call(env) })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -139,7 +139,7 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
def decode_credentials(request)
|
def decode_credentials(request)
|
||||||
ActiveSupport::Base64.decode64(authorization(request).split.last || '')
|
ActiveSupport::Base64.decode64(authorization(request).split(' ', 2).last || '')
|
||||||
end
|
end
|
||||||
|
|
||||||
def encode_credentials(user_name, password)
|
def encode_credentials(user_name, password)
|
||||||
|
@ -195,9 +195,10 @@ module ActionController
|
||||||
return false unless password
|
return false unless password
|
||||||
|
|
||||||
method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
|
method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
|
||||||
|
uri = credentials[:uri][0,1] == '/' ? request.request_uri : request.url
|
||||||
|
|
||||||
[true, false].any? do |password_is_ha1|
|
[true, false].any? do |password_is_ha1|
|
||||||
expected = expected_response(method, request.env['REQUEST_URI'], credentials, password, password_is_ha1)
|
expected = expected_response(method, uri, credentials, password, password_is_ha1)
|
||||||
expected == credentials[:response]
|
expected == credentials[:response]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -47,6 +47,8 @@ module ActionController
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
rescue Exception => e # YAML, XML or Ruby code block errors
|
rescue Exception => e # YAML, XML or Ruby code block errors
|
||||||
|
logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
|
||||||
|
|
||||||
raise
|
raise
|
||||||
{ "body" => request.raw_post,
|
{ "body" => request.raw_post,
|
||||||
"content_type" => request.content_type,
|
"content_type" => request.content_type,
|
||||||
|
@ -67,5 +69,9 @@ module ActionController
|
||||||
|
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def logger
|
||||||
|
defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
|
require 'thread'
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
class Reloader
|
class Reloader
|
||||||
|
@@default_lock = Mutex.new
|
||||||
|
cattr_accessor :default_lock
|
||||||
|
|
||||||
class BodyWrapper
|
class BodyWrapper
|
||||||
def initialize(body)
|
def initialize(body, lock)
|
||||||
@body = body
|
@body = body
|
||||||
|
@lock = lock
|
||||||
end
|
end
|
||||||
|
|
||||||
def close
|
def close
|
||||||
@body.close if @body.respond_to?(:close)
|
@body.close if @body.respond_to?(:close)
|
||||||
ensure
|
ensure
|
||||||
Dispatcher.cleanup_application
|
Dispatcher.cleanup_application
|
||||||
|
@lock.unlock
|
||||||
end
|
end
|
||||||
|
|
||||||
def method_missing(*args, &block)
|
def method_missing(*args, &block)
|
||||||
|
@ -20,26 +27,28 @@ module ActionController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(app)
|
def self.run(lock = @@default_lock)
|
||||||
@app = app
|
lock.lock
|
||||||
end
|
begin
|
||||||
|
Dispatcher.reload_application
|
||||||
def call(env)
|
status, headers, body = yield
|
||||||
Dispatcher.reload_application
|
# We do not want to call 'cleanup_application' in an ensure block
|
||||||
status, headers, body = @app.call(env)
|
# because the returned Rack response body may lazily generate its data. This
|
||||||
# We do not want to call 'cleanup_application' in an ensure block
|
# is for example the case if one calls
|
||||||
# because the returned Rack response body may lazily generate its data. This
|
#
|
||||||
# is for example the case if one calls
|
# render :text => lambda { ... code here which refers to application models ... }
|
||||||
#
|
#
|
||||||
# render :text => lambda { ... code here which refers to application models ... }
|
# in an ActionController.
|
||||||
#
|
#
|
||||||
# in an ActionController.
|
# Instead, we will want to cleanup the application code after the request is
|
||||||
#
|
# completely finished. So we wrap the body in a BodyWrapper class so that
|
||||||
# Instead, we will want to cleanup the application code after the request is
|
# when the Rack handler calls #close during the end of the request, we get to
|
||||||
# completely finished. So we wrap the body in a BodyWrapper class so that
|
# run our cleanup code.
|
||||||
# when the Rack handler calls #close during the end of the request, we get to
|
[status, headers, BodyWrapper.new(body, lock)]
|
||||||
# run our cleanup code.
|
rescue Exception
|
||||||
[status, headers, BodyWrapper.new(body)]
|
lock.unlock
|
||||||
|
raise
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -81,12 +81,13 @@ module ActionController #:nodoc:
|
||||||
|
|
||||||
# Returns true or false if a request is verified. Checks:
|
# Returns true or false if a request is verified. Checks:
|
||||||
#
|
#
|
||||||
# * is the format restricted? By default, only HTML and AJAX requests are checked.
|
# * is the format restricted? By default, only HTML requests are checked.
|
||||||
# * is it a GET request? Gets should be safe and idempotent
|
# * is it a GET request? Gets should be safe and idempotent
|
||||||
# * Does the form_authenticity_token match the given token value from the params?
|
# * Does the form_authenticity_token match the given token value from the params?
|
||||||
def verified_request?
|
def verified_request?
|
||||||
!protect_against_forgery? ||
|
!protect_against_forgery? ||
|
||||||
request.method == :get ||
|
request.method == :get ||
|
||||||
|
request.xhr? ||
|
||||||
!verifiable_request_format? ||
|
!verifiable_request_format? ||
|
||||||
form_authenticity_token == params[request_forgery_protection_token]
|
form_authenticity_token == params[request_forgery_protection_token]
|
||||||
end
|
end
|
||||||
|
|
|
@ -317,9 +317,10 @@ module ActionController
|
||||||
# notes.resources :attachments
|
# notes.resources :attachments
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# * <tt>:path_names</tt> - Specify different names for the 'new' and 'edit' actions. For example:
|
# * <tt>:path_names</tt> - Specify different path names for the actions. For example:
|
||||||
# # new_products_path == '/productos/nuevo'
|
# # new_products_path == '/productos/nuevo'
|
||||||
# map.resources :products, :as => 'productos', :path_names => { :new => 'nuevo', :edit => 'editar' }
|
# # bids_product_path(1) == '/productos/1/licitacoes'
|
||||||
|
# map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
|
||||||
#
|
#
|
||||||
# You can also set default action names from an environment, like this:
|
# You can also set default action names from an environment, like this:
|
||||||
# config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
|
# config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
|
||||||
|
@ -525,16 +526,16 @@ module ActionController
|
||||||
resource = Resource.new(entities, options)
|
resource = Resource.new(entities, options)
|
||||||
|
|
||||||
with_options :controller => resource.controller do |map|
|
with_options :controller => resource.controller do |map|
|
||||||
map_collection_actions(map, resource)
|
|
||||||
map_default_collection_actions(map, resource)
|
|
||||||
map_new_actions(map, resource)
|
|
||||||
map_member_actions(map, resource)
|
|
||||||
|
|
||||||
map_associations(resource, options)
|
map_associations(resource, options)
|
||||||
|
|
||||||
if block_given?
|
if block_given?
|
||||||
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
|
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
map_collection_actions(map, resource)
|
||||||
|
map_default_collection_actions(map, resource)
|
||||||
|
map_new_actions(map, resource)
|
||||||
|
map_member_actions(map, resource)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -542,16 +543,16 @@ module ActionController
|
||||||
resource = SingletonResource.new(entities, options)
|
resource = SingletonResource.new(entities, options)
|
||||||
|
|
||||||
with_options :controller => resource.controller do |map|
|
with_options :controller => resource.controller do |map|
|
||||||
map_collection_actions(map, resource)
|
|
||||||
map_new_actions(map, resource)
|
|
||||||
map_member_actions(map, resource)
|
|
||||||
map_default_singleton_actions(map, resource)
|
|
||||||
|
|
||||||
map_associations(resource, options)
|
map_associations(resource, options)
|
||||||
|
|
||||||
if block_given?
|
if block_given?
|
||||||
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
|
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
map_collection_actions(map, resource)
|
||||||
|
map_new_actions(map, resource)
|
||||||
|
map_member_actions(map, resource)
|
||||||
|
map_default_singleton_actions(map, resource)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -586,7 +587,10 @@ module ActionController
|
||||||
resource.collection_methods.each do |method, actions|
|
resource.collection_methods.each do |method, actions|
|
||||||
actions.each do |action|
|
actions.each do |action|
|
||||||
[method].flatten.each do |m|
|
[method].flatten.each do |m|
|
||||||
map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
|
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
|
||||||
|
action_path ||= action
|
||||||
|
|
||||||
|
map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -166,6 +166,12 @@ module ActionController # :nodoc:
|
||||||
str
|
str
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def flush #:nodoc:
|
||||||
|
ActiveSupport::Deprecation.warn(
|
||||||
|
'Calling output.flush is no longer needed for streaming output ' +
|
||||||
|
'because ActionController::Response automatically handles it', caller)
|
||||||
|
end
|
||||||
|
|
||||||
def set_cookie(key, value)
|
def set_cookie(key, value)
|
||||||
if value.has_key?(:http_only)
|
if value.has_key?(:http_only)
|
||||||
ActiveSupport::Deprecation.warn(
|
ActiveSupport::Deprecation.warn(
|
||||||
|
|
|
@ -271,6 +271,9 @@ module ActionController
|
||||||
|
|
||||||
ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
|
ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
|
||||||
|
|
||||||
|
mattr_accessor :generate_best_match
|
||||||
|
self.generate_best_match = true
|
||||||
|
|
||||||
# The root paths which may contain controller files
|
# The root paths which may contain controller files
|
||||||
mattr_accessor :controller_paths
|
mattr_accessor :controller_paths
|
||||||
self.controller_paths = []
|
self.controller_paths = []
|
||||||
|
|
|
@ -405,11 +405,14 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
# don't use the recalled keys when determining which routes to check
|
# don't use the recalled keys when determining which routes to check
|
||||||
routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }]
|
future_routes, deprecated_routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }]
|
||||||
|
routes = Routing.generate_best_match ? deprecated_routes : future_routes
|
||||||
|
|
||||||
routes.each do |route|
|
routes.each_with_index do |route, index|
|
||||||
results = route.__send__(method, options, merged, expire_on)
|
results = route.__send__(method, options, merged, expire_on)
|
||||||
return results if results && (!results.is_a?(Array) || results.first)
|
if results && (!results.is_a?(Array) || results.first)
|
||||||
|
return results
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -448,7 +451,10 @@ module ActionController
|
||||||
@routes_by_controller ||= Hash.new do |controller_hash, controller|
|
@routes_by_controller ||= Hash.new do |controller_hash, controller|
|
||||||
controller_hash[controller] = Hash.new do |action_hash, action|
|
controller_hash[controller] = Hash.new do |action_hash, action|
|
||||||
action_hash[action] = Hash.new do |key_hash, keys|
|
action_hash[action] = Hash.new do |key_hash, keys|
|
||||||
key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys)
|
key_hash[keys] = [
|
||||||
|
routes_for_controller_and_action_and_keys(controller, action, keys),
|
||||||
|
deprecated_routes_for_controller_and_action_and_keys(controller, action, keys)
|
||||||
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -460,10 +466,11 @@ module ActionController
|
||||||
merged = options if expire_on[:controller]
|
merged = options if expire_on[:controller]
|
||||||
action = merged[:action] || 'index'
|
action = merged[:action] || 'index'
|
||||||
|
|
||||||
routes_by_controller[controller][action][merged.keys]
|
routes_by_controller[controller][action][merged.keys][1]
|
||||||
end
|
end
|
||||||
|
|
||||||
def routes_for_controller_and_action(controller, action)
|
def routes_for_controller_and_action(controller, action)
|
||||||
|
ActiveSupport::Deprecation.warn "routes_for_controller_and_action() has been deprecated. Please use routes_for()"
|
||||||
selected = routes.select do |route|
|
selected = routes.select do |route|
|
||||||
route.matches_controller_and_action? controller, action
|
route.matches_controller_and_action? controller, action
|
||||||
end
|
end
|
||||||
|
@ -471,6 +478,12 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
def routes_for_controller_and_action_and_keys(controller, action, keys)
|
def routes_for_controller_and_action_and_keys(controller, action, keys)
|
||||||
|
routes.select do |route|
|
||||||
|
route.matches_controller_and_action? controller, action
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def deprecated_routes_for_controller_and_action_and_keys(controller, action, keys)
|
||||||
selected = routes.select do |route|
|
selected = routes.select do |route|
|
||||||
route.matches_controller_and_action? controller, action
|
route.matches_controller_and_action? controller, action
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require 'active_support/core_ext/string/bytesize'
|
||||||
|
|
||||||
module ActionController #:nodoc:
|
module ActionController #:nodoc:
|
||||||
# Methods for sending arbitrary data and for streaming files to the browser,
|
# Methods for sending arbitrary data and for streaming files to the browser,
|
||||||
# instead of rendering.
|
# instead of rendering.
|
||||||
|
@ -137,7 +139,7 @@ module ActionController #:nodoc:
|
||||||
# instead. See ActionController::Base#render for more information.
|
# instead. See ActionController::Base#render for more information.
|
||||||
def send_data(data, options = {}) #:doc:
|
def send_data(data, options = {}) #:doc:
|
||||||
logger.info "Sending data #{options[:filename]}" if logger
|
logger.info "Sending data #{options[:filename]}" if logger
|
||||||
send_file_headers! options.merge(:length => data.size)
|
send_file_headers! options.merge(:length => data.bytesize)
|
||||||
@performed_render = false
|
@performed_render = false
|
||||||
render :status => options[:status], :text => data
|
render :status => options[:status], :text => data
|
||||||
end
|
end
|
||||||
|
|
|
@ -184,7 +184,7 @@ module ActionController
|
||||||
path = rewrite_path(options)
|
path = rewrite_path(options)
|
||||||
rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||||
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
|
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
|
||||||
rewritten_url << "##{options[:anchor]}" if options[:anchor]
|
rewritten_url << "##{CGI.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
|
||||||
|
|
||||||
rewritten_url
|
rewritten_url
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@ module ActionPack #:nodoc:
|
||||||
module VERSION #:nodoc:
|
module VERSION #:nodoc:
|
||||||
MAJOR = 2
|
MAJOR = 2
|
||||||
MINOR = 3
|
MINOR = 3
|
||||||
TINY = 3
|
TINY = 4
|
||||||
|
|
||||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||||
end
|
end
|
||||||
|
|
|
@ -98,7 +98,7 @@ module ActionView
|
||||||
options[:schema_date] = "2005" # The Atom spec copyright date
|
options[:schema_date] = "2005" # The Atom spec copyright date
|
||||||
end
|
end
|
||||||
|
|
||||||
xml = options[:xml] || eval("xml", block.binding)
|
xml = options.delete(:xml) || eval("xml", block.binding)
|
||||||
xml.instruct!
|
xml.instruct!
|
||||||
if options[:instruct]
|
if options[:instruct]
|
||||||
options[:instruct].each do |target,attrs|
|
options[:instruct].each do |target,attrs|
|
||||||
|
|
|
@ -726,6 +726,7 @@ module ActionView
|
||||||
options = options.stringify_keys
|
options = options.stringify_keys
|
||||||
tag_value = options.delete("value")
|
tag_value = options.delete("value")
|
||||||
name_and_id = options.dup
|
name_and_id = options.dup
|
||||||
|
name_and_id["id"] = name_and_id["for"]
|
||||||
add_default_name_and_id_for_value(tag_value, name_and_id)
|
add_default_name_and_id_for_value(tag_value, name_and_id)
|
||||||
options.delete("index")
|
options.delete("index")
|
||||||
options["for"] ||= name_and_id["id"]
|
options["for"] ||= name_and_id["id"]
|
||||||
|
@ -860,8 +861,8 @@ module ActionView
|
||||||
|
|
||||||
private
|
private
|
||||||
def add_default_name_and_id_for_value(tag_value, options)
|
def add_default_name_and_id_for_value(tag_value, options)
|
||||||
if tag_value
|
unless tag_value.nil?
|
||||||
pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
|
pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
|
||||||
specified_id = options["id"]
|
specified_id = options["id"]
|
||||||
add_default_name_and_id(options)
|
add_default_name_and_id(options)
|
||||||
options["id"] += "_#{pretty_tag_value}" unless specified_id
|
options["id"] += "_#{pretty_tag_value}" unless specified_id
|
||||||
|
|
|
@ -162,6 +162,60 @@ module ActionView
|
||||||
InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
|
InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Returns <tt><select></tt>, <tt><optgroup></tt> and <tt><option></tt> tags for the collection of existing return values of
|
||||||
|
# +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
|
||||||
|
# be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
|
||||||
|
# or <tt>:include_blank</tt> in the +options+ hash.
|
||||||
|
#
|
||||||
|
# Parameters:
|
||||||
|
# * +object+ - The instance of the class to be used for the select tag
|
||||||
|
# * +method+ - The attribute of +object+ corresponding to the select tag
|
||||||
|
# * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
|
||||||
|
# * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
|
||||||
|
# array of child objects representing the <tt><option></tt> tags.
|
||||||
|
# * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
|
||||||
|
# string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
|
||||||
|
# * +option_key_method+ - The name of a method which, when called on a child object of a member of
|
||||||
|
# +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
|
||||||
|
# * +option_value_method+ - The name of a method which, when called on a child object of a member of
|
||||||
|
# +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
|
||||||
|
#
|
||||||
|
# Example object structure for use with this method:
|
||||||
|
# class Continent < ActiveRecord::Base
|
||||||
|
# has_many :countries
|
||||||
|
# # attribs: id, name
|
||||||
|
# end
|
||||||
|
# class Country < ActiveRecord::Base
|
||||||
|
# belongs_to :continent
|
||||||
|
# # attribs: id, name, continent_id
|
||||||
|
# end
|
||||||
|
# class City < ActiveRecord::Base
|
||||||
|
# belongs_to :country
|
||||||
|
# # attribs: id, name, country_id
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Sample usage:
|
||||||
|
# grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name)
|
||||||
|
#
|
||||||
|
# Possible output:
|
||||||
|
# <select name="city[country_id]">
|
||||||
|
# <optgroup label="Africa">
|
||||||
|
# <option value="1">South Africa</option>
|
||||||
|
# <option value="3">Somalia</option>
|
||||||
|
# </optgroup>
|
||||||
|
# <optgroup label="Europe">
|
||||||
|
# <option value="7" selected="selected">Denmark</option>
|
||||||
|
# <option value="2">Ireland</option>
|
||||||
|
# </optgroup>
|
||||||
|
# </select>
|
||||||
|
#
|
||||||
|
def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
|
||||||
|
InstanceTag.new(object, method, self, options.delete(:object)).to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Return select and option tags for the given object and method, using
|
# Return select and option tags for the given object and method, using
|
||||||
# #time_zone_options_for_select to generate the list of option tags.
|
# #time_zone_options_for_select to generate the list of option tags.
|
||||||
#
|
#
|
||||||
|
@ -490,6 +544,15 @@ module ActionView
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
|
||||||
|
html_options = html_options.stringify_keys
|
||||||
|
add_default_name_and_id(html_options)
|
||||||
|
value = value(object)
|
||||||
|
content_tag(
|
||||||
|
"select", add_options(option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value), options, value), html_options
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def to_time_zone_select_tag(priority_zones, options, html_options)
|
def to_time_zone_select_tag(priority_zones, options, html_options)
|
||||||
html_options = html_options.stringify_keys
|
html_options = html_options.stringify_keys
|
||||||
add_default_name_and_id(html_options)
|
add_default_name_and_id(html_options)
|
||||||
|
@ -508,7 +571,8 @@ module ActionView
|
||||||
option_tags = "<option value=\"\">#{options[:include_blank] if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
|
option_tags = "<option value=\"\">#{options[:include_blank] if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
|
||||||
end
|
end
|
||||||
if value.blank? && options[:prompt]
|
if value.blank? && options[:prompt]
|
||||||
("<option value=\"\">#{options[:prompt].kind_of?(String) ? options[:prompt] : 'Please select'}</option>\n") + option_tags
|
prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('support.select.prompt', :default => 'Please select')
|
||||||
|
"<option value=\"\">#{prompt}</option>\n" + option_tags
|
||||||
else
|
else
|
||||||
option_tags
|
option_tags
|
||||||
end
|
end
|
||||||
|
@ -524,6 +588,10 @@ module ActionView
|
||||||
@template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
|
@template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
|
||||||
|
@template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options))
|
||||||
|
end
|
||||||
|
|
||||||
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
|
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
|
||||||
@template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
|
@template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
|
||||||
end
|
end
|
||||||
|
|
|
@ -103,7 +103,7 @@ module ActionView
|
||||||
# escape_once("<< Accept & Checkout")
|
# escape_once("<< Accept & Checkout")
|
||||||
# # => "<< Accept & Checkout"
|
# # => "<< Accept & Checkout"
|
||||||
def escape_once(html)
|
def escape_once(html)
|
||||||
html.to_s.gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] }
|
ActiveSupport::Multibyte.clean(html.to_s).gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] }
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -33,30 +33,31 @@ module ActionView
|
||||||
end
|
end
|
||||||
|
|
||||||
# Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt>
|
# Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt>
|
||||||
# (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...").
|
# (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...")
|
||||||
|
# for a total length not exceeding <tt>:length</tt>.
|
||||||
#
|
#
|
||||||
# ==== Examples
|
# ==== Examples
|
||||||
#
|
#
|
||||||
# truncate("Once upon a time in a world far far away")
|
# truncate("Once upon a time in a world far far away")
|
||||||
# # => Once upon a time in a world f...
|
# # => Once upon a time in a world...
|
||||||
#
|
#
|
||||||
# truncate("Once upon a time in a world far far away", :length => 14)
|
# truncate("Once upon a time in a world far far away", :length => 14)
|
||||||
# # => Once upon a...
|
# # => Once upon a...
|
||||||
#
|
#
|
||||||
# truncate("And they found that many people were sleeping better.", :length => 25, "(clipped)")
|
# truncate("And they found that many people were sleeping better.", :length => 25, "(clipped)")
|
||||||
# # => And they found that many (clipped)
|
# # => And they found t(clipped)
|
||||||
#
|
#
|
||||||
# truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 15)
|
# truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 25)
|
||||||
# # => And they found... (continued)
|
# # => And they f... (continued)
|
||||||
#
|
#
|
||||||
# You can still use <tt>truncate</tt> with the old API that accepts the
|
# You can still use <tt>truncate</tt> with the old API that accepts the
|
||||||
# +length+ as its optional second and the +ellipsis+ as its
|
# +length+ as its optional second and the +ellipsis+ as its
|
||||||
# optional third parameter:
|
# optional third parameter:
|
||||||
# truncate("Once upon a time in a world far far away", 14)
|
# truncate("Once upon a time in a world far far away", 14)
|
||||||
# # => Once upon a time in a world f...
|
# # => Once upon a...
|
||||||
#
|
#
|
||||||
# truncate("And they found that many people were sleeping better.", 15, "... (continued)")
|
# truncate("And they found that many people were sleeping better.", 25, "... (continued)")
|
||||||
# # => And they found... (continued)
|
# # => And they f... (continued)
|
||||||
def truncate(text, *args)
|
def truncate(text, *args)
|
||||||
options = args.extract_options!
|
options = args.extract_options!
|
||||||
unless args.empty?
|
unless args.empty?
|
||||||
|
@ -234,12 +235,20 @@ module ActionView
|
||||||
#
|
#
|
||||||
# textilize("Visit the Rails website "here":http://www.rubyonrails.org/.)
|
# textilize("Visit the Rails website "here":http://www.rubyonrails.org/.)
|
||||||
# # => "<p>Visit the Rails website <a href="http://www.rubyonrails.org/">here</a>.</p>"
|
# # => "<p>Visit the Rails website <a href="http://www.rubyonrails.org/">here</a>.</p>"
|
||||||
def textilize(text)
|
#
|
||||||
|
# textilize("This is worded <strong>strongly</strong>")
|
||||||
|
# # => "<p>This is worded <strong>strongly</strong></p>"
|
||||||
|
#
|
||||||
|
# textilize("This is worded <strong>strongly</strong>", :filter_html)
|
||||||
|
# # => "<p>This is worded <strong>strongly</strong></p>"
|
||||||
|
#
|
||||||
|
def textilize(text, *options)
|
||||||
|
options ||= [:hard_breaks]
|
||||||
|
|
||||||
if text.blank?
|
if text.blank?
|
||||||
""
|
""
|
||||||
else
|
else
|
||||||
textilized = RedCloth.new(text, [ :hard_breaks ])
|
textilized = RedCloth.new(text, options)
|
||||||
textilized.hard_breaks = true if textilized.respond_to?(:hard_breaks=)
|
|
||||||
textilized.to_html
|
textilized.to_html
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -568,7 +568,7 @@ module ActionView
|
||||||
when confirm && popup
|
when confirm && popup
|
||||||
"if (#{confirm_javascript_function(confirm)}) { #{popup_javascript_function(popup)} };return false;"
|
"if (#{confirm_javascript_function(confirm)}) { #{popup_javascript_function(popup)} };return false;"
|
||||||
when confirm && method
|
when confirm && method
|
||||||
"if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method)} };return false;"
|
"if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method, url, href)} };return false;"
|
||||||
when confirm
|
when confirm
|
||||||
"return #{confirm_javascript_function(confirm)};"
|
"return #{confirm_javascript_function(confirm)};"
|
||||||
when method
|
when method
|
||||||
|
|
|
@ -108,3 +108,7 @@
|
||||||
# The variable :count is also available
|
# The variable :count is also available
|
||||||
body: "There were problems with the following fields:"
|
body: "There were problems with the following fields:"
|
||||||
|
|
||||||
|
support:
|
||||||
|
select:
|
||||||
|
# default value for :prompt => true in FormOptionsHelper
|
||||||
|
prompt: "Please select"
|
16
vendor/rails/actionpack/test/abstract_unit.rb
vendored
16
vendor/rails/actionpack/test/abstract_unit.rb
vendored
|
@ -43,3 +43,19 @@ ActionController::Base.view_paths = FIXTURE_LOAD_PATH
|
||||||
CACHED_VIEW_PATHS = ActionView::Base.cache_template_loading? ?
|
CACHED_VIEW_PATHS = ActionView::Base.cache_template_loading? ?
|
||||||
ActionController::Base.view_paths :
|
ActionController::Base.view_paths :
|
||||||
ActionController::Base.view_paths.map {|path| ActionView::Template::EagerPath.new(path.to_s)}
|
ActionController::Base.view_paths.map {|path| ActionView::Template::EagerPath.new(path.to_s)}
|
||||||
|
|
||||||
|
class DummyMutex
|
||||||
|
def lock
|
||||||
|
@locked = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def unlock
|
||||||
|
@locked = false
|
||||||
|
end
|
||||||
|
|
||||||
|
def locked?
|
||||||
|
@locked
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActionController::Reloader.default_lock = DummyMutex.new
|
|
@ -52,7 +52,7 @@ class PageCachingTest < ActionController::TestCase
|
||||||
ActionController::Base.perform_caching = true
|
ActionController::Base.perform_caching = true
|
||||||
|
|
||||||
ActionController::Routing::Routes.draw do |map|
|
ActionController::Routing::Routes.draw do |map|
|
||||||
map.main '', :controller => 'posts'
|
map.main '', :controller => 'posts', :format => nil
|
||||||
map.formatted_posts 'posts.:format', :controller => 'posts'
|
map.formatted_posts 'posts.:format', :controller => 'posts'
|
||||||
map.resources :posts
|
map.resources :posts
|
||||||
map.connect ':controller/:action/:id'
|
map.connect ':controller/:action/:id'
|
||||||
|
|
|
@ -118,4 +118,10 @@ class CookieTest < ActionController::TestCase
|
||||||
get :delete_cookie_with_path
|
get :delete_cookie_with_path
|
||||||
assert_equal ["user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT"], @response.headers["Set-Cookie"]
|
assert_equal ["user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT"], @response.headers["Set-Cookie"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_cookies_persist_throughout_request
|
||||||
|
get :authenticate
|
||||||
|
cookies = @controller.send(:cookies)
|
||||||
|
assert_equal 'david', cookies['user_name']
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,25 +2,17 @@ require 'abstract_unit'
|
||||||
|
|
||||||
class DispatcherTest < Test::Unit::TestCase
|
class DispatcherTest < Test::Unit::TestCase
|
||||||
Dispatcher = ActionController::Dispatcher
|
Dispatcher = ActionController::Dispatcher
|
||||||
|
Reloader = ActionController::Reloader
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
ENV['REQUEST_METHOD'] = 'GET'
|
ENV['REQUEST_METHOD'] = 'GET'
|
||||||
|
reset_dispatcher
|
||||||
Dispatcher.middleware = ActionController::MiddlewareStack.new do |middleware|
|
|
||||||
middlewares = File.expand_path(File.join(File.dirname(__FILE__), "../../lib/action_controller/middlewares.rb"))
|
|
||||||
middleware.instance_eval(File.read(middlewares))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Clear callbacks as they are redefined by Dispatcher#define_dispatcher_callbacks
|
|
||||||
Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
|
||||||
Dispatcher.instance_variable_set("@before_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
|
||||||
Dispatcher.instance_variable_set("@after_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
|
||||||
|
|
||||||
Dispatcher.stubs(:require_dependency)
|
Dispatcher.stubs(:require_dependency)
|
||||||
end
|
end
|
||||||
|
|
||||||
def teardown
|
def teardown
|
||||||
ENV.delete 'REQUEST_METHOD'
|
ENV.delete 'REQUEST_METHOD'
|
||||||
|
reset_dispatcher
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_clears_dependencies_after_dispatch_if_in_loading_mode
|
def test_clears_dependencies_after_dispatch_if_in_loading_mode
|
||||||
|
@ -41,6 +33,34 @@ class DispatcherTest < Test::Unit::TestCase
|
||||||
dispatch
|
dispatch
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_builds_middleware_stack_only_during_initialization_if_not_in_loading_mode
|
||||||
|
dispatcher = create_dispatcher
|
||||||
|
assert_not_nil dispatcher.instance_variable_get(:"@app")
|
||||||
|
dispatcher.instance_variable_set(:"@app", lambda { |env| })
|
||||||
|
dispatcher.expects(:build_middleware_stack).never
|
||||||
|
dispatcher.call(nil)
|
||||||
|
dispatcher.call(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_rebuilds_middleware_stack_on_every_request_if_in_loading_mode
|
||||||
|
dispatcher = create_dispatcher(false)
|
||||||
|
dispatcher.instance_variable_set(:"@app", lambda { |env| })
|
||||||
|
dispatcher.expects(:build_middleware_stack).twice
|
||||||
|
dispatcher.call(nil)
|
||||||
|
Reloader.default_lock.unlock
|
||||||
|
dispatcher.call(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_doesnt_wrap_call_in_reloader_if_not_in_loading_mode
|
||||||
|
Reloader.expects(:run).never
|
||||||
|
dispatch
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_wraps_call_in_reloader_if_in_loading_mode
|
||||||
|
Reloader.expects(:run).once
|
||||||
|
dispatch(false)
|
||||||
|
end
|
||||||
|
|
||||||
# Stub out dispatch error logger
|
# Stub out dispatch error logger
|
||||||
class << Dispatcher
|
class << Dispatcher
|
||||||
def log_failsafe_exception(status, exception); end
|
def log_failsafe_exception(status, exception); end
|
||||||
|
@ -99,6 +119,25 @@ class DispatcherTest < Test::Unit::TestCase
|
||||||
Dispatcher.new.call({'rack.input' => StringIO.new('')})
|
Dispatcher.new.call({'rack.input' => StringIO.new('')})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_dispatcher(cache_classes = true)
|
||||||
|
Dispatcher.define_dispatcher_callbacks(cache_classes)
|
||||||
|
Dispatcher.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset_dispatcher
|
||||||
|
Dispatcher.middleware = ActionController::MiddlewareStack.new do |middleware|
|
||||||
|
middlewares = File.expand_path(File.join(File.dirname(__FILE__), "../../lib/action_controller/middlewares.rb"))
|
||||||
|
middleware.instance_eval(File.read(middlewares))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Clear callbacks as they are redefined by Dispatcher#define_dispatcher_callbacks
|
||||||
|
Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||||
|
Dispatcher.instance_variable_set("@before_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||||
|
Dispatcher.instance_variable_set("@after_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||||
|
|
||||||
|
Dispatcher.define_dispatcher_callbacks(true)
|
||||||
|
end
|
||||||
|
|
||||||
def assert_subclasses(howmany, klass, message = klass.subclasses.inspect)
|
def assert_subclasses(howmany, klass, message = klass.subclasses.inspect)
|
||||||
assert_equal howmany, klass.subclasses.size, message
|
assert_equal howmany, klass.subclasses.size, message
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,7 +24,8 @@ class FilterParamTest < Test::Unit::TestCase
|
||||||
[{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'],
|
[{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'],
|
||||||
[{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'],
|
[{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'],
|
||||||
[{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana'],
|
[{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana'],
|
||||||
[{'baz'=>[{'foo'=>'baz'}]}, {'baz'=>[{'foo'=>'[FILTERED]'}]}, %w(foo)]]
|
[{'baz'=>[{'foo'=>'baz'}]}, {'baz'=>[{'foo'=>'[FILTERED]'}]}, %w(foo)],
|
||||||
|
[{'baz'=>[{'foo'=>'baz'}, 1, 2, 3]}, {'baz'=>[{'foo'=>'[FILTERED]'}, 1, 2, 3]}, %w(foo)]]
|
||||||
|
|
||||||
test_hashes.each do |before_filter, after_filter, filter_words|
|
test_hashes.each do |before_filter, after_filter, filter_words|
|
||||||
FilterParamController.filter_parameter_logging(*filter_words)
|
FilterParamController.filter_parameter_logging(*filter_words)
|
||||||
|
|
|
@ -4,6 +4,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
|
||||||
class DummyController < ActionController::Base
|
class DummyController < ActionController::Base
|
||||||
before_filter :authenticate, :only => :index
|
before_filter :authenticate, :only => :index
|
||||||
before_filter :authenticate_with_request, :only => :display
|
before_filter :authenticate_with_request, :only => :display
|
||||||
|
before_filter :authenticate_long_credentials, :only => :show
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render :text => "Hello Secret"
|
render :text => "Hello Secret"
|
||||||
|
@ -13,6 +14,10 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
|
||||||
render :text => 'Definitely Maybe'
|
render :text => 'Definitely Maybe'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
render :text => 'Only for loooooong credentials'
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def authenticate
|
def authenticate
|
||||||
|
@ -28,6 +33,12 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
|
||||||
request_http_basic_authentication("SuperSecret")
|
request_http_basic_authentication("SuperSecret")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authenticate_long_credentials
|
||||||
|
authenticate_or_request_with_http_basic do |username, password|
|
||||||
|
username == '1234567890123456789012345678901234567890' && password == '1234567890123456789012345678901234567890'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION']
|
AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION']
|
||||||
|
@ -42,6 +53,13 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
|
||||||
assert_response :success
|
assert_response :success
|
||||||
assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}"
|
assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}"
|
||||||
end
|
end
|
||||||
|
test "successful authentication with #{header.downcase} and long credentials" do
|
||||||
|
@request.env[header] = encode_credentials('1234567890123456789012345678901234567890', '1234567890123456789012345678901234567890')
|
||||||
|
get :show
|
||||||
|
|
||||||
|
assert_response :success
|
||||||
|
assert_equal 'Only for loooooong credentials', @response.body, "Authentication failed for request header #{header} and long credentials"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
AUTH_HEADERS.each do |header|
|
AUTH_HEADERS.each do |header|
|
||||||
|
@ -52,6 +70,13 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
|
||||||
assert_response :unauthorized
|
assert_response :unauthorized
|
||||||
assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}"
|
assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}"
|
||||||
end
|
end
|
||||||
|
test "unsuccessful authentication with #{header.downcase} and long credentials" do
|
||||||
|
@request.env[header] = encode_credentials('h4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0r', 'worldworldworldworldworldworldworldworld')
|
||||||
|
get :show
|
||||||
|
|
||||||
|
assert_response :unauthorized
|
||||||
|
assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and long credentials"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "authentication request without credential" do
|
test "authentication request without credential" do
|
||||||
|
|
|
@ -129,7 +129,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
|
||||||
assert_equal 'Definitely Maybe', @response.body
|
assert_equal 'Definitely Maybe', @response.body
|
||||||
end
|
end
|
||||||
|
|
||||||
test "authentication request with request-uri that doesn't match credentials digest-uri" do
|
test "authentication request with request-uri that doesn't match credentials digest-uri" do
|
||||||
@request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
|
@request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
|
||||||
@request.env['REQUEST_URI'] = "/http_digest_authentication_test/dummy_digest/altered/uri"
|
@request.env['REQUEST_URI'] = "/http_digest_authentication_test/dummy_digest/altered/uri"
|
||||||
get :display
|
get :display
|
||||||
|
@ -138,10 +138,33 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
|
||||||
assert_equal "Authentication Failed", @response.body
|
assert_equal "Authentication Failed", @response.body
|
||||||
end
|
end
|
||||||
|
|
||||||
test "authentication request with absolute uri" do
|
test "authentication request with absolute request uri (as in webrick)" do
|
||||||
@request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri => "http://test.host/http_digest_authentication_test/dummy_digest/display",
|
@request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
|
||||||
|
@request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest"
|
||||||
|
|
||||||
|
get :display
|
||||||
|
|
||||||
|
assert_response :success
|
||||||
|
assert assigns(:logged_in)
|
||||||
|
assert_equal 'Definitely Maybe', @response.body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "authentication request with absolute uri in credentials (as in IE)" do
|
||||||
|
@request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest",
|
||||||
:username => 'pretty', :password => 'please')
|
:username => 'pretty', :password => 'please')
|
||||||
@request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest/display"
|
|
||||||
|
get :display
|
||||||
|
|
||||||
|
assert_response :success
|
||||||
|
assert assigns(:logged_in)
|
||||||
|
assert_equal 'Definitely Maybe', @response.body
|
||||||
|
end
|
||||||
|
|
||||||
|
test "authentication request with absolute uri in both request and credentials (as in Webrick with IE)" do
|
||||||
|
@request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest",
|
||||||
|
:username => 'pretty', :password => 'please')
|
||||||
|
@request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest"
|
||||||
|
|
||||||
get :display
|
get :display
|
||||||
|
|
||||||
assert_response :success
|
assert_response :success
|
||||||
|
@ -199,7 +222,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
|
||||||
|
|
||||||
credentials = decode_credentials(@response.headers['WWW-Authenticate'])
|
credentials = decode_credentials(@response.headers['WWW-Authenticate'])
|
||||||
credentials.merge!(options)
|
credentials.merge!(options)
|
||||||
credentials.reverse_merge!(:uri => "#{@request.env['REQUEST_URI']}")
|
credentials.merge!(:uri => @request.env['REQUEST_URI'].to_s)
|
||||||
ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1])
|
ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require 'abstract_unit'
|
require 'abstract_unit'
|
||||||
|
|
||||||
class BaseRackTest < Test::Unit::TestCase
|
class BaseRackTest < ActiveSupport::TestCase
|
||||||
def setup
|
def setup
|
||||||
@env = {
|
@env = {
|
||||||
"HTTP_MAX_FORWARDS" => "10",
|
"HTTP_MAX_FORWARDS" => "10",
|
||||||
|
@ -261,6 +261,23 @@ class RackResponseTest < BaseRackTest
|
||||||
body.each { |part| parts << part }
|
body.each { |part| parts << part }
|
||||||
assert_equal ["0", "1", "2", "3", "4"], parts
|
assert_equal ["0", "1", "2", "3", "4"], parts
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_streaming_block_with_flush_is_deprecated
|
||||||
|
@response.body = Proc.new do |response, output|
|
||||||
|
5.times do |n|
|
||||||
|
output.write(n)
|
||||||
|
output.flush
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_deprecated(/output.flush is no longer needed/) do
|
||||||
|
@response.prepare!
|
||||||
|
status, headers, body = @response.to_a
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
body.each { |part| parts << part }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class RackResponseHeadersTest < BaseRackTest
|
class RackResponseHeadersTest < BaseRackTest
|
||||||
|
|
|
@ -236,7 +236,7 @@ class RedirectTest < ActionController::TestCase
|
||||||
def test_redirect_with_partial_params
|
def test_redirect_with_partial_params
|
||||||
get :module_redirect
|
get :module_redirect
|
||||||
|
|
||||||
assert_deprecated do
|
assert_deprecated(/test_redirect_with_partial_params/) do
|
||||||
assert_redirected_to :action => 'hello_world'
|
assert_redirected_to :action => 'hello_world'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,35 +22,62 @@ class ReloaderTests < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup_and_return_body(app = lambda { })
|
def setup
|
||||||
Dispatcher.expects(:reload_application)
|
@lock = Mutex.new
|
||||||
reloader = Reloader.new(app)
|
|
||||||
headers, status, body = reloader.call({ })
|
|
||||||
body
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_it_reloads_the_application_before_the_request
|
def test_it_reloads_the_application_before_yielding
|
||||||
Dispatcher.expects(:reload_application)
|
Dispatcher.expects(:reload_application)
|
||||||
reloader = Reloader.new(lambda {
|
Reloader.run(@lock) do
|
||||||
[200, { "Content-Type" => "text/html" }, [""]]
|
[200, { "Content-Type" => "text/html" }, [""]]
|
||||||
})
|
end
|
||||||
reloader.call({ })
|
end
|
||||||
|
|
||||||
|
def test_it_locks_before_yielding
|
||||||
|
lock = DummyMutex.new
|
||||||
|
Dispatcher.expects(:reload_application)
|
||||||
|
Reloader.run(lock) do
|
||||||
|
assert lock.locked?
|
||||||
|
[200, { "Content-Type" => "text/html" }, [""]]
|
||||||
|
end
|
||||||
|
assert lock.locked?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_it_unlocks_upon_calling_close_on_body
|
||||||
|
lock = DummyMutex.new
|
||||||
|
Dispatcher.expects(:reload_application)
|
||||||
|
headers, status, body = Reloader.run(lock) do
|
||||||
|
[200, { "Content-Type" => "text/html" }, [""]]
|
||||||
|
end
|
||||||
|
body.close
|
||||||
|
assert !lock.locked?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_it_unlocks_if_app_object_raises_exception
|
||||||
|
lock = DummyMutex.new
|
||||||
|
Dispatcher.expects(:reload_application)
|
||||||
|
assert_raise(RuntimeError) do
|
||||||
|
Reloader.run(lock) do
|
||||||
|
raise "oh no!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert !lock.locked?
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_returned_body_object_always_responds_to_close
|
def test_returned_body_object_always_responds_to_close
|
||||||
body = setup_and_return_body(lambda {
|
status, headers, body = Reloader.run(@lock) do
|
||||||
[200, { "Content-Type" => "text/html" }, [""]]
|
[200, { "Content-Type" => "text/html" }, [""]]
|
||||||
})
|
end
|
||||||
assert body.respond_to?(:close)
|
assert body.respond_to?(:close)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_returned_body_object_behaves_like_underlying_object
|
def test_returned_body_object_behaves_like_underlying_object
|
||||||
body = setup_and_return_body(lambda {
|
status, headers, body = Reloader.run(@lock) do
|
||||||
b = MyBody.new
|
b = MyBody.new
|
||||||
b << "hello"
|
b << "hello"
|
||||||
b << "world"
|
b << "world"
|
||||||
[200, { "Content-Type" => "text/html" }, b]
|
[200, { "Content-Type" => "text/html" }, b]
|
||||||
})
|
end
|
||||||
assert_equal 2, body.size
|
assert_equal 2, body.size
|
||||||
assert_equal "hello", body[0]
|
assert_equal "hello", body[0]
|
||||||
assert_equal "world", body[1]
|
assert_equal "world", body[1]
|
||||||
|
@ -60,20 +87,20 @@ class ReloaderTests < ActiveSupport::TestCase
|
||||||
|
|
||||||
def test_it_calls_close_on_underlying_object_when_close_is_called_on_body
|
def test_it_calls_close_on_underlying_object_when_close_is_called_on_body
|
||||||
close_called = false
|
close_called = false
|
||||||
body = setup_and_return_body(lambda {
|
status, headers, body = Reloader.run(@lock) do
|
||||||
b = MyBody.new do
|
b = MyBody.new do
|
||||||
close_called = true
|
close_called = true
|
||||||
end
|
end
|
||||||
[200, { "Content-Type" => "text/html" }, b]
|
[200, { "Content-Type" => "text/html" }, b]
|
||||||
})
|
end
|
||||||
body.close
|
body.close
|
||||||
assert close_called
|
assert close_called
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_returned_body_object_responds_to_all_methods_supported_by_underlying_object
|
def test_returned_body_object_responds_to_all_methods_supported_by_underlying_object
|
||||||
body = setup_and_return_body(lambda {
|
status, headers, body = Reloader.run(@lock) do
|
||||||
[200, { "Content-Type" => "text/html" }, MyBody.new]
|
[200, { "Content-Type" => "text/html" }, MyBody.new]
|
||||||
})
|
end
|
||||||
assert body.respond_to?(:size)
|
assert body.respond_to?(:size)
|
||||||
assert body.respond_to?(:each)
|
assert body.respond_to?(:each)
|
||||||
assert body.respond_to?(:foo)
|
assert body.respond_to?(:foo)
|
||||||
|
@ -82,16 +109,16 @@ class ReloaderTests < ActiveSupport::TestCase
|
||||||
|
|
||||||
def test_it_doesnt_clean_up_the_application_after_call
|
def test_it_doesnt_clean_up_the_application_after_call
|
||||||
Dispatcher.expects(:cleanup_application).never
|
Dispatcher.expects(:cleanup_application).never
|
||||||
body = setup_and_return_body(lambda {
|
status, headers, body = Reloader.run(@lock) do
|
||||||
[200, { "Content-Type" => "text/html" }, MyBody.new]
|
[200, { "Content-Type" => "text/html" }, MyBody.new]
|
||||||
})
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_it_cleans_up_the_application_when_close_is_called_on_body
|
def test_it_cleans_up_the_application_when_close_is_called_on_body
|
||||||
Dispatcher.expects(:cleanup_application)
|
Dispatcher.expects(:cleanup_application)
|
||||||
body = setup_and_return_body(lambda {
|
status, headers, body = Reloader.run(@lock) do
|
||||||
[200, { "Content-Type" => "text/html" }, MyBody.new]
|
[200, { "Content-Type" => "text/html" }, MyBody.new]
|
||||||
})
|
end
|
||||||
body.close
|
body.close
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,16 +30,36 @@ class JsonParamsParsingTest < ActionController::IntegrationTest
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "logs error if parsing unsuccessful" do
|
||||||
|
with_test_routing do
|
||||||
|
begin
|
||||||
|
$stderr = StringIO.new
|
||||||
|
json = "[\"person]\": {\"name\": \"David\"}}"
|
||||||
|
post "/parse", json, {'CONTENT_TYPE' => 'application/json'}
|
||||||
|
assert_response :error
|
||||||
|
$stderr.rewind && err = $stderr.read
|
||||||
|
assert err =~ /Error occurred while parsing request parameters/
|
||||||
|
ensure
|
||||||
|
$stderr = STDERR
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def assert_parses(expected, actual, headers = {})
|
def assert_parses(expected, actual, headers = {})
|
||||||
with_routing do |set|
|
with_test_routing do
|
||||||
set.draw do |map|
|
|
||||||
map.connect ':action', :controller => "json_params_parsing_test/test"
|
|
||||||
end
|
|
||||||
|
|
||||||
post "/parse", actual, headers
|
post "/parse", actual, headers
|
||||||
assert_response :ok
|
assert_response :ok
|
||||||
assert_equal(expected, TestController.last_request_parameters)
|
assert_equal(expected, TestController.last_request_parameters)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def with_test_routing
|
||||||
|
with_routing do |set|
|
||||||
|
set.draw do |map|
|
||||||
|
map.connect ':action', :controller => "json_params_parsing_test/test"
|
||||||
|
end
|
||||||
|
yield
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,6 +38,21 @@ class XmlParamsParsingTest < ActionController::IntegrationTest
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "logs error if parsing unsuccessful" do
|
||||||
|
with_test_routing do
|
||||||
|
begin
|
||||||
|
$stderr = StringIO.new
|
||||||
|
xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar></pineapple>"
|
||||||
|
post "/parse", xml, default_headers
|
||||||
|
assert_response :error
|
||||||
|
$stderr.rewind && err = $stderr.read
|
||||||
|
assert err =~ /Error occurred while parsing request parameters/
|
||||||
|
ensure
|
||||||
|
$stderr = STDERR
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test "parses multiple files" do
|
test "parses multiple files" do
|
||||||
xml = <<-end_body
|
xml = <<-end_body
|
||||||
<person>
|
<person>
|
||||||
|
|
|
@ -155,10 +155,6 @@ module RequestForgeryProtectionTests
|
||||||
def test_should_allow_xhr_post_without_token
|
def test_should_allow_xhr_post_without_token
|
||||||
assert_nothing_raised { xhr :post, :index }
|
assert_nothing_raised { xhr :post, :index }
|
||||||
end
|
end
|
||||||
def test_should_not_allow_xhr_post_with_html_without_token
|
|
||||||
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
|
|
||||||
assert_raise(ActionController::InvalidAuthenticityToken) { xhr :post, :index }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_should_allow_xhr_put_without_token
|
def test_should_allow_xhr_put_without_token
|
||||||
assert_nothing_raised { xhr :put, :index }
|
assert_nothing_raised { xhr :put, :index }
|
||||||
|
@ -168,6 +164,11 @@ module RequestForgeryProtectionTests
|
||||||
assert_nothing_raised { xhr :delete, :index }
|
assert_nothing_raised { xhr :delete, :index }
|
||||||
end
|
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
|
def test_should_allow_post_with_token
|
||||||
post :index, :authenticity_token => @token
|
post :index, :authenticity_token => @token
|
||||||
assert_response :success
|
assert_response :success
|
||||||
|
|
|
@ -76,6 +76,50 @@ class ResourcesTest < ActionController::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_override_paths_for_member_and_collection_methods
|
||||||
|
collection_methods = { 'rss' => :get, 'reorder' => :post, 'csv' => :post }
|
||||||
|
member_methods = { 'rss' => :get, :atom => :get, :upload => :post, :fix => :post }
|
||||||
|
path_names = {:new => 'nuevo', 'rss' => 'canal', :fix => 'corrigir' }
|
||||||
|
|
||||||
|
with_restful_routing :messages,
|
||||||
|
:collection => collection_methods,
|
||||||
|
:member => member_methods,
|
||||||
|
:path_names => path_names do
|
||||||
|
|
||||||
|
assert_restful_routes_for :messages,
|
||||||
|
:collection => collection_methods,
|
||||||
|
:member => member_methods,
|
||||||
|
:path_names => path_names do |options|
|
||||||
|
member_methods.each do |action, method|
|
||||||
|
assert_recognizes(options.merge(:action => action.to_s, :id => '1'),
|
||||||
|
:path => "/messages/1/#{path_names[action] || action}",
|
||||||
|
:method => method)
|
||||||
|
end
|
||||||
|
|
||||||
|
collection_methods.each do |action, method|
|
||||||
|
assert_recognizes(options.merge(:action => action),
|
||||||
|
:path => "/messages/#{path_names[action] || action}",
|
||||||
|
:method => method)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_restful_named_routes_for :messages,
|
||||||
|
:collection => collection_methods,
|
||||||
|
:member => member_methods,
|
||||||
|
:path_names => path_names do |options|
|
||||||
|
|
||||||
|
collection_methods.keys.each do |action|
|
||||||
|
assert_named_route "/messages/#{path_names[action] || action}", "#{action}_messages_path", :action => action
|
||||||
|
end
|
||||||
|
|
||||||
|
member_methods.keys.each do |action|
|
||||||
|
assert_named_route "/messages/1/#{path_names[action] || action}", "#{action}_message_path", :action => action, :id => "1"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_override_paths_for_default_restful_actions
|
def test_override_paths_for_default_restful_actions
|
||||||
resource = ActionController::Resources::Resource.new(:messages,
|
resource = ActionController::Resources::Resource.new(:messages,
|
||||||
:path_names => {:new => 'nuevo', :edit => 'editar'})
|
:path_names => {:new => 'nuevo', :edit => 'editar'})
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
require 'abstract_unit'
|
require 'abstract_unit'
|
||||||
require 'controller/fake_controllers'
|
require 'controller/fake_controllers'
|
||||||
|
require 'action_controller/routing/route_set'
|
||||||
|
|
||||||
class MilestonesController < ActionController::Base
|
class MilestonesController < ActionController::Base
|
||||||
def index() head :ok end
|
def index() head :ok end
|
||||||
|
@ -742,7 +743,7 @@ class MockController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class LegacyRouteSetTests < Test::Unit::TestCase
|
class LegacyRouteSetTests < ActiveSupport::TestCase
|
||||||
attr_reader :rs
|
attr_reader :rs
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
|
@ -758,6 +759,10 @@ class LegacyRouteSetTests < Test::Unit::TestCase
|
||||||
@rs.clear!
|
@rs.clear!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_routes_for_controller_and_action_deprecated
|
||||||
|
assert_deprecated { @rs.routes_for_controller_and_action("controller", "action") }
|
||||||
|
end
|
||||||
|
|
||||||
def test_default_setup
|
def test_default_setup
|
||||||
@rs.draw {|m| m.connect ':controller/:action/:id' }
|
@rs.draw {|m| m.connect ':controller/:action/:id' }
|
||||||
assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content"))
|
assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content"))
|
||||||
|
@ -1605,7 +1610,7 @@ class RouteTest < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class RouteSetTest < Test::Unit::TestCase
|
class RouteSetTest < ActiveSupport::TestCase
|
||||||
def set
|
def set
|
||||||
@set ||= ROUTING::RouteSet.new
|
@set ||= ROUTING::RouteSet.new
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
# encoding: utf-8
|
||||||
require 'abstract_unit'
|
require 'abstract_unit'
|
||||||
|
|
||||||
module TestFileUtils
|
module TestFileUtils
|
||||||
def file_name() File.basename(__FILE__) end
|
def file_name() File.basename(__FILE__) end
|
||||||
def file_path() File.expand_path(__FILE__) end
|
def file_path() File.expand_path(__FILE__) end
|
||||||
def file_data() File.open(file_path, 'rb') { |f| f.read } end
|
def file_data() @data ||= File.open(file_path, 'rb') { |f| f.read } end
|
||||||
end
|
end
|
||||||
|
|
||||||
class SendFileController < ActionController::Base
|
class SendFileController < ActionController::Base
|
||||||
|
@ -15,6 +16,7 @@ class SendFileController < ActionController::Base
|
||||||
|
|
||||||
def file() send_file(file_path, options) end
|
def file() send_file(file_path, options) end
|
||||||
def data() send_data(file_data, options) end
|
def data() send_data(file_data, options) end
|
||||||
|
def multibyte_text_data() send_data("Кирилица\n祝您好運", options) end
|
||||||
|
|
||||||
def rescue_action(e) raise end
|
def rescue_action(e) raise end
|
||||||
end
|
end
|
||||||
|
@ -49,6 +51,7 @@ class SendFileTest < ActionController::TestCase
|
||||||
require 'stringio'
|
require 'stringio'
|
||||||
output = StringIO.new
|
output = StringIO.new
|
||||||
output.binmode
|
output.binmode
|
||||||
|
output.string.force_encoding(file_data.encoding) if output.string.respond_to?(:force_encoding)
|
||||||
assert_nothing_raised { response.body.call(response, output) }
|
assert_nothing_raised { response.body.call(response, output) }
|
||||||
assert_equal file_data, output.string
|
assert_equal file_data, output.string
|
||||||
end
|
end
|
||||||
|
@ -158,4 +161,11 @@ class SendFileTest < ActionController::TestCase
|
||||||
assert_equal ActionController::Base::DEFAULT_RENDER_STATUS_CODE, @response.status
|
assert_equal ActionController::Base::DEFAULT_RENDER_STATUS_CODE, @response.status
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_send_data_content_length_header
|
||||||
|
@controller.headers = {}
|
||||||
|
@controller.options = { :type => :text, :filename => 'file_with_utf8_text' }
|
||||||
|
process('multibyte_text_data')
|
||||||
|
assert_equal '29', @controller.headers['Content-Length']
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -46,6 +46,20 @@ class UrlRewriterTests < ActionController::TestCase
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_anchor_should_call_to_param
|
||||||
|
assert_equal(
|
||||||
|
'http://test.host/c/a/i#anchor',
|
||||||
|
@rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anchor'))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_anchor_should_be_cgi_escaped
|
||||||
|
assert_equal(
|
||||||
|
'http://test.host/c/a/i#anc%2Fhor',
|
||||||
|
@rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anc/hor'))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def test_overwrite_params
|
def test_overwrite_params
|
||||||
@params[:controller] = 'hi'
|
@params[:controller] = 'hi'
|
||||||
@params[:action] = 'bye'
|
@params[:action] = 'bye'
|
||||||
|
@ -110,6 +124,18 @@ class UrlWriterTests < ActionController::TestCase
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_anchor_should_call_to_param
|
||||||
|
assert_equal('/c/a#anchor',
|
||||||
|
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor'))
|
||||||
|
)
|
||||||
|
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'))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def test_default_host
|
def test_default_host
|
||||||
add_host!
|
add_host!
|
||||||
assert_equal('http://www.basecamphq.com/c/a/i',
|
assert_equal('http://www.basecamphq.com/c/a/i',
|
||||||
|
@ -304,7 +330,7 @@ class UrlWriterTests < ActionController::TestCase
|
||||||
def test_named_routes_with_nil_keys
|
def test_named_routes_with_nil_keys
|
||||||
ActionController::Routing::Routes.clear!
|
ActionController::Routing::Routes.clear!
|
||||||
ActionController::Routing::Routes.draw do |map|
|
ActionController::Routing::Routes.draw do |map|
|
||||||
map.main '', :controller => 'posts'
|
map.main '', :controller => 'posts', :format => nil
|
||||||
map.resources :posts
|
map.resources :posts
|
||||||
map.connect ':controller/:action/:id'
|
map.connect ':controller/:action/:id'
|
||||||
end
|
end
|
||||||
|
|
23
vendor/rails/actionpack/test/fixtures/public/absolute/test.css
vendored
Normal file
23
vendor/rails/actionpack/test/fixtures/public/absolute/test.css
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/* bank.css */
|
||||||
|
|
||||||
|
/* robber.css */
|
||||||
|
|
||||||
|
/* version.1.0.css */
|
||||||
|
|
||||||
|
/* bank.css */
|
||||||
|
|
||||||
|
/* bank.css */
|
||||||
|
|
||||||
|
/* robber.css */
|
||||||
|
|
||||||
|
/* version.1.0.css */
|
||||||
|
|
||||||
|
/* bank.css */
|
||||||
|
|
||||||
|
/* robber.css */
|
||||||
|
|
||||||
|
/* version.1.0.css */
|
||||||
|
|
||||||
|
/* robber.css */
|
||||||
|
|
||||||
|
/* version.1.0.css */
|
63
vendor/rails/actionpack/test/fixtures/public/absolute/test.js
vendored
Normal file
63
vendor/rails/actionpack/test/fixtures/public/absolute/test.js
vendored
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// prototype js
|
||||||
|
|
||||||
|
// effects js
|
||||||
|
|
||||||
|
// dragdrop js
|
||||||
|
|
||||||
|
// controls js
|
||||||
|
|
||||||
|
// prototype js
|
||||||
|
|
||||||
|
// effects js
|
||||||
|
|
||||||
|
// dragdrop js
|
||||||
|
|
||||||
|
// controls js
|
||||||
|
|
||||||
|
// application js
|
||||||
|
|
||||||
|
// bank js
|
||||||
|
|
||||||
|
// robber js
|
||||||
|
|
||||||
|
// version.1.0 js
|
||||||
|
|
||||||
|
// application js
|
||||||
|
|
||||||
|
// bank js
|
||||||
|
|
||||||
|
// prototype js
|
||||||
|
|
||||||
|
// effects js
|
||||||
|
|
||||||
|
// dragdrop js
|
||||||
|
|
||||||
|
// controls js
|
||||||
|
|
||||||
|
// prototype js
|
||||||
|
|
||||||
|
// effects js
|
||||||
|
|
||||||
|
// dragdrop js
|
||||||
|
|
||||||
|
// controls js
|
||||||
|
|
||||||
|
// application js
|
||||||
|
|
||||||
|
// bank js
|
||||||
|
|
||||||
|
// robber js
|
||||||
|
|
||||||
|
// version.1.0 js
|
||||||
|
|
||||||
|
// application js
|
||||||
|
|
||||||
|
// bank js
|
||||||
|
|
||||||
|
// robber js
|
||||||
|
|
||||||
|
// version.1.0 js
|
||||||
|
|
||||||
|
// robber js
|
||||||
|
|
||||||
|
// version.1.0 js
|
|
@ -150,6 +150,26 @@ class ScrollsController < ActionController::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
EOT
|
EOT
|
||||||
|
FEEDS["provide_builder"] = <<-'EOT'
|
||||||
|
# we pass in the new_xml to the helper so it doesn't
|
||||||
|
# call anything on the original builder
|
||||||
|
new_xml = Builder::XmlMarkup.new(:target=>'')
|
||||||
|
atom_feed(:xml => new_xml) do |feed|
|
||||||
|
feed.title("My great blog!")
|
||||||
|
feed.updated((@scrolls.first.created_at))
|
||||||
|
|
||||||
|
for scroll in @scrolls
|
||||||
|
feed.entry(scroll) do |entry|
|
||||||
|
entry.title(scroll.title)
|
||||||
|
entry.content(scroll.body, :type => 'html')
|
||||||
|
|
||||||
|
entry.author do |author|
|
||||||
|
author.name("DHH")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
EOT
|
||||||
def index
|
def index
|
||||||
@scrolls = [
|
@scrolls = [
|
||||||
Scroll.new(1, "1", "Hello One", "Something <i>COOL!</i>", Time.utc(2007, 12, 12, 15), Time.utc(2007, 12, 12, 15)),
|
Scroll.new(1, "1", "Hello One", "Something <i>COOL!</i>", Time.utc(2007, 12, 12, 15), Time.utc(2007, 12, 12, 15)),
|
||||||
|
@ -194,6 +214,15 @@ class AtomFeedTest < ActionController::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_providing_builder_to_atom_feed
|
||||||
|
with_restful_routing(:scrolls) do
|
||||||
|
get :index, :id=>"provide_builder"
|
||||||
|
# because we pass in the non-default builder, the content generated by the
|
||||||
|
# helper should go 'nowhere'. Leaving the response body blank.
|
||||||
|
assert @response.body.blank?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_entry_with_prefilled_options_should_use_those_instead_of_querying_the_record
|
def test_entry_with_prefilled_options_should_use_those_instead_of_querying_the_record
|
||||||
with_restful_routing(:scrolls) do
|
with_restful_routing(:scrolls) do
|
||||||
get :index, :id => "entry_options"
|
get :index, :id => "entry_options"
|
||||||
|
|
|
@ -145,6 +145,22 @@ class FormHelperTest < ActionView::TestCase
|
||||||
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, "for" => "my_for"))
|
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, "for" => "my_for"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_label_with_id_attribute_as_symbol
|
||||||
|
assert_dom_equal('<label for="post_title" id="my_id">Title</label>', label(:post, :title, nil, :id => "my_id"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_label_with_id_attribute_as_string
|
||||||
|
assert_dom_equal('<label for="post_title" id="my_id">Title</label>', label(:post, :title, nil, "id" => "my_id"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_label_with_for_and_id_attributes_as_symbol
|
||||||
|
assert_dom_equal('<label for="my_for" id="my_id">Title</label>', label(:post, :title, nil, :for => "my_for", :id => "my_id"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_label_with_for_and_id_attributes_as_string
|
||||||
|
assert_dom_equal('<label for="my_for" id="my_id">Title</label>', label(:post, :title, nil, "for" => "my_for", "id" => "my_id"))
|
||||||
|
end
|
||||||
|
|
||||||
def test_label_for_radio_buttons_with_value
|
def test_label_for_radio_buttons_with_value
|
||||||
assert_dom_equal('<label for="post_title_great_title">The title goes here</label>', label("post", "title", "The title goes here", :value => "great_title"))
|
assert_dom_equal('<label for="post_title_great_title">The title goes here</label>', label("post", "title", "The title goes here", :value => "great_title"))
|
||||||
assert_dom_equal('<label for="post_title_great_title">The title goes here</label>', label("post", "title", "The title goes here", :value => "great title"))
|
assert_dom_equal('<label for="post_title_great_title">The title goes here</label>', label("post", "title", "The title goes here", :value => "great title"))
|
||||||
|
@ -295,6 +311,16 @@ class FormHelperTest < ActionView::TestCase
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_radio_button_with_booleans
|
||||||
|
assert_dom_equal('<input id="post_secret_true" name="post[secret]" type="radio" value="true" />',
|
||||||
|
radio_button("post", "secret", true)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_dom_equal('<input id="post_secret_false" name="post[secret]" type="radio" value="false" />',
|
||||||
|
radio_button("post", "secret", false)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def test_text_area
|
def test_text_area
|
||||||
assert_dom_equal(
|
assert_dom_equal(
|
||||||
'<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
|
'<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
|
||||||
|
|
27
vendor/rails/actionpack/test/template/form_options_helper_i18n_test.rb
vendored
Normal file
27
vendor/rails/actionpack/test/template/form_options_helper_i18n_test.rb
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
require 'abstract_unit'
|
||||||
|
|
||||||
|
class FormOptionsHelperI18nTests < ActionView::TestCase
|
||||||
|
tests ActionView::Helpers::FormOptionsHelper
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@prompt_message = 'Select!'
|
||||||
|
I18n.backend.send(:init_translations)
|
||||||
|
I18n.backend.store_translations :en, :support => { :select => { :prompt => @prompt_message } }
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
I18n.backend = I18n::Backend::Simple.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_select_with_prompt_true_translates_prompt_message
|
||||||
|
I18n.expects(:translate).with('support.select.prompt', { :default => 'Please select' })
|
||||||
|
select('post', 'category', [], :prompt => true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_select_with_translated_prompt
|
||||||
|
assert_dom_equal(
|
||||||
|
%Q(<select id="post_category" name="post[category]"><option value="">#{@prompt_message}</option>\n</select>),
|
||||||
|
select('post', 'category', [], :prompt => true)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -763,6 +763,40 @@ class FormOptionsHelperTest < ActionView::TestCase
|
||||||
html
|
html
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_grouped_collection_select
|
||||||
|
@continents = [
|
||||||
|
Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
|
||||||
|
Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
|
||||||
|
]
|
||||||
|
|
||||||
|
@post = Post.new
|
||||||
|
@post.origin = 'dk'
|
||||||
|
|
||||||
|
assert_dom_equal(
|
||||||
|
%Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk" selected="selected">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>},
|
||||||
|
grouped_collection_select("post", "origin", @continents, :countries, :continent_name, :country_id, :country_name)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_grouped_collection_select_under_fields_for
|
||||||
|
@continents = [
|
||||||
|
Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
|
||||||
|
Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
|
||||||
|
]
|
||||||
|
|
||||||
|
@post = Post.new
|
||||||
|
@post.origin = 'dk'
|
||||||
|
|
||||||
|
fields_for :post, @post do |f|
|
||||||
|
concat f.grouped_collection_select("origin", @continents, :countries, :continent_name, :country_id, :country_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_dom_equal(
|
||||||
|
%Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk" selected="selected">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>},
|
||||||
|
output_buffer
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def dummy_posts
|
def dummy_posts
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
require 'abstract_unit'
|
require 'abstract_unit'
|
||||||
require 'testing_sandbox'
|
require 'testing_sandbox'
|
||||||
|
begin
|
||||||
|
require 'redcloth'
|
||||||
|
rescue LoadError
|
||||||
|
$stderr.puts "Skipping textilize tests. `gem install RedCloth` to enable."
|
||||||
|
end
|
||||||
|
|
||||||
class TextHelperTest < ActionView::TestCase
|
class TextHelperTest < ActionView::TestCase
|
||||||
tests ActionView::Helpers::TextHelper
|
tests ActionView::Helpers::TextHelper
|
||||||
|
@ -517,4 +522,22 @@ class TextHelperTest < ActionView::TestCase
|
||||||
assert_equal("red", cycle("red", "blue"))
|
assert_equal("red", cycle("red", "blue"))
|
||||||
assert_equal(%w{Specialized Fuji Giant}, @cycles)
|
assert_equal(%w{Specialized Fuji Giant}, @cycles)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if defined? RedCloth
|
||||||
|
def test_textilize
|
||||||
|
assert_equal("<p><strong>This is Textile!</strong> Rejoice!</p>", textilize("*This is Textile!* Rejoice!"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_textilize_with_blank
|
||||||
|
assert_equal("", textilize(""))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_textilize_with_options
|
||||||
|
assert_equal("<p>This is worded <strong>strongly</strong></p>", textilize("This is worded <strong>strongly</strong>", :filter_html))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_textilize_with_hard_breaks
|
||||||
|
assert_equal("<p>This is one scary world.<br />\n True.</p>", textilize("This is one scary world.\n True."))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -219,6 +219,14 @@ class UrlHelperTest < ActionView::TestCase
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_link_tag_using_delete_javascript_and_href_and_confirm
|
||||||
|
assert_dom_equal(
|
||||||
|
"<a href='\#' onclick=\"if (confirm('Are you serious?')) { var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = 'http://www.example.com';var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); m.setAttribute('value', 'delete'); f.appendChild(m);f.submit(); };return false;\">Destroy</a>",
|
||||||
|
link_to("Destroy", "http://www.example.com", :method => :delete, :href => '#', :confirm => "Are you serious?"),
|
||||||
|
"When specifying url, form should be generated with it, but not this.href"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def test_link_tag_using_post_javascript_and_popup
|
def test_link_tag_using_post_javascript_and_popup
|
||||||
assert_raise(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") }
|
assert_raise(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") }
|
||||||
end
|
end
|
||||||
|
|
7
vendor/rails/activerecord/CHANGELOG
vendored
7
vendor/rails/activerecord/CHANGELOG
vendored
|
@ -1,3 +1,10 @@
|
||||||
|
*2.3.4 (September 4, 2009)*
|
||||||
|
|
||||||
|
* PostgreSQL: XML datatype support. #1874 [Leonardo Borges]
|
||||||
|
|
||||||
|
* SQLite: deprecate the 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper]
|
||||||
|
|
||||||
|
|
||||||
*2.3.3 (July 12, 2009)*
|
*2.3.3 (July 12, 2009)*
|
||||||
|
|
||||||
* Added :primary_key option to belongs_to associations. #765 [Szymon Nowak, Philip Hallstrom, Noel Rocha]
|
* Added :primary_key option to belongs_to associations. #765 [Szymon Nowak, Philip Hallstrom, Noel Rocha]
|
||||||
|
|
32
vendor/rails/activerecord/Rakefile
vendored
32
vendor/rails/activerecord/Rakefile
vendored
|
@ -24,14 +24,30 @@ PKG_FILES = FileList[
|
||||||
"lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "Rakefile"
|
"lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "Rakefile"
|
||||||
].exclude(/\bCVS\b|~$/)
|
].exclude(/\bCVS\b|~$/)
|
||||||
|
|
||||||
|
def run_without_aborting(*tasks)
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
tasks.each do |task|
|
||||||
|
begin
|
||||||
|
Rake::Task[task].invoke
|
||||||
|
rescue Exception
|
||||||
|
errors << task
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
abort "Errors running #{errors.join(', ')}" if errors.any?
|
||||||
|
end
|
||||||
|
|
||||||
desc 'Run mysql, sqlite, and postgresql tests by default'
|
desc 'Run mysql, sqlite, and postgresql tests by default'
|
||||||
task :default => :test
|
task :default => :test
|
||||||
|
|
||||||
desc 'Run mysql, sqlite, and postgresql tests'
|
desc 'Run mysql, sqlite, and postgresql tests'
|
||||||
task :test => defined?(JRUBY_VERSION) ?
|
task :test do
|
||||||
%w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
|
tasks = defined?(JRUBY_VERSION) ?
|
||||||
%w(test_mysql test_sqlite3 test_postgresql)
|
%w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
|
||||||
|
%w(test_mysql test_sqlite3 test_postgresql)
|
||||||
|
run_without_aborting(*tasks)
|
||||||
|
end
|
||||||
|
|
||||||
for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb )
|
for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb )
|
||||||
Rake::TestTask.new("test_#{adapter}") { |t|
|
Rake::TestTask.new("test_#{adapter}") { |t|
|
||||||
|
@ -53,8 +69,8 @@ end
|
||||||
namespace :mysql do
|
namespace :mysql do
|
||||||
desc 'Build the MySQL test databases'
|
desc 'Build the MySQL test databases'
|
||||||
task :build_databases do
|
task :build_databases do
|
||||||
%x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest )
|
%x( echo "create DATABASE activerecord_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER})
|
||||||
%x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest2 )
|
%x( echo "create DATABASE activerecord_unittest2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER})
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Drop the MySQL test databases'
|
desc 'Drop the MySQL test databases'
|
||||||
|
@ -75,8 +91,8 @@ task :rebuild_mysql_databases => 'mysql:rebuild_databases'
|
||||||
namespace :postgresql do
|
namespace :postgresql do
|
||||||
desc 'Build the PostgreSQL test databases'
|
desc 'Build the PostgreSQL test databases'
|
||||||
task :build_databases do
|
task :build_databases do
|
||||||
%x( createdb activerecord_unittest )
|
%x( createdb -E UTF8 activerecord_unittest )
|
||||||
%x( createdb activerecord_unittest2 )
|
%x( createdb -E UTF8 activerecord_unittest2 )
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Drop the PostgreSQL test databases'
|
desc 'Drop the PostgreSQL test databases'
|
||||||
|
@ -176,7 +192,7 @@ spec = Gem::Specification.new do |s|
|
||||||
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
||||||
end
|
end
|
||||||
|
|
||||||
s.add_dependency('activesupport', '= 2.3.3' + PKG_BUILD)
|
s.add_dependency('activesupport', '= 2.3.4' + PKG_BUILD)
|
||||||
|
|
||||||
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
|
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
|
||||||
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
|
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
|
||||||
|
|
1
vendor/rails/activerecord/examples/.gitignore
vendored
Normal file
1
vendor/rails/activerecord/examples/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
performance.sql
|
162
vendor/rails/activerecord/examples/performance.rb
vendored
Executable file
162
vendor/rails/activerecord/examples/performance.rb
vendored
Executable file
|
@ -0,0 +1,162 @@
|
||||||
|
#!/usr/bin/env ruby -KU
|
||||||
|
|
||||||
|
TIMES = (ENV['N'] || 10000).to_i
|
||||||
|
|
||||||
|
require 'rubygems'
|
||||||
|
gem 'addressable', '~>2.0'
|
||||||
|
gem 'faker', '~>0.3.1'
|
||||||
|
gem 'rbench', '~>0.2.3'
|
||||||
|
require 'addressable/uri'
|
||||||
|
require 'faker'
|
||||||
|
require 'rbench'
|
||||||
|
|
||||||
|
__DIR__ = File.dirname(__FILE__)
|
||||||
|
$:.unshift "#{__DIR__}/../lib"
|
||||||
|
require 'active_record'
|
||||||
|
|
||||||
|
conn = { :adapter => 'mysql',
|
||||||
|
:database => 'activerecord_unittest',
|
||||||
|
:username => 'rails', :password => '',
|
||||||
|
:encoding => 'utf8' }
|
||||||
|
|
||||||
|
conn[:socket] = Pathname.glob(%w[
|
||||||
|
/opt/local/var/run/mysql5/mysqld.sock
|
||||||
|
/tmp/mysqld.sock
|
||||||
|
/tmp/mysql.sock
|
||||||
|
/var/mysql/mysql.sock
|
||||||
|
/var/run/mysqld/mysqld.sock
|
||||||
|
]).find { |path| path.socket? }
|
||||||
|
|
||||||
|
ActiveRecord::Base.establish_connection(conn)
|
||||||
|
|
||||||
|
class User < ActiveRecord::Base
|
||||||
|
connection.create_table :users, :force => true do |t|
|
||||||
|
t.string :name, :email
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
has_many :exhibits
|
||||||
|
end
|
||||||
|
|
||||||
|
class Exhibit < ActiveRecord::Base
|
||||||
|
connection.create_table :exhibits, :force => true do |t|
|
||||||
|
t.belongs_to :user
|
||||||
|
t.string :name
|
||||||
|
t.text :notes
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
belongs_to :user
|
||||||
|
|
||||||
|
def look; attributes end
|
||||||
|
def feel; look; user.name end
|
||||||
|
|
||||||
|
def self.look(exhibits) exhibits.each { |e| e.look } end
|
||||||
|
def self.feel(exhibits) exhibits.each { |e| e.feel } end
|
||||||
|
end
|
||||||
|
|
||||||
|
sqlfile = "#{__DIR__}/performance.sql"
|
||||||
|
|
||||||
|
if File.exists?(sqlfile)
|
||||||
|
mysql_bin = %w[mysql mysql5].select { |bin| `which #{bin}`.length > 0 }
|
||||||
|
`#{mysql_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} < #{sqlfile}`
|
||||||
|
else
|
||||||
|
puts 'Generating data...'
|
||||||
|
|
||||||
|
# pre-compute the insert statements and fake data compilation,
|
||||||
|
# so the benchmarks below show the actual runtime for the execute
|
||||||
|
# method, minus the setup steps
|
||||||
|
|
||||||
|
# Using the same paragraph for all exhibits because it is very slow
|
||||||
|
# to generate unique paragraphs for all exhibits.
|
||||||
|
notes = Faker::Lorem.paragraphs.join($/)
|
||||||
|
today = Date.today
|
||||||
|
|
||||||
|
puts 'Inserting 10,000 users and exhibits...'
|
||||||
|
10_000.times do
|
||||||
|
user = User.create(
|
||||||
|
:created_at => today,
|
||||||
|
:name => Faker::Name.name,
|
||||||
|
:email => Faker::Internet.email
|
||||||
|
)
|
||||||
|
|
||||||
|
Exhibit.create(
|
||||||
|
:created_at => today,
|
||||||
|
:name => Faker::Company.name,
|
||||||
|
:user => user,
|
||||||
|
:notes => notes
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
mysqldump_bin = %w[mysqldump mysqldump5].select { |bin| `which #{bin}`.length > 0 }
|
||||||
|
`#{mysqldump_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} exhibits users > #{sqlfile}`
|
||||||
|
end
|
||||||
|
|
||||||
|
RBench.run(TIMES) do
|
||||||
|
column :times
|
||||||
|
column :ar
|
||||||
|
|
||||||
|
report 'Model#id', (TIMES * 100).ceil do
|
||||||
|
ar_obj = Exhibit.find(1)
|
||||||
|
|
||||||
|
ar { ar_obj.id }
|
||||||
|
end
|
||||||
|
|
||||||
|
report 'Model.new (instantiation)' do
|
||||||
|
ar { Exhibit.new }
|
||||||
|
end
|
||||||
|
|
||||||
|
report 'Model.new (setting attributes)' do
|
||||||
|
attrs = { :name => 'sam' }
|
||||||
|
ar { Exhibit.new(attrs) }
|
||||||
|
end
|
||||||
|
|
||||||
|
report 'Model.first' do
|
||||||
|
ar { Exhibit.first.look }
|
||||||
|
end
|
||||||
|
|
||||||
|
report 'Model.all limit(100)', (TIMES / 10).ceil do
|
||||||
|
ar { Exhibit.look Exhibit.all(:limit => 100) }
|
||||||
|
end
|
||||||
|
|
||||||
|
report 'Model.all limit(100) with relationship', (TIMES / 10).ceil do
|
||||||
|
ar { Exhibit.feel Exhibit.all(:limit => 100, :include => :user) }
|
||||||
|
end
|
||||||
|
|
||||||
|
report 'Model.all limit(10,000)', (TIMES / 1000).ceil do
|
||||||
|
ar { Exhibit.look Exhibit.all(:limit => 10000) }
|
||||||
|
end
|
||||||
|
|
||||||
|
exhibit = {
|
||||||
|
:name => Faker::Company.name,
|
||||||
|
:notes => Faker::Lorem.paragraphs.join($/),
|
||||||
|
:created_at => Date.today
|
||||||
|
}
|
||||||
|
|
||||||
|
report 'Model.create' do
|
||||||
|
ar { Exhibit.create(exhibit) }
|
||||||
|
end
|
||||||
|
|
||||||
|
report 'Resource#attributes=' do
|
||||||
|
attrs_first = { :name => 'sam' }
|
||||||
|
attrs_second = { :name => 'tom' }
|
||||||
|
ar { exhibit = Exhibit.new(attrs_first); exhibit.attributes = attrs_second }
|
||||||
|
end
|
||||||
|
|
||||||
|
report 'Resource#update' do
|
||||||
|
ar { Exhibit.first.update_attributes(:name => 'bob') }
|
||||||
|
end
|
||||||
|
|
||||||
|
report 'Resource#destroy' do
|
||||||
|
ar { Exhibit.first.destroy }
|
||||||
|
end
|
||||||
|
|
||||||
|
report 'Model.transaction' do
|
||||||
|
ar { Exhibit.transaction { Exhibit.new } }
|
||||||
|
end
|
||||||
|
|
||||||
|
summary 'Total'
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveRecord::Migration.drop_table "exhibits"
|
||||||
|
ActiveRecord::Migration.drop_table "users"
|
|
@ -34,11 +34,13 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class HasManyThroughCantAssociateThroughHasManyReflection < ActiveRecordError #:nodoc:
|
class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
|
||||||
def initialize(owner, reflection)
|
def initialize(owner, reflection)
|
||||||
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
|
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
HasManyThroughCantAssociateThroughHasManyReflection = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection', 'ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection')
|
||||||
|
|
||||||
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
|
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
|
||||||
def initialize(owner, reflection)
|
def initialize(owner, reflection)
|
||||||
super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
|
super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
|
||||||
|
@ -410,6 +412,32 @@ module ActiveRecord
|
||||||
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
|
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
|
||||||
# @firm.invoices # selects all invoices by going through the Client join model.
|
# @firm.invoices # selects all invoices by going through the Client join model.
|
||||||
#
|
#
|
||||||
|
# Similarly you can go through a +has_one+ association on the join model:
|
||||||
|
#
|
||||||
|
# class Group < ActiveRecord::Base
|
||||||
|
# has_many :users
|
||||||
|
# has_many :avatars, :through => :users
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# class User < ActiveRecord::Base
|
||||||
|
# belongs_to :group
|
||||||
|
# has_one :avatar
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# class Avatar < ActiveRecord::Base
|
||||||
|
# belongs_to :user
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# @group = Group.first
|
||||||
|
# @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
|
||||||
|
# @group.avatars # selects all avatars by going through the User join model.
|
||||||
|
#
|
||||||
|
# An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are
|
||||||
|
# *read-only*. For example, the following would not work following the previous example:
|
||||||
|
#
|
||||||
|
# @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around.
|
||||||
|
# @group.avatars.delete(@group.avatars.last) # so would this
|
||||||
|
#
|
||||||
# === Polymorphic Associations
|
# === Polymorphic Associations
|
||||||
#
|
#
|
||||||
# Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
|
# Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
|
||||||
|
@ -759,7 +787,7 @@ module ActiveRecord
|
||||||
# [:through]
|
# [:through]
|
||||||
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
|
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
|
||||||
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
|
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
|
||||||
# or <tt>has_many</tt> association on the join model.
|
# <tt>has_one</tt> or <tt>has_many</tt> association on the join model.
|
||||||
# [:source]
|
# [:source]
|
||||||
# Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
|
# Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
|
||||||
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
|
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
|
||||||
|
@ -1241,7 +1269,11 @@ module ActiveRecord
|
||||||
|
|
||||||
if association_proxy_class == HasOneThroughAssociation
|
if association_proxy_class == HasOneThroughAssociation
|
||||||
association.create_through_record(new_value)
|
association.create_through_record(new_value)
|
||||||
self.send(reflection.name, new_value)
|
if new_record?
|
||||||
|
association_instance_set(reflection.name, new_value.nil? ? nil : association)
|
||||||
|
else
|
||||||
|
self.send(reflection.name, new_value)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
association.replace(new_value)
|
association.replace(new_value)
|
||||||
association_instance_set(reflection.name, new_value.nil? ? nil : association)
|
association_instance_set(reflection.name, new_value.nil? ? nil : association)
|
||||||
|
@ -1293,7 +1325,7 @@ module ActiveRecord
|
||||||
|
|
||||||
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
|
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
|
||||||
ids = (new_value || []).reject { |nid| nid.blank? }
|
ids = (new_value || []).reject { |nid| nid.blank? }
|
||||||
send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
|
send("#{reflection.name}=", reflection.klass.find(ids))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1838,7 +1870,7 @@ module ActiveRecord
|
||||||
descendant
|
descendant
|
||||||
end.flatten.compact
|
end.flatten.compact
|
||||||
|
|
||||||
remove_duplicate_results!(reflection.class_name.constantize, parent_records, associations[name]) unless parent_records.empty?
|
remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -208,6 +208,7 @@ module ActiveRecord
|
||||||
# Note that this method will _always_ remove records from the database
|
# Note that this method will _always_ remove records from the database
|
||||||
# ignoring the +:dependent+ option.
|
# ignoring the +:dependent+ option.
|
||||||
def destroy(*records)
|
def destroy(*records)
|
||||||
|
records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)}
|
||||||
remove_records(records) do |records, old_records|
|
remove_records(records) do |records, old_records|
|
||||||
old_records.each { |record| record.destroy }
|
old_records.each { |record| record.destroy }
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
module ActiveRecord
|
module ActiveRecord
|
||||||
module Associations
|
module Associations
|
||||||
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
|
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
|
||||||
|
def initialize(owner, reflection)
|
||||||
|
super
|
||||||
|
@primary_key_list = {}
|
||||||
|
end
|
||||||
|
|
||||||
def create(attributes = {})
|
def create(attributes = {})
|
||||||
create_record(attributes) { |record| insert_record(record) }
|
create_record(attributes) { |record| insert_record(record) }
|
||||||
end
|
end
|
||||||
|
@ -17,6 +22,12 @@ module ActiveRecord
|
||||||
@reflection.reset_column_information
|
@reflection.reset_column_information
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def has_primary_key?
|
||||||
|
return @has_primary_key unless @has_primary_key.nil?
|
||||||
|
@has_primary_key = (ActiveRecord::Base.connection.supports_primary_key? &&
|
||||||
|
ActiveRecord::Base.connection.primary_key(@reflection.options[:join_table]))
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
def construct_find_options!(options)
|
def construct_find_options!(options)
|
||||||
options[:joins] = @join_sql
|
options[:joins] = @join_sql
|
||||||
|
@ -29,6 +40,11 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def insert_record(record, force = true, validate = true)
|
def insert_record(record, force = true, validate = true)
|
||||||
|
if has_primary_key?
|
||||||
|
raise ActiveRecord::ConfigurationError,
|
||||||
|
"Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})."
|
||||||
|
end
|
||||||
|
|
||||||
if record.new_record?
|
if record.new_record?
|
||||||
if force
|
if force
|
||||||
record.save!
|
record.save!
|
||||||
|
|
|
@ -74,6 +74,7 @@ module ActiveRecord
|
||||||
"#{@reflection.primary_key_name} = NULL",
|
"#{@reflection.primary_key_name} = NULL",
|
||||||
"#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
|
"#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
|
||||||
)
|
)
|
||||||
|
@owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,17 @@ module ActiveRecord
|
||||||
|
|
||||||
def create(attrs = nil)
|
def create(attrs = nil)
|
||||||
transaction do
|
transaction do
|
||||||
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association)
|
object = if attrs
|
||||||
|
@reflection.klass.send(:with_scope, :create => attrs) {
|
||||||
|
@reflection.create_association
|
||||||
|
}
|
||||||
|
else
|
||||||
|
@reflection.create_association
|
||||||
|
end
|
||||||
|
raise_on_type_mismatch(object)
|
||||||
|
add_record_to_target_with_callbacks(object) do |r|
|
||||||
|
insert_record(object, false)
|
||||||
|
end
|
||||||
object
|
object
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -44,7 +54,7 @@ module ActiveRecord
|
||||||
options[:select] = construct_select(options[:select])
|
options[:select] = construct_select(options[:select])
|
||||||
options[:from] ||= construct_from
|
options[:from] ||= construct_from
|
||||||
options[:joins] = construct_joins(options[:joins])
|
options[:joins] = construct_joins(options[:joins])
|
||||||
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
|
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include]
|
||||||
end
|
end
|
||||||
|
|
||||||
def insert_record(record, force = true, validate = true)
|
def insert_record(record, force = true, validate = true)
|
||||||
|
@ -96,7 +106,7 @@ module ActiveRecord
|
||||||
# Construct attributes for :through pointing to owner and associate.
|
# Construct attributes for :through pointing to owner and associate.
|
||||||
def construct_join_attributes(associate)
|
def construct_join_attributes(associate)
|
||||||
# TODO: revist this to allow it for deletion, supposing dependent option is supported
|
# TODO: revist this to allow it for deletion, supposing dependent option is supported
|
||||||
raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many
|
raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
|
||||||
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
|
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
|
||||||
if @reflection.options[:source_type]
|
if @reflection.options[:source_type]
|
||||||
join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
|
join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
|
||||||
|
|
|
@ -9,8 +9,14 @@ module ActiveRecord
|
||||||
|
|
||||||
if current_object
|
if current_object
|
||||||
new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
|
new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
|
||||||
else
|
elsif new_value
|
||||||
@owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value))) if new_value
|
if @owner.new_record?
|
||||||
|
self.target = new_value
|
||||||
|
through_association = @owner.send(:association_instance_get, @reflection.through_reflection.name)
|
||||||
|
through_association.build(construct_join_attributes(new_value))
|
||||||
|
else
|
||||||
|
@owner.send(@reflection.through_reflection.name, klass.create(construct_join_attributes(new_value)))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -249,9 +249,10 @@ module ActiveRecord
|
||||||
unless valid = association.valid?
|
unless valid = association.valid?
|
||||||
if reflection.options[:autosave]
|
if reflection.options[:autosave]
|
||||||
unless association.marked_for_destruction?
|
unless association.marked_for_destruction?
|
||||||
association.errors.each do |attribute, message|
|
association.errors.each_error do |attribute, error|
|
||||||
attribute = "#{reflection.name}_#{attribute}"
|
error = error.dup
|
||||||
errors.add(attribute, message) unless errors.on(attribute)
|
error.attribute = "#{reflection.name}_#{attribute}"
|
||||||
|
errors.add(error) unless errors.on(error.attribute)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
|
@ -1364,7 +1364,7 @@ module ActiveRecord #:nodoc:
|
||||||
end
|
end
|
||||||
defaults << options[:default] if options[:default]
|
defaults << options[:default] if options[:default]
|
||||||
defaults.flatten!
|
defaults.flatten!
|
||||||
defaults << attribute_key_name.humanize
|
defaults << attribute_key_name.to_s.humanize
|
||||||
options[:count] ||= 1
|
options[:count] ||= 1
|
||||||
I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes]))
|
I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes]))
|
||||||
end
|
end
|
||||||
|
@ -2294,20 +2294,24 @@ module ActiveRecord #:nodoc:
|
||||||
# And for value objects on a composed_of relationship:
|
# And for value objects on a composed_of relationship:
|
||||||
# { :address => Address.new("123 abc st.", "chicago") }
|
# { :address => Address.new("123 abc st.", "chicago") }
|
||||||
# # => "address_street='123 abc st.' and address_city='chicago'"
|
# # => "address_street='123 abc st.' and address_city='chicago'"
|
||||||
def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name)
|
def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_table_name)
|
||||||
attrs = expand_hash_conditions_for_aggregates(attrs)
|
attrs = expand_hash_conditions_for_aggregates(attrs)
|
||||||
|
|
||||||
conditions = attrs.map do |attr, value|
|
conditions = attrs.map do |attr, value|
|
||||||
|
table_name = default_table_name
|
||||||
|
|
||||||
unless value.is_a?(Hash)
|
unless value.is_a?(Hash)
|
||||||
attr = attr.to_s
|
attr = attr.to_s
|
||||||
|
|
||||||
# Extract table name from qualified attribute names.
|
# Extract table name from qualified attribute names.
|
||||||
if attr.include?('.')
|
if attr.include?('.')
|
||||||
table_name, attr = attr.split('.', 2)
|
attr_table_name, attr = attr.split('.', 2)
|
||||||
table_name = connection.quote_table_name(table_name)
|
attr_table_name = connection.quote_table_name(attr_table_name)
|
||||||
|
else
|
||||||
|
attr_table_name = table_name
|
||||||
end
|
end
|
||||||
|
|
||||||
attribute_condition("#{table_name}.#{connection.quote_column_name(attr)}", value)
|
attribute_condition("#{attr_table_name}.#{connection.quote_column_name(attr)}", value)
|
||||||
else
|
else
|
||||||
sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
|
sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
|
||||||
end
|
end
|
||||||
|
@ -3028,16 +3032,22 @@ module ActiveRecord #:nodoc:
|
||||||
|
|
||||||
def execute_callstack_for_multiparameter_attributes(callstack)
|
def execute_callstack_for_multiparameter_attributes(callstack)
|
||||||
errors = []
|
errors = []
|
||||||
callstack.each do |name, values|
|
callstack.each do |name, values_with_empty_parameters|
|
||||||
begin
|
begin
|
||||||
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
|
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
|
||||||
|
# in order to allow a date to be set without a year, we must keep the empty values.
|
||||||
|
# Otherwise, we wouldn't be able to distinguish it from a date with an empty day.
|
||||||
|
values = values_with_empty_parameters.reject(&:nil?)
|
||||||
|
|
||||||
if values.empty?
|
if values.empty?
|
||||||
send(name + "=", nil)
|
send(name + "=", nil)
|
||||||
else
|
else
|
||||||
|
|
||||||
value = if Time == klass
|
value = if Time == klass
|
||||||
instantiate_time_object(name, values)
|
instantiate_time_object(name, values)
|
||||||
elsif Date == klass
|
elsif Date == klass
|
||||||
begin
|
begin
|
||||||
|
values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end
|
||||||
Date.new(*values)
|
Date.new(*values)
|
||||||
rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
|
rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
|
||||||
instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
|
instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
|
||||||
|
@ -3065,10 +3075,8 @@ module ActiveRecord #:nodoc:
|
||||||
attribute_name = multiparameter_name.split("(").first
|
attribute_name = multiparameter_name.split("(").first
|
||||||
attributes[attribute_name] = [] unless attributes.include?(attribute_name)
|
attributes[attribute_name] = [] unless attributes.include?(attribute_name)
|
||||||
|
|
||||||
unless value.empty?
|
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
||||||
attributes[attribute_name] <<
|
attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ]
|
||||||
[ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
|
attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
|
||||||
|
|
|
@ -190,6 +190,8 @@ module ActiveRecord
|
||||||
sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
|
sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
|
||||||
if options[:from]
|
if options[:from]
|
||||||
sql << " FROM #{options[:from]} "
|
sql << " FROM #{options[:from]} "
|
||||||
|
elsif scope && scope[:from] && !use_workaround
|
||||||
|
sql << " FROM #{scope[:from]} "
|
||||||
else
|
else
|
||||||
sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
|
sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
|
||||||
sql << " FROM #{connection.quote_table_name(table_name)} "
|
sql << " FROM #{connection.quote_table_name(table_name)} "
|
||||||
|
|
|
@ -277,7 +277,6 @@ module ActiveRecord
|
||||||
add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key
|
add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key
|
||||||
column_sql
|
column_sql
|
||||||
end
|
end
|
||||||
alias to_s :to_sql
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
@ -316,6 +315,20 @@ module ActiveRecord
|
||||||
@base = base
|
@base = base
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#Handles non supported datatypes - e.g. XML
|
||||||
|
def method_missing(symbol, *args)
|
||||||
|
if symbol.to_s == 'xml'
|
||||||
|
xml_column_fallback(args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def xml_column_fallback(*args)
|
||||||
|
case @base.adapter_name.downcase
|
||||||
|
when 'sqlite', 'mysql'
|
||||||
|
options = args.extract_options!
|
||||||
|
column(args[0], :text, options)
|
||||||
|
end
|
||||||
|
end
|
||||||
# Appends a primary key definition to the table definition.
|
# Appends a primary key definition to the table definition.
|
||||||
# Can be called multiple times, but this is probably not a good idea.
|
# Can be called multiple times, but this is probably not a good idea.
|
||||||
def primary_key(name)
|
def primary_key(name)
|
||||||
|
@ -508,7 +521,7 @@ module ActiveRecord
|
||||||
# concatenated together. This string can then be prepended and appended to
|
# concatenated together. This string can then be prepended and appended to
|
||||||
# to generate the final SQL to create the table.
|
# to generate the final SQL to create the table.
|
||||||
def to_sql
|
def to_sql
|
||||||
@columns * ', '
|
@columns.map(&:to_sql) * ', '
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -706,3 +719,4 @@ module ActiveRecord
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ module ActiveRecord
|
||||||
# See also TableDefinition#column for details on how to create columns.
|
# See also TableDefinition#column for details on how to create columns.
|
||||||
def create_table(table_name, options = {})
|
def create_table(table_name, options = {})
|
||||||
table_definition = TableDefinition.new(self)
|
table_definition = TableDefinition.new(self)
|
||||||
table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name)) unless options[:id] == false
|
table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
|
||||||
|
|
||||||
yield table_definition
|
yield table_definition
|
||||||
|
|
||||||
|
@ -321,7 +321,7 @@ module ActiveRecord
|
||||||
schema_migrations_table.column :version, :string, :null => false
|
schema_migrations_table.column :version, :string, :null => false
|
||||||
end
|
end
|
||||||
add_index sm_table, :version, :unique => true,
|
add_index sm_table, :version, :unique => true,
|
||||||
:name => 'unique_schema_migrations'
|
:name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
|
||||||
|
|
||||||
# Backwards-compatibility: if we find schema_info, assume we've
|
# Backwards-compatibility: if we find schema_info, assume we've
|
||||||
# migrated up to that point:
|
# migrated up to that point:
|
||||||
|
|
|
@ -54,6 +54,13 @@ module ActiveRecord
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Can this adapter determine the primary key for tables not attached
|
||||||
|
# to an ActiveRecord class, such as join tables? Backend specific, as
|
||||||
|
# the abstract adapter always returns +false+.
|
||||||
|
def supports_primary_key?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
# Does this adapter support using DISTINCT within COUNT? This is +true+
|
# Does this adapter support using DISTINCT within COUNT? This is +true+
|
||||||
# for all adapters except sqlite.
|
# for all adapters except sqlite.
|
||||||
def supports_count_distinct?
|
def supports_count_distinct?
|
||||||
|
|
|
@ -52,12 +52,7 @@ module ActiveRecord
|
||||||
socket = config[:socket]
|
socket = config[:socket]
|
||||||
username = config[:username] ? config[:username].to_s : 'root'
|
username = config[:username] ? config[:username].to_s : 'root'
|
||||||
password = config[:password].to_s
|
password = config[:password].to_s
|
||||||
|
database = config[:database]
|
||||||
if config.has_key?(:database)
|
|
||||||
database = config[:database]
|
|
||||||
else
|
|
||||||
raise ArgumentError, "No database specified. Missing argument: database."
|
|
||||||
end
|
|
||||||
|
|
||||||
# Require the MySQL driver and define Mysql::Result.all_hashes
|
# Require the MySQL driver and define Mysql::Result.all_hashes
|
||||||
unless defined? Mysql
|
unless defined? Mysql
|
||||||
|
@ -80,7 +75,7 @@ module ActiveRecord
|
||||||
module ConnectionAdapters
|
module ConnectionAdapters
|
||||||
class MysqlColumn < Column #:nodoc:
|
class MysqlColumn < Column #:nodoc:
|
||||||
def extract_default(default)
|
def extract_default(default)
|
||||||
if type == :binary || type == :text
|
if sql_type =~ /blob/i || type == :text
|
||||||
if default.blank?
|
if default.blank?
|
||||||
return null ? nil : ''
|
return null ? nil : ''
|
||||||
else
|
else
|
||||||
|
@ -94,7 +89,7 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_default?
|
def has_default?
|
||||||
return false if type == :binary || type == :text #mysql forbids defaults on blob and text columns
|
return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -212,6 +207,10 @@ module ActiveRecord
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def supports_primary_key? #:nodoc:
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
def supports_savepoints? #:nodoc:
|
def supports_savepoints? #:nodoc:
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
@ -554,6 +553,12 @@ module ActiveRecord
|
||||||
keys.length == 1 ? [keys.first, nil] : nil
|
keys.length == 1 ? [keys.first, nil] : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns just a table's primary key
|
||||||
|
def primary_key(table)
|
||||||
|
pk_and_sequence = pk_and_sequence_for(table)
|
||||||
|
pk_and_sequence && pk_and_sequence.first
|
||||||
|
end
|
||||||
|
|
||||||
def case_sensitive_equality_operator
|
def case_sensitive_equality_operator
|
||||||
"= BINARY"
|
"= BINARY"
|
||||||
end
|
end
|
||||||
|
@ -573,6 +578,10 @@ module ActiveRecord
|
||||||
@connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
|
@connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
|
||||||
|
@connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
|
||||||
|
@connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
|
||||||
|
|
||||||
@connection.real_connect(*@connection_options)
|
@connection.real_connect(*@connection_options)
|
||||||
|
|
||||||
# reconnect must be set after real_connect is called, because real_connect sets it to false internally
|
# reconnect must be set after real_connect is called, because real_connect sets it to false internally
|
||||||
|
|
|
@ -39,6 +39,12 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
module ConnectionAdapters
|
module ConnectionAdapters
|
||||||
|
class TableDefinition
|
||||||
|
def xml(*args)
|
||||||
|
options = args.extract_options!
|
||||||
|
column(args[0], 'xml', options)
|
||||||
|
end
|
||||||
|
end
|
||||||
# PostgreSQL-specific extensions to column definitions in a table.
|
# PostgreSQL-specific extensions to column definitions in a table.
|
||||||
class PostgreSQLColumn < Column #:nodoc:
|
class PostgreSQLColumn < Column #:nodoc:
|
||||||
# Instantiates a new PostgreSQL column definition in a table.
|
# Instantiates a new PostgreSQL column definition in a table.
|
||||||
|
@ -99,7 +105,7 @@ module ActiveRecord
|
||||||
:string
|
:string
|
||||||
# XML type
|
# XML type
|
||||||
when /^xml$/
|
when /^xml$/
|
||||||
:string
|
:xml
|
||||||
# Arrays
|
# Arrays
|
||||||
when /^\D+\[\]$/
|
when /^\D+\[\]$/
|
||||||
:string
|
:string
|
||||||
|
@ -194,7 +200,8 @@ module ActiveRecord
|
||||||
:time => { :name => "time" },
|
:time => { :name => "time" },
|
||||||
:date => { :name => "date" },
|
:date => { :name => "date" },
|
||||||
:binary => { :name => "bytea" },
|
:binary => { :name => "bytea" },
|
||||||
:boolean => { :name => "boolean" }
|
:boolean => { :name => "boolean" },
|
||||||
|
:xml => { :name => "xml" }
|
||||||
}
|
}
|
||||||
|
|
||||||
# Returns 'PostgreSQL' as adapter name for identification purposes.
|
# Returns 'PostgreSQL' as adapter name for identification purposes.
|
||||||
|
@ -249,6 +256,11 @@ module ActiveRecord
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Does PostgreSQL support finding primary key on non-ActiveRecord tables?
|
||||||
|
def supports_primary_key? #:nodoc:
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
# Does PostgreSQL support standard conforming strings?
|
# Does PostgreSQL support standard conforming strings?
|
||||||
def supports_standard_conforming_strings?
|
def supports_standard_conforming_strings?
|
||||||
# Temporarily set the client message level above error to prevent unintentional
|
# Temporarily set the client message level above error to prevent unintentional
|
||||||
|
@ -364,7 +376,7 @@ module ActiveRecord
|
||||||
if value.kind_of?(String) && column && column.type == :binary
|
if value.kind_of?(String) && column && column.type == :binary
|
||||||
"#{quoted_string_prefix}'#{escape_bytea(value)}'"
|
"#{quoted_string_prefix}'#{escape_bytea(value)}'"
|
||||||
elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
|
elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
|
||||||
"xml '#{quote_string(value)}'"
|
"xml E'#{quote_string(value)}'"
|
||||||
elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
|
elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
|
||||||
# Not truly string input, so doesn't require (or allow) escape string syntax.
|
# Not truly string input, so doesn't require (or allow) escape string syntax.
|
||||||
"'#{value.to_s}'"
|
"'#{value.to_s}'"
|
||||||
|
@ -810,6 +822,12 @@ module ActiveRecord
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns just a table's primary key
|
||||||
|
def primary_key(table)
|
||||||
|
pk_and_sequence = pk_and_sequence_for(table)
|
||||||
|
pk_and_sequence && pk_and_sequence.first
|
||||||
|
end
|
||||||
|
|
||||||
# Renames a table.
|
# Renames a table.
|
||||||
def rename_table(name, new_name)
|
def rename_table(name, new_name)
|
||||||
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
|
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
|
||||||
|
@ -1092,3 +1110,4 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,10 @@ module ActiveRecord
|
||||||
db.results_as_hash = true if defined? SQLite::Version
|
db.results_as_hash = true if defined? SQLite::Version
|
||||||
db.type_translation = false
|
db.type_translation = false
|
||||||
|
|
||||||
|
message = "Support for SQLite2Adapter and DeprecatedSQLiteAdapter has been removed from Rails 3. "
|
||||||
|
message << "You should migrate to SQLite 3+ or use the plugin from git://github.com/rails/sqlite2_adapter.git with Rails 3."
|
||||||
|
ActiveSupport::Deprecation.warn(message)
|
||||||
|
|
||||||
# "Downgrade" deprecated sqlite API
|
# "Downgrade" deprecated sqlite API
|
||||||
if SQLite.const_defined?(:Version)
|
if SQLite.const_defined?(:Version)
|
||||||
ConnectionAdapters::SQLite2Adapter.new(db, logger, config)
|
ConnectionAdapters::SQLite2Adapter.new(db, logger, config)
|
||||||
|
@ -27,6 +31,10 @@ module ActiveRecord
|
||||||
|
|
||||||
private
|
private
|
||||||
def parse_sqlite_config!(config)
|
def parse_sqlite_config!(config)
|
||||||
|
if config.include?(:dbfile)
|
||||||
|
ActiveSupport::Deprecation.warn "Please update config/database.yml to use 'database' instead of 'dbfile'"
|
||||||
|
end
|
||||||
|
|
||||||
config[:database] ||= config[:dbfile]
|
config[:database] ||= config[:dbfile]
|
||||||
# Require database.
|
# Require database.
|
||||||
unless config[:database]
|
unless config[:database]
|
||||||
|
@ -104,6 +112,10 @@ module ActiveRecord
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def supports_primary_key? #:nodoc:
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
def requires_reloading?
|
def requires_reloading?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
|
@ -143,7 +143,7 @@ module ActiveRecord
|
||||||
if partial_updates?
|
if partial_updates?
|
||||||
# Serialized attributes should always be written in case they've been
|
# Serialized attributes should always be written in case they've been
|
||||||
# changed in place.
|
# changed in place.
|
||||||
update_without_dirty(changed | self.class.serialized_attributes.keys)
|
update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
|
||||||
else
|
else
|
||||||
update_without_dirty
|
update_without_dirty
|
||||||
end
|
end
|
||||||
|
|
|
@ -621,7 +621,8 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
|
||||||
targets.each do |target|
|
targets.each do |target|
|
||||||
join_fixtures["#{label}_#{target}"] = Fixture.new(
|
join_fixtures["#{label}_#{target}"] = Fixture.new(
|
||||||
{ association.primary_key_name => row[primary_key_name],
|
{ association.primary_key_name => row[primary_key_name],
|
||||||
association.association_foreign_key => Fixtures.identify(target) }, nil)
|
association.association_foreign_key => Fixtures.identify(target) },
|
||||||
|
nil, @connection)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -705,12 +706,12 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
|
||||||
|
|
||||||
yaml_value.each do |fixture|
|
yaml_value.each do |fixture|
|
||||||
raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
|
raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
|
||||||
fixture.each do |name, data|
|
fixture.each do |name, data|
|
||||||
unless data
|
unless data
|
||||||
raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
|
raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
|
||||||
end
|
end
|
||||||
|
|
||||||
self[name] = Fixture.new(data, model_class)
|
self[name] = Fixture.new(data, model_class, @connection)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -723,7 +724,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
|
||||||
reader.each do |row|
|
reader.each do |row|
|
||||||
data = {}
|
data = {}
|
||||||
row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
|
row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
|
||||||
self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class)
|
self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class, @connection)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -761,7 +762,8 @@ class Fixture #:nodoc:
|
||||||
|
|
||||||
attr_reader :model_class
|
attr_reader :model_class
|
||||||
|
|
||||||
def initialize(fixture, model_class)
|
def initialize(fixture, model_class, connection = ActiveRecord::Base.connection)
|
||||||
|
@connection = connection
|
||||||
@fixture = fixture
|
@fixture = fixture
|
||||||
@model_class = model_class.is_a?(Class) ? model_class : model_class.constantize rescue nil
|
@model_class = model_class.is_a?(Class) ? model_class : model_class.constantize rescue nil
|
||||||
end
|
end
|
||||||
|
@ -783,14 +785,14 @@ class Fixture #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def key_list
|
def key_list
|
||||||
columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
|
columns = @fixture.keys.collect{ |column_name| @connection.quote_column_name(column_name) }
|
||||||
columns.join(", ")
|
columns.join(", ")
|
||||||
end
|
end
|
||||||
|
|
||||||
def value_list
|
def value_list
|
||||||
list = @fixture.inject([]) do |fixtures, (key, value)|
|
list = @fixture.inject([]) do |fixtures, (key, value)|
|
||||||
col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base)
|
col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base)
|
||||||
fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
|
fixtures << @connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
|
||||||
end
|
end
|
||||||
list * ', '
|
list * ', '
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ module I18n
|
||||||
|
|
||||||
protected
|
protected
|
||||||
def interpolate_with_deprecated_syntax(locale, string, values = {})
|
def interpolate_with_deprecated_syntax(locale, string, values = {})
|
||||||
return string unless string.is_a?(String)
|
return string unless string.is_a?(String) && !values.empty?
|
||||||
|
|
||||||
string = string.gsub(/%d|%s/) do |s|
|
string = string.gsub(/%d|%s/) do |s|
|
||||||
instead = DEPRECATED_INTERPOLATORS[s]
|
instead = DEPRECATED_INTERPOLATORS[s]
|
||||||
|
|
|
@ -23,8 +23,12 @@ en:
|
||||||
less_than_or_equal_to: "must be less than or equal to {{count}}"
|
less_than_or_equal_to: "must be less than or equal to {{count}}"
|
||||||
odd: "must be odd"
|
odd: "must be odd"
|
||||||
even: "must be even"
|
even: "must be even"
|
||||||
|
record_invalid: "Validation failed: {{errors}}"
|
||||||
# Append your own errors here or at the model/attributes scope.
|
# Append your own errors here or at the model/attributes scope.
|
||||||
|
|
||||||
|
full_messages:
|
||||||
|
format: "{{attribute}} {{message}}"
|
||||||
|
|
||||||
# You can define own errors for models or model attributes.
|
# You can define own errors for models or model attributes.
|
||||||
# The values :model, :attribute and :value are always available for interpolation.
|
# The values :model, :attribute and :value are always available for interpolation.
|
||||||
#
|
#
|
||||||
|
|
|
@ -89,12 +89,7 @@ module ActiveRecord
|
||||||
when Hash
|
when Hash
|
||||||
options
|
options
|
||||||
when Proc
|
when Proc
|
||||||
case parent_scope
|
options.call(*args)
|
||||||
when Scope
|
|
||||||
with_scope(:find => parent_scope.proxy_options) { options.call(*args) }
|
|
||||||
else
|
|
||||||
options.call(*args)
|
|
||||||
end
|
|
||||||
end, &block)
|
end, &block)
|
||||||
end
|
end
|
||||||
(class << self; self end).instance_eval do
|
(class << self; self end).instance_eval do
|
||||||
|
|
|
@ -297,7 +297,7 @@ module ActiveRecord
|
||||||
raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
|
raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
|
||||||
end
|
end
|
||||||
|
|
||||||
unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
|
unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil?
|
||||||
raise HasManyThroughSourceAssociationMacroError.new(self)
|
raise HasManyThroughSourceAssociationMacroError.new(self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -84,7 +84,6 @@ HEADER
|
||||||
elsif @connection.respond_to?(:primary_key)
|
elsif @connection.respond_to?(:primary_key)
|
||||||
pk = @connection.primary_key(table)
|
pk = @connection.primary_key(table)
|
||||||
end
|
end
|
||||||
pk ||= 'id'
|
|
||||||
|
|
||||||
tbl.print " create_table #{table.inspect}"
|
tbl.print " create_table #{table.inspect}"
|
||||||
if columns.detect { |c| c.name == pk }
|
if columns.detect { |c| c.name == pk }
|
||||||
|
|
|
@ -74,12 +74,14 @@ module ActiveRecord #:nodoc:
|
||||||
# {"comments": [{"body": "Don't think too hard"}],
|
# {"comments": [{"body": "Don't think too hard"}],
|
||||||
# "title": "So I was thinking"}]}
|
# "title": "So I was thinking"}]}
|
||||||
def to_json(options = {})
|
def to_json(options = {})
|
||||||
hash = Serializer.new(self, options).serializable_record
|
super
|
||||||
hash = { self.class.model_name.element => hash } if include_root_in_json
|
|
||||||
ActiveSupport::JSON.encode(hash)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_json(options = nil) self end #:nodoc:
|
def as_json(options = nil) #:nodoc:
|
||||||
|
hash = Serializer.new(self, options).serializable_record
|
||||||
|
hash = { self.class.model_name.element => hash } if include_root_in_json
|
||||||
|
hash
|
||||||
|
end
|
||||||
|
|
||||||
def from_json(json)
|
def from_json(json)
|
||||||
self.attributes = ActiveSupport::JSON.decode(json)
|
self.attributes = ActiveSupport::JSON.decode(json)
|
||||||
|
|
|
@ -178,7 +178,7 @@ module ActiveRecord #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def root
|
def root
|
||||||
root = (options[:root] || @record.class.to_s.underscore).to_s
|
root = (options[:root] || @record.class.model_name.singular).to_s
|
||||||
reformat_name(root)
|
reformat_name(root)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -320,7 +320,11 @@ module ActiveRecord #:nodoc:
|
||||||
|
|
||||||
protected
|
protected
|
||||||
def compute_type
|
def compute_type
|
||||||
type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.columns_hash[name].type
|
type = if @record.class.serialized_attributes.has_key?(name)
|
||||||
|
:yaml
|
||||||
|
else
|
||||||
|
@record.class.columns_hash[name].try(:type)
|
||||||
|
end
|
||||||
|
|
||||||
case type
|
case type
|
||||||
when :text
|
when :text
|
||||||
|
|
|
@ -10,10 +10,117 @@ module ActiveRecord
|
||||||
attr_reader :record
|
attr_reader :record
|
||||||
def initialize(record)
|
def initialize(record)
|
||||||
@record = record
|
@record = record
|
||||||
super("Validation failed: #{@record.errors.full_messages.join(", ")}")
|
errors = @record.errors.full_messages.join(I18n.t('support.array.words_connector', :default => ', '))
|
||||||
|
super(I18n.t('activerecord.errors.messages.record_invalid', :errors => errors))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class Error
|
||||||
|
attr_accessor :base, :attribute, :type, :message, :options
|
||||||
|
|
||||||
|
def initialize(base, attribute, type = nil, options = {})
|
||||||
|
self.base = base
|
||||||
|
self.attribute = attribute
|
||||||
|
self.type = type || :invalid
|
||||||
|
self.options = options
|
||||||
|
self.message = options.delete(:message) || self.type
|
||||||
|
end
|
||||||
|
|
||||||
|
def message
|
||||||
|
generate_message(@message, options.dup)
|
||||||
|
end
|
||||||
|
|
||||||
|
def full_message
|
||||||
|
attribute.to_s == 'base' ? message : generate_full_message(message, options.dup)
|
||||||
|
end
|
||||||
|
|
||||||
|
alias :to_s :message
|
||||||
|
|
||||||
|
def value
|
||||||
|
@base.respond_to?(attribute) ? @base.send(attribute) : nil
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
# Translates an error message in it's default scope (<tt>activerecord.errrors.messages</tt>).
|
||||||
|
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there,
|
||||||
|
# it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the
|
||||||
|
# default message (e.g. <tt>activerecord.errors.messages.MESSAGE</tt>). The translated model name,
|
||||||
|
# translated attribute name and the value are available for interpolation.
|
||||||
|
#
|
||||||
|
# When using inheritence in your models, it will check all the inherited models too, but only if the model itself
|
||||||
|
# hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt>
|
||||||
|
# error +message+ for the <tt>title</tt> +attribute+, it looks for these translations:
|
||||||
|
#
|
||||||
|
# <ol>
|
||||||
|
# <li><tt>activerecord.errors.models.admin.attributes.title.blank</tt></li>
|
||||||
|
# <li><tt>activerecord.errors.models.admin.blank</tt></li>
|
||||||
|
# <li><tt>activerecord.errors.models.user.attributes.title.blank</tt></li>
|
||||||
|
# <li><tt>activerecord.errors.models.user.blank</tt></li>
|
||||||
|
# <li><tt>activerecord.errors.messages.blank</tt></li>
|
||||||
|
# <li>any default you provided through the +options+ hash (in the activerecord.errors scope)</li>
|
||||||
|
# </ol>
|
||||||
|
def generate_message(message, options = {})
|
||||||
|
keys = @base.class.self_and_descendants_from_active_record.map do |klass|
|
||||||
|
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
|
||||||
|
:"models.#{klass.name.underscore}.#{message}" ]
|
||||||
|
end.flatten
|
||||||
|
|
||||||
|
keys << options.delete(:default)
|
||||||
|
keys << :"messages.#{message}"
|
||||||
|
keys << message if message.is_a?(String)
|
||||||
|
keys << @type unless @type == message
|
||||||
|
keys.compact!
|
||||||
|
|
||||||
|
options.reverse_merge! :default => keys,
|
||||||
|
:scope => [:activerecord, :errors],
|
||||||
|
:model => @base.class.human_name,
|
||||||
|
:attribute => @base.class.human_attribute_name(attribute.to_s),
|
||||||
|
:value => value
|
||||||
|
|
||||||
|
I18n.translate(keys.shift, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Wraps an error message into a full_message format.
|
||||||
|
#
|
||||||
|
# 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>.
|
||||||
|
#
|
||||||
|
# Additionally one can specify a validation specific error message format by
|
||||||
|
# storing a translation for <tt>:"activerecord.errors.full_messages.[message_key]"</tt>.
|
||||||
|
# E.g. the full_message format for any validation that uses :blank as a message
|
||||||
|
# key (such as validates_presence_of) can be stored to <tt>:"activerecord.errors.full_messages.blank".</tt>
|
||||||
|
#
|
||||||
|
# Because the message key used by a validation can be overwritten on the
|
||||||
|
# <tt>validates_*</tt> class macro level one can customize the full_message format for
|
||||||
|
# any particular validation:
|
||||||
|
#
|
||||||
|
# # app/models/article.rb
|
||||||
|
# class Article < ActiveRecord::Base
|
||||||
|
# validates_presence_of :title, :message => :"title.blank"
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # config/locales/en.yml
|
||||||
|
# en:
|
||||||
|
# activerecord:
|
||||||
|
# errors:
|
||||||
|
# full_messages:
|
||||||
|
# title:
|
||||||
|
# blank: This title is screwed!
|
||||||
|
def generate_full_message(message, options = {})
|
||||||
|
options.reverse_merge! :message => self.message,
|
||||||
|
:model => @base.class.human_name,
|
||||||
|
:attribute => @base.class.human_attribute_name(attribute.to_s),
|
||||||
|
:value => value
|
||||||
|
|
||||||
|
key = :"full_messages.#{@message}"
|
||||||
|
defaults = [:'full_messages.format', '{{attribute}} {{message}}']
|
||||||
|
|
||||||
|
I18n.t(key, options.merge(:default => defaults, :scope => [:activerecord, :errors]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Active Record validation is reported to and from this object, which is used by Base#save to
|
# Active Record validation is reported to and from this object, which is used by Base#save to
|
||||||
# determine whether the object is in a valid state to be saved. See usage example in Validations.
|
# determine whether the object is in a valid state to be saved. See usage example in Validations.
|
||||||
class Errors
|
class Errors
|
||||||
|
@ -43,11 +150,19 @@ module ActiveRecord
|
||||||
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
||||||
# If no +messsage+ is supplied, :invalid is assumed.
|
# If no +messsage+ is supplied, :invalid is assumed.
|
||||||
# If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
|
# If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
|
||||||
def add(attribute, message = nil, options = {})
|
# def add(attribute, message = nil, options = {})
|
||||||
message ||= :invalid
|
# message ||= :invalid
|
||||||
message = generate_message(attribute, message, options) if message.is_a?(Symbol)
|
# message = generate_message(attribute, message, options)) if message.is_a?(Symbol)
|
||||||
|
# @errors[attribute.to_s] ||= []
|
||||||
|
# @errors[attribute.to_s] << message
|
||||||
|
# end
|
||||||
|
|
||||||
|
def add(error_or_attr, message = nil, options = {})
|
||||||
|
error, attribute = error_or_attr.is_a?(Error) ? [error_or_attr, error_or_attr.attribute] : [nil, error_or_attr]
|
||||||
|
options[:message] = options.delete(:default) if options.has_key?(:default)
|
||||||
|
|
||||||
@errors[attribute.to_s] ||= []
|
@errors[attribute.to_s] ||= []
|
||||||
@errors[attribute.to_s] << message
|
@errors[attribute.to_s] << (error || Error.new(@base, attribute, message, options))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Will add an error message to each of the attributes in +attributes+ that is empty.
|
# Will add an error message to each of the attributes in +attributes+ that is empty.
|
||||||
|
@ -67,49 +182,6 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Translates an error message in it's default scope (<tt>activerecord.errrors.messages</tt>).
|
|
||||||
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there,
|
|
||||||
# it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the
|
|
||||||
# default message (e.g. <tt>activerecord.errors.messages.MESSAGE</tt>). The translated model name,
|
|
||||||
# translated attribute name and the value are available for interpolation.
|
|
||||||
#
|
|
||||||
# When using inheritence in your models, it will check all the inherited models too, but only if the model itself
|
|
||||||
# hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt>
|
|
||||||
# error +message+ for the <tt>title</tt> +attribute+, it looks for these translations:
|
|
||||||
#
|
|
||||||
# <ol>
|
|
||||||
# <li><tt>activerecord.errors.models.admin.attributes.title.blank</tt></li>
|
|
||||||
# <li><tt>activerecord.errors.models.admin.blank</tt></li>
|
|
||||||
# <li><tt>activerecord.errors.models.user.attributes.title.blank</tt></li>
|
|
||||||
# <li><tt>activerecord.errors.models.user.blank</tt></li>
|
|
||||||
# <li><tt>activerecord.errors.messages.blank</tt></li>
|
|
||||||
# <li>any default you provided through the +options+ hash (in the activerecord.errors scope)</li>
|
|
||||||
# </ol>
|
|
||||||
def generate_message(attribute, message = :invalid, options = {})
|
|
||||||
|
|
||||||
message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
|
|
||||||
|
|
||||||
defaults = @base.class.self_and_descendants_from_active_record.map do |klass|
|
|
||||||
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
|
|
||||||
:"models.#{klass.name.underscore}.#{message}" ]
|
|
||||||
end
|
|
||||||
|
|
||||||
defaults << options.delete(:default)
|
|
||||||
defaults = defaults.compact.flatten << :"messages.#{message}"
|
|
||||||
|
|
||||||
key = defaults.shift
|
|
||||||
value = @base.respond_to?(attribute) ? @base.send(attribute) : nil
|
|
||||||
|
|
||||||
options = { :default => defaults,
|
|
||||||
:model => @base.class.human_name,
|
|
||||||
:attribute => @base.class.human_attribute_name(attribute.to_s),
|
|
||||||
:value => value,
|
|
||||||
:scope => [:activerecord, :errors]
|
|
||||||
}.merge(options)
|
|
||||||
|
|
||||||
I18n.translate(key, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns true if the specified +attribute+ has errors associated with it.
|
# Returns true if the specified +attribute+ has errors associated with it.
|
||||||
#
|
#
|
||||||
# class Company < ActiveRecord::Base
|
# class Company < ActiveRecord::Base
|
||||||
|
@ -138,8 +210,9 @@ module ActiveRecord
|
||||||
# company.errors.on(:email) # => "can't be blank"
|
# company.errors.on(:email) # => "can't be blank"
|
||||||
# company.errors.on(:address) # => nil
|
# company.errors.on(:address) # => nil
|
||||||
def on(attribute)
|
def on(attribute)
|
||||||
errors = @errors[attribute.to_s]
|
attribute = attribute.to_s
|
||||||
return nil if errors.nil?
|
return nil unless @errors.has_key?(attribute)
|
||||||
|
errors = @errors[attribute].map(&:to_s)
|
||||||
errors.size == 1 ? errors.first : errors
|
errors.size == 1 ? errors.first : errors
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -163,7 +236,11 @@ module ActiveRecord
|
||||||
# # name - can't be blank
|
# # name - can't be blank
|
||||||
# # address - can't be blank
|
# # address - can't be blank
|
||||||
def each
|
def each
|
||||||
@errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
|
@errors.each_key { |attr| @errors[attr].each { |error| yield attr, error.message } }
|
||||||
|
end
|
||||||
|
|
||||||
|
def each_error
|
||||||
|
@errors.each_key { |attr| @errors[attr].each { |error| yield attr, error } }
|
||||||
end
|
end
|
||||||
|
|
||||||
# Yields each full error message added. So <tt>Person.errors.add("first_name", "can't be empty")</tt> will be returned
|
# Yields each full error message added. So <tt>Person.errors.add("first_name", "can't be empty")</tt> will be returned
|
||||||
|
@ -194,21 +271,9 @@ module ActiveRecord
|
||||||
# company.errors.full_messages # =>
|
# company.errors.full_messages # =>
|
||||||
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
|
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
|
||||||
def full_messages(options = {})
|
def full_messages(options = {})
|
||||||
full_messages = []
|
@errors.values.inject([]) do |full_messages, errors|
|
||||||
|
full_messages + errors.map { |error| error.full_message }
|
||||||
@errors.each_key do |attr|
|
|
||||||
@errors[attr].each do |message|
|
|
||||||
next unless message
|
|
||||||
|
|
||||||
if attr == "base"
|
|
||||||
full_messages << message
|
|
||||||
else
|
|
||||||
attr_name = @base.class.human_attribute_name(attr)
|
|
||||||
full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
full_messages
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if no errors have been added.
|
# Returns true if no errors have been added.
|
||||||
|
@ -255,6 +320,10 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def generate_message(attribute, message = :invalid, options = {})
|
||||||
|
ActiveSupport::Deprecation.warn("ActiveRecord::Errors#generate_message has been deprecated. Please use ActiveRecord::Error#generate_message.")
|
||||||
|
Error.new(@base, attribute, message, options).to_s
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -726,7 +795,7 @@ module ActiveRecord
|
||||||
comparison_operator = "IS ?"
|
comparison_operator = "IS ?"
|
||||||
elsif column.text?
|
elsif column.text?
|
||||||
comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
|
comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
|
||||||
value = column.limit ? value.to_s[0, column.limit] : value.to_s
|
value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s
|
||||||
else
|
else
|
||||||
comparison_operator = "= ?"
|
comparison_operator = "= ?"
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@ module ActiveRecord
|
||||||
module VERSION #:nodoc:
|
module VERSION #:nodoc:
|
||||||
MAJOR = 2
|
MAJOR = 2
|
||||||
MINOR = 3
|
MINOR = 3
|
||||||
TINY = 3
|
TINY = 4
|
||||||
|
|
||||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||||
end
|
end
|
||||||
|
|
|
@ -63,6 +63,18 @@ class AdapterTest < ActiveRecord::TestCase
|
||||||
def test_show_nonexistent_variable_returns_nil
|
def test_show_nonexistent_variable_returns_nil
|
||||||
assert_nil @connection.show_variable('foo_bar_baz')
|
assert_nil @connection.show_variable('foo_bar_baz')
|
||||||
end
|
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"
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveRecord::Base.establish_connection 'arunit'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if current_adapter?(:PostgreSQLAdapter)
|
if current_adapter?(:PostgreSQLAdapter)
|
||||||
|
|
|
@ -249,24 +249,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
|
||||||
assert_equal 1, Topic.find(topic.id)[:replies_count]
|
assert_equal 1, Topic.find(topic.id)[:replies_count]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_belongs_to_counter_after_save
|
|
||||||
topic = Topic.create("title" => "monday night")
|
|
||||||
topic.replies.create("title" => "re: monday night", "content" => "football")
|
|
||||||
assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
|
|
||||||
|
|
||||||
topic.save
|
|
||||||
assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_belongs_to_counter_after_update_attributes
|
|
||||||
topic = Topic.create("title" => "37s")
|
|
||||||
topic.replies.create("title" => "re: 37s", "content" => "rails")
|
|
||||||
assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
|
|
||||||
|
|
||||||
topic.update_attributes("title" => "37signals")
|
|
||||||
assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_assignment_before_child_saved
|
def test_assignment_before_child_saved
|
||||||
final_cut = Client.new("name" => "Final Cut")
|
final_cut = Client.new("name" => "Final Cut")
|
||||||
firm = Firm.find(1)
|
firm = Firm.find(1)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require 'cases/helper'
|
require 'cases/helper'
|
||||||
require 'models/author'
|
|
||||||
require 'models/post'
|
require 'models/post'
|
||||||
|
require 'models/author'
|
||||||
require 'models/comment'
|
require 'models/comment'
|
||||||
require 'models/category'
|
require 'models/category'
|
||||||
require 'models/categorization'
|
require 'models/categorization'
|
||||||
|
|
56
vendor/rails/activerecord/test/cases/associations/habtm_join_table_test.rb
vendored
Normal file
56
vendor/rails/activerecord/test/cases/associations/habtm_join_table_test.rb
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
require 'cases/helper'
|
||||||
|
|
||||||
|
class MyReader < ActiveRecord::Base
|
||||||
|
has_and_belongs_to_many :my_books
|
||||||
|
end
|
||||||
|
|
||||||
|
class MyBook < ActiveRecord::Base
|
||||||
|
has_and_belongs_to_many :my_readers
|
||||||
|
end
|
||||||
|
|
||||||
|
class HabtmJoinTableTest < ActiveRecord::TestCase
|
||||||
|
def setup
|
||||||
|
ActiveRecord::Base.connection.create_table :my_books, :force => true do |t|
|
||||||
|
t.string :name
|
||||||
|
end
|
||||||
|
assert ActiveRecord::Base.connection.table_exists?(:my_books)
|
||||||
|
|
||||||
|
ActiveRecord::Base.connection.create_table :my_readers, :force => true do |t|
|
||||||
|
t.string :name
|
||||||
|
end
|
||||||
|
assert ActiveRecord::Base.connection.table_exists?(:my_readers)
|
||||||
|
|
||||||
|
ActiveRecord::Base.connection.create_table :my_books_my_readers, :force => true do |t|
|
||||||
|
t.integer :my_book_id
|
||||||
|
t.integer :my_reader_id
|
||||||
|
end
|
||||||
|
assert ActiveRecord::Base.connection.table_exists?(:my_books_my_readers)
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
ActiveRecord::Base.connection.drop_table :my_books
|
||||||
|
ActiveRecord::Base.connection.drop_table :my_readers
|
||||||
|
ActiveRecord::Base.connection.drop_table :my_books_my_readers
|
||||||
|
end
|
||||||
|
|
||||||
|
uses_transaction :test_should_raise_exception_when_join_table_has_a_primary_key
|
||||||
|
def test_should_raise_exception_when_join_table_has_a_primary_key
|
||||||
|
if ActiveRecord::Base.connection.supports_primary_key?
|
||||||
|
assert_raise ActiveRecord::ConfigurationError do
|
||||||
|
jaime = MyReader.create(:name=>"Jaime")
|
||||||
|
jaime.my_books << MyBook.create(:name=>'Great Expectations')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
uses_transaction :test_should_cache_result_of_primary_key_check
|
||||||
|
def test_should_cache_result_of_primary_key_check
|
||||||
|
if ActiveRecord::Base.connection.supports_primary_key?
|
||||||
|
ActiveRecord::Base.connection.stubs(:primary_key).with('my_books_my_readers').returns(false).once
|
||||||
|
weaz = MyReader.create(:name=>'Weaz')
|
||||||
|
|
||||||
|
weaz.my_books << MyBook.create(:name=>'Great Expectations')
|
||||||
|
weaz.my_books << MyBook.create(:name=>'Greater Expectations')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,11 +10,12 @@ require 'models/author'
|
||||||
require 'models/comment'
|
require 'models/comment'
|
||||||
require 'models/person'
|
require 'models/person'
|
||||||
require 'models/reader'
|
require 'models/reader'
|
||||||
|
require 'models/tagging'
|
||||||
|
|
||||||
class HasManyAssociationsTest < ActiveRecord::TestCase
|
class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||||
fixtures :accounts, :categories, :companies, :developers, :projects,
|
fixtures :accounts, :categories, :companies, :developers, :projects,
|
||||||
:developers_projects, :topics, :authors, :comments, :author_addresses,
|
:developers_projects, :topics, :authors, :comments, :author_addresses,
|
||||||
:people, :posts, :readers
|
:people, :posts, :readers, :taggings
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
Client.destroyed_client_ids.clear
|
Client.destroyed_client_ids.clear
|
||||||
|
@ -279,6 +280,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||||
assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }])
|
assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_find_all_with_include_and_conditions
|
||||||
|
assert_nothing_raised do
|
||||||
|
Developer.find(:all, :joins => :audit_logs, :conditions => {'audit_logs.message' => nil, :name => 'Smith'})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_find_in_collection
|
def test_find_in_collection
|
||||||
assert_equal Client.find(2).name, companies(:first_firm).clients.find(2).name
|
assert_equal Client.find(2).name, companies(:first_firm).clients.find(2).name
|
||||||
assert_raise(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) }
|
assert_raise(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) }
|
||||||
|
@ -502,6 +509,23 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||||
assert_equal 0, new_firm.clients_of_firm.size
|
assert_equal 0, new_firm.clients_of_firm.size
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_deleting_updates_counter_cache
|
||||||
|
topic = Topic.first
|
||||||
|
assert_equal topic.replies.to_a.size, topic.replies_count
|
||||||
|
|
||||||
|
topic.replies.delete(topic.replies.first)
|
||||||
|
topic.reload
|
||||||
|
assert_equal topic.replies.to_a.size, topic.replies_count
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_deleting_updates_counter_cache_without_dependent_destroy
|
||||||
|
post = posts(:welcome)
|
||||||
|
|
||||||
|
assert_difference "post.reload.taggings_count", -1 do
|
||||||
|
post.taggings.delete(post.taggings.first)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_deleting_a_collection
|
def test_deleting_a_collection
|
||||||
force_signal37_to_load_all_clients_of_firm
|
force_signal37_to_load_all_clients_of_firm
|
||||||
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
|
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
|
||||||
|
@ -547,6 +571,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_clearing_updates_counter_cache
|
||||||
|
topic = Topic.first
|
||||||
|
|
||||||
|
topic.replies.clear
|
||||||
|
topic.reload
|
||||||
|
assert_equal 0, topic.replies_count
|
||||||
|
end
|
||||||
|
|
||||||
def test_clearing_a_dependent_association_collection
|
def test_clearing_a_dependent_association_collection
|
||||||
firm = companies(:first_firm)
|
firm = companies(:first_firm)
|
||||||
client_id = firm.dependent_clients_of_firm.first.id
|
client_id = firm.dependent_clients_of_firm.first.id
|
||||||
|
@ -691,6 +723,28 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||||
assert_equal 0, companies(:first_firm).clients_of_firm(true).size
|
assert_equal 0, companies(:first_firm).clients_of_firm(true).size
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_destroying_by_fixnum_id
|
||||||
|
force_signal37_to_load_all_clients_of_firm
|
||||||
|
|
||||||
|
assert_difference "Client.count", -1 do
|
||||||
|
companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 0, companies(:first_firm).reload.clients_of_firm.size
|
||||||
|
assert_equal 0, companies(:first_firm).clients_of_firm(true).size
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_destroying_by_string_id
|
||||||
|
force_signal37_to_load_all_clients_of_firm
|
||||||
|
|
||||||
|
assert_difference "Client.count", -1 do
|
||||||
|
companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 0, companies(:first_firm).reload.clients_of_firm.size
|
||||||
|
assert_equal 0, companies(:first_firm).clients_of_firm(true).size
|
||||||
|
end
|
||||||
|
|
||||||
def test_destroying_a_collection
|
def test_destroying_a_collection
|
||||||
force_signal37_to_load_all_clients_of_firm
|
force_signal37_to_load_all_clients_of_firm
|
||||||
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
|
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
|
||||||
|
@ -861,7 +915,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||||
lambda { authors(:mary).comments = [comments(:greetings), comments(:more_greetings)] },
|
lambda { authors(:mary).comments = [comments(:greetings), comments(:more_greetings)] },
|
||||||
lambda { authors(:mary).comments << Comment.create!(:body => "Yay", :post_id => 424242) },
|
lambda { authors(:mary).comments << Comment.create!(:body => "Yay", :post_id => 424242) },
|
||||||
lambda { authors(:mary).comments.delete(authors(:mary).comments.first) },
|
lambda { authors(:mary).comments.delete(authors(:mary).comments.first) },
|
||||||
].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection, &block) }
|
].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_dynamic_find_should_respect_association_order_for_through
|
def test_dynamic_find_should_respect_association_order_for_through
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue