Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge commit 'mainstream/master'

  • Loading branch information...
commit 6c375d95054169e7fa02401a1838664e56a195ae 2 parents 46702e6 + 89b7581
@lifo lifo authored
Showing with 1,864 additions and 1,520 deletions.
  1. +8 −8 actionmailer/CHANGELOG
  2. +374 −374 actionpack/CHANGELOG
  3. +1 −0  actionpack/lib/action_controller.rb
  4. +0 −5 actionpack/lib/action_controller/base.rb
  5. +2 −0  actionpack/lib/action_controller/cookies.rb
  6. +26 −33 actionpack/lib/action_controller/dispatcher.rb
  7. +1 −1  actionpack/lib/action_controller/failsafe.rb
  8. +18 −11 actionpack/lib/action_controller/integration.rb
  9. +16 −0 actionpack/lib/action_controller/lock.rb
  10. +29 −5 actionpack/lib/action_controller/middleware_stack.rb
  11. +2 −7 actionpack/lib/action_controller/rack_process.rb
  12. +1 −1  actionpack/lib/action_controller/rescue.rb
  13. +1 −1  actionpack/lib/action_controller/routing/recognition_optimisation.rb
  14. +28 −1 actionpack/lib/action_controller/session/abstract_store.rb
  15. +43 −17 actionpack/lib/action_controller/session/cookie_store.rb
  16. +0 −29 actionpack/lib/action_controller/session_management.rb
  17. +7 −3 actionpack/lib/action_view/template.rb
  18. +1 −24 actionpack/lib/action_view/template_handlers.rb
  19. +4 −2 actionpack/test/controller/dispatcher_test.rb
  20. +31 −0 actionpack/test/controller/integration_test.rb
  21. +3 −3 actionpack/test/controller/rack_test.rb
  22. +24 −2 actionpack/test/controller/session/cookie_store_test.rb
  23. +0 −84 actionpack/test/controller/session_fixation_test.rb
  24. +392 −392 activerecord/CHANGELOG
  25. +13 −3 activerecord/lib/active_record/association_preload.rb
  26. +33 −10 activerecord/lib/active_record/associations.rb
  27. +5 −1 activerecord/lib/active_record/associations/association_collection.rb
  28. +1 −1  activerecord/lib/active_record/base.rb
  29. +13 −4 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
  30. +67 −1 activerecord/test/cases/associations/eager_test.rb
  31. +3 −0  activerecord/test/cases/associations/has_many_through_associations_test.rb
  32. +16 −1 activerecord/test/cases/associations/has_one_through_associations_test.rb
  33. +4 −0 activerecord/test/cases/helper.rb
  34. +50 −0 activerecord/test/cases/repair_helper.rb
  35. +191 −160 activerecord/test/cases/validations_test.rb
  36. +6 −0 activerecord/test/fixtures/member_types.yml
  37. +3 −1 activerecord/test/fixtures/members.yml
  38. +1 −0  activerecord/test/models/member.rb
  39. +1 −0  activerecord/test/models/member_detail.rb
  40. +3 −0  activerecord/test/models/member_type.rb
  41. +5 −0 activerecord/test/schema/schema.rb
  42. +21 −21 activeresource/CHANGELOG
  43. +88 −88 activesupport/CHANGELOG
  44. +195 −195 railties/CHANGELOG
  45. +1 −1  railties/lib/commands/server.rb
  46. +8 −0 railties/lib/initializer.rb
  47. +2 −1  railties/lib/rails/rack.rb
  48. +31 −0 railties/lib/rails/rack/cascade.rb
  49. +35 −0 railties/lib/rails/rack/log_tailer.rb
  50. +0 −28 railties/lib/rails/rack/logger.rb
  51. +27 −0 railties/lib/rails/rack/metal.rb
  52. +8 −0 railties/lib/rails_generator/generators/components/metal/USAGE
  53. +8 −0 railties/lib/rails_generator/generators/components/metal/metal_generator.rb
  54. +12 −0 railties/lib/rails_generator/generators/components/metal/templates/metal.rb
  55. +1 −1  railties/lib/tasks/middleware.rake
View
16 actionmailer/CHANGELOG
@@ -12,7 +12,7 @@
*2.2.0 [RC1] (October 24th, 2008)*
-* Add layout functionality to mailers [Pratik]
+* Add layout functionality to mailers [Pratik Naik]
Mailer layouts behaves just like controller layouts, except layout names need to
have '_mailer' postfix for them to be automatically picked up.
@@ -24,7 +24,7 @@
* Less verbose mail logging: just recipients for :info log level; the whole email for :debug only. #8000 [iaddict, Tarmo Tänav]
-* Updated TMail to version 1.2.1 [raasdnil]
+* Updated TMail to version 1.2.1 [Mikel Lindsaar]
* Fixed that you don't have to call super in ActionMailer::TestCase#setup #10406 [jamesgolick]
@@ -36,7 +36,7 @@
*2.0.1* (December 7th, 2007)
-* Update ActionMailer so it treats ActionView the same way that ActionController does. Closes #10244 [rick]
+* Update ActionMailer so it treats ActionView the same way that ActionController does. Closes #10244 [Rick Olson]
* Pass the template_root as an array as ActionView's view_path
* Request templates with the "#{mailer_name}/#{action}" as opposed to just "#{action}"
@@ -45,11 +45,11 @@
* Update README to use new smtp settings configuration API. Closes #10060 [psq]
-* Allow ActionMailer subclasses to individually set their delivery method (so two subclasses can have different delivery methods) #10033 [zdennis]
+* Allow ActionMailer subclasses to individually set their delivery method (so two subclasses can have different delivery methods) #10033 [Zach Dennis]
-* Update TMail to v1.1.0. Use an updated version of TMail if available. [mikel]
+* Update TMail to v1.1.0. Use an updated version of TMail if available. [Mikel Lindsaar]
-* Introduce a new base test class for testing Mailers. ActionMailer::TestCase [Koz]
+* Introduce a new base test class for testing Mailers. ActionMailer::TestCase [Michael Koziarski]
* Fix silent failure of rxml templates. #9879 [jstewart]
@@ -84,7 +84,7 @@
*1.3.2* (February 5th, 2007)
-* Deprecate server_settings renaming it to smtp_settings, add sendmail_settings to allow you to override the arguments to and location of the sendmail executable. [Koz]
+* Deprecate server_settings renaming it to smtp_settings, add sendmail_settings to allow you to override the arguments to and location of the sendmail executable. [Michael Koziarski]
*1.3.1* (January 16th, 2007)
@@ -104,7 +104,7 @@
* Tighten rescue clauses. #5985 [james@grayproductions.net]
-* Automatically included ActionController::UrlWriter, such that URL generation can happen within ActionMailer controllers. [DHH]
+* Automatically included ActionController::UrlWriter, such that URL generation can happen within ActionMailer controllers. [David Heinemeier Hansson]
* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
View
748 actionpack/CHANGELOG
374 additions, 374 deletions not shown
View
1  actionpack/lib/action_controller.rb
@@ -56,6 +56,7 @@ def self.load_all!
autoload :Integration, 'action_controller/integration'
autoload :IntegrationTest, 'action_controller/integration'
autoload :Layout, 'action_controller/layout'
+ autoload :Lock, 'action_controller/lock'
autoload :MiddlewareStack, 'action_controller/middleware_stack'
autoload :MimeResponds, 'action_controller/mime_responds'
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
View
5 actionpack/lib/action_controller/base.rb
@@ -1160,13 +1160,8 @@ def expires_now #:doc:
def reset_session #:doc:
request.reset_session
@_session = request.session
- #http://rails.lighthouseapp.com/projects/8994/tickets/1558-memory-problem-on-reset_session-in-around_filter#ticket-1558-1
- #MRI appears to have a GC related memory leak to do with the finalizer that is defined on CGI::Session
- ObjectSpace.undefine_finalizer(@_session)
- response.session = @_session
end
-
private
def render_for_file(template_path, status = nil, layout = nil, locals = {}) #:nodoc:
logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
View
2  actionpack/lib/action_controller/cookies.rb
@@ -67,6 +67,8 @@ def [](name)
cookie = @cookies[name.to_s]
if cookie && cookie.respond_to?(:value)
cookie.size > 1 ? cookie.value : cookie.value[0]
+ else
+ cookie
end
end
View
59 actionpack/lib/action_controller/dispatcher.rb
@@ -2,8 +2,6 @@ module ActionController
# Dispatches requests to the appropriate controller and takes care of
# reloading the app after each request when Dependencies.load? is true.
class Dispatcher
- @@guard = Mutex.new
-
class << self
def define_dispatcher_callbacks(cache_classes)
unless cache_classes
@@ -46,40 +44,49 @@ def to_prepare(identifier = nil, &block)
cattr_accessor :middleware
self.middleware = MiddlewareStack.new do |middleware|
+ middleware.use "ActionController::Lock", :if => lambda {
+ !ActionController::Base.allow_concurrency
+ }
middleware.use "ActionController::Failsafe"
- middleware.use "ActionController::SessionManagement::Middleware"
+
+ ["ActionController::Session::CookieStore",
+ "ActionController::Session::MemCacheStore",
+ "ActiveRecord::SessionStore"].each do |store|
+ middleware.use(store, ActionController::Base.session_options,
+ :if => lambda {
+ if session_store = ActionController::Base.session_store
+ session_store.name == store
+ end
+ }
+ )
+ end
end
include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
- # DEPRECATE: Remove arguments
+ # DEPRECATE: Remove arguments, since they are only used by CGI
def initialize(output = $stdout, request = nil, response = nil)
- @output, @request, @response = output, request, response
+ @output = output
@app = @@middleware.build(lambda { |env| self.dup._call(env) })
end
- def dispatch_unlocked
+ def dispatch
begin
run_callbacks :before_dispatch
- handle_request
+ controller = Routing::Routes.recognize(@request)
+ controller.process(@request, @response).to_a
rescue Exception => exception
- failsafe_rescue exception
+ if controller ||= (::ApplicationController rescue Base)
+ controller.process_with_exception(@request, @response, exception).to_a
+ else
+ raise exception
+ end
ensure
run_callbacks :after_dispatch, :enumerator => :reverse_each
end
end
- def dispatch
- if ActionController::Base.allow_concurrency
- dispatch_unlocked
- else
- @@guard.synchronize do
- dispatch_unlocked
- end
- end
- end
-
# DEPRECATE: Remove CGI support
def dispatch_cgi(cgi, session_options)
CGIHandler.dispatch_cgi(self, cgi, @output)
@@ -118,22 +125,8 @@ def flush_logger
def checkin_connections
# Don't return connection (and peform implicit rollback) if this request is a part of integration test
# TODO: This callback should have direct access to env
- return if @request.key?("action_controller.test")
+ return if @request.key?("rack.test")
ActiveRecord::Base.clear_active_connections!
end
-
- protected
- def handle_request
- @controller = Routing::Routes.recognize(@request)
- @controller.process(@request, @response).out
- end
-
- def failsafe_rescue(exception)
- if @controller ||= (::ApplicationController rescue Base)
- @controller.process_with_exception(@request, @response, exception).out
- else
- raise exception
- end
- end
end
end
View
2  actionpack/lib/action_controller/failsafe.rb
@@ -11,7 +11,7 @@ def call(env)
@app.call(env)
rescue Exception => exception
# Reraise exception in test environment
- if env["action_controller.test"]
+ if env["rack.test"]
raise exception
else
failsafe_response(exception)
View
29 actionpack/lib/action_controller/integration.rb
@@ -276,6 +276,7 @@ def process(method, path, parameters = nil, headers = nil)
"SCRIPT_NAME" => "",
"REQUEST_URI" => path,
+ "PATH_INFO" => path,
"HTTP_HOST" => host,
"REMOTE_ADDR" => remote_addr,
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
@@ -290,7 +291,7 @@ def process(method, path, parameters = nil, headers = nil)
"rack.multiprocess" => true,
"rack.run_once" => false,
- "action_controller.test" => true
+ "rack.test" => true
)
(headers || {}).each do |key, value|
@@ -310,16 +311,6 @@ def process(method, path, parameters = nil, headers = nil)
status, headers, body = app.call(env)
@request_count += 1
- if @controller = ActionController::Base.last_instantiation
- @request = @controller.request
- @response = @controller.response
-
- # Decorate the response with the standard behavior of the
- # TestResponse so that things like assert_response can be
- # used in integration tests.
- @response.extend(TestResponseBehavior)
- end
-
@html_document = nil
@status = status.to_i
@@ -335,6 +326,22 @@ def process(method, path, parameters = nil, headers = nil)
@body = ""
body.each { |part| @body << part }
+ if @controller = ActionController::Base.last_instantiation
+ @request = @controller.request
+ @response = @controller.response
+ else
+ # Decorate responses from Rack Middleware and Rails Metal
+ # as an AbstractResponse for the purposes of integration testing
+ @response = AbstractResponse.new
+ @response.headers = @headers.merge('Status' => status.to_s)
+ @response.body = @body
+ end
+
+ # Decorate the response with the standard behavior of the
+ # TestResponse so that things like assert_response can be
+ # used in integration tests.
+ @response.extend(TestResponseBehavior)
+
return @status
rescue MultiPartNeededException
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
View
16 actionpack/lib/action_controller/lock.rb
@@ -0,0 +1,16 @@
+module ActionController
+ class Lock
+ FLAG = 'rack.multithread'.freeze
+
+ def initialize(app, lock = Mutex.new)
+ @app, @lock = app, lock
+ end
+
+ def call(env)
+ old, env[FLAG] = env[FLAG], false
+ @lock.synchronize { @app.call(env) }
+ ensure
+ env[FLAG] = old
+ end
+ end
+end
View
34 actionpack/lib/action_controller/middleware_stack.rb
@@ -1,19 +1,39 @@
module ActionController
class MiddlewareStack < Array
class Middleware
- attr_reader :klass, :args, :block
+ attr_reader :args, :block
def initialize(klass, *args, &block)
- if klass.is_a?(Class)
- @klass = klass
+ @klass = klass
+
+ options = args.extract_options!
+ if options.has_key?(:if)
+ @conditional = options.delete(:if)
else
- @klass = klass.to_s.constantize
+ @conditional = true
end
+ args << options unless options.empty?
@args = args
@block = block
end
+ def klass
+ if @klass.is_a?(Class)
+ @klass
+ else
+ @klass.to_s.constantize
+ end
+ end
+
+ def active?
+ if @conditional.respond_to?(:call)
+ @conditional.call
+ else
+ @conditional
+ end
+ end
+
def ==(middleware)
case middleware
when Middleware
@@ -50,8 +70,12 @@ def use(*args, &block)
push(middleware)
end
+ def active
+ find_all { |middleware| middleware.active? }
+ end
+
def build(app)
- reverse.inject(app) { |a, e| e.build(a) }
+ active.reverse.inject(app) { |a, e| e.build(a) }
end
end
end
View
9 actionpack/lib/action_controller/rack_process.rb
@@ -83,11 +83,7 @@ def status
@status || super
end
- def out(&block)
- # Nasty hack because CGI sessions are closed after the normal
- # prepare! statement
- set_cookies!
-
+ def to_a(&block)
@block = block
@status = headers.delete("Status")
if [204, 304].include?(status.to_i)
@@ -97,7 +93,6 @@ def out(&block)
[status, headers.to_hash, self]
end
end
- alias to_a out
def each(&callback)
if @body.respond_to?(:call)
@@ -132,7 +127,7 @@ def prepare!
convert_language!
convert_expires!
set_status!
- # set_cookies!
+ set_cookies!
end
private
View
2  actionpack/lib/action_controller/rescue.rb
@@ -104,7 +104,7 @@ def render_optional_error_file(status_code)
status = interpret_status(status_code)
path = "#{Rails.public_path}/#{status[0,3]}.html"
if File.exist?(path)
- render :file => path, :status => status
+ render :file => path, :status => status, :content_type => Mime::HTML
else
head status
end
View
2  actionpack/lib/action_controller/routing/recognition_optimisation.rb
@@ -56,7 +56,7 @@ def recognize_path(path, environment={})
result = recognize_optimized(path, environment) and return result
# Route was not recognized. Try to find out why (maybe wrong verb).
- allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } }
+ allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, environment.merge(:method => verb)) } }
if environment[:method] && !HTTP_METHODS.include?(environment[:method])
raise NotImplemented.new(*allows)
View
29 actionpack/lib/action_controller/session/abstract_store.rb
@@ -11,6 +11,7 @@ class AbstractStore
class SessionHash < Hash
def initialize(by, env)
+ super()
@by = by
@env = env
@loaded = false
@@ -21,6 +22,13 @@ def id
@id
end
+ def session_id
+ ActiveSupport::Deprecation.warn(
+ "ActionController::Session::AbstractStore::SessionHash#session_id" +
+ "has been deprecated.Please use #id instead.", caller)
+ id
+ end
+
def [](key)
load! unless @loaded
super
@@ -37,6 +45,13 @@ def to_hash
h
end
+ def data
+ ActiveSupport::Deprecation.warn(
+ "ActionController::Session::AbstractStore::SessionHash#data" +
+ "has been deprecated.Please use #to_hash instead.", caller)
+ to_hash
+ end
+
private
def load!
@id, session = @by.send(:load_session, @env)
@@ -46,7 +61,7 @@ def load!
end
DEFAULT_OPTIONS = {
- :key => 'rack.session',
+ :key => '_session_id',
:path => '/',
:domain => nil,
:expire_after => nil,
@@ -56,6 +71,18 @@ def load!
}
def initialize(app, options = {})
+ # Process legacy CGI options
+ options = options.symbolize_keys
+ if options.has_key?(:session_path)
+ options[:path] = options.delete(:session_path)
+ end
+ if options.has_key?(:session_key)
+ options[:key] = options.delete(:session_key)
+ end
+ if options.has_key?(:session_http_only)
+ options[:httponly] = options.delete(:session_http_only)
+ end
+
@app = app
@default_options = DEFAULT_OPTIONS.merge(options)
@key = @default_options[:key]
View
60 actionpack/lib/action_controller/session/cookie_store.rb
@@ -41,9 +41,11 @@ class CookieStore
SECRET_MIN_LENGTH = 30 # characters
DEFAULT_OPTIONS = {
- :domain => nil,
- :path => "/",
- :expire_after => nil
+ :key => '_session_id',
+ :domain => nil,
+ :path => "/",
+ :expire_after => nil,
+ :httponly => false
}.freeze
ENV_SESSION_KEY = "rack.session".freeze
@@ -56,6 +58,18 @@ class CookieOverflow < StandardError; end
def initialize(app, options = {})
options = options.dup
+ # Process legacy CGI options
+ options = options.symbolize_keys
+ if options.has_key?(:session_path)
+ options[:path] = options.delete(:session_path)
+ end
+ if options.has_key?(:session_key)
+ options[:key] = options.delete(:session_key)
+ end
+ if options.has_key?(:session_http_only)
+ options[:httponly] = options.delete(:session_http_only)
+ end
+
@app = app
# The session_key option is required.
@@ -74,21 +88,12 @@ def initialize(app, options = {})
freeze
end
- class SessionHash < AbstractStore::SessionHash
- private
- def load!
- session = @by.send(:load_session, @env)
- replace(session)
- @loaded = true
- end
- end
-
def call(env)
- session_data = SessionHash.new(self, env)
+ session_data = AbstractStore::SessionHash.new(self, env)
original_value = session_data.dup
env[ENV_SESSION_KEY] = session_data
- env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
+ env[ENV_SESSION_OPTIONS_KEY] = @default_options
status, headers, body = @app.call(env)
@@ -142,17 +147,18 @@ def build_cookie(key, value)
def load_session(env)
request = Rack::Request.new(env)
session_data = request.cookies[@key]
- unmarshal(session_data) || {}
+ data = unmarshal(session_data) || persistent_session_id!({})
+ [data[:session_id], data]
end
# Marshal a session hash into safe cookie data. Include an integrity hash.
def marshal(session)
- @verifier.generate(session)
+ @verifier.generate( persistent_session_id!(session))
end
# Unmarshal cookie data to a hash and verify its integrity.
def unmarshal(cookie)
- @verifier.verify(cookie) if cookie
+ persistent_session_id!(@verifier.verify(cookie)) if cookie
rescue ActiveSupport::MessageVerifier::InvalidSignature
nil
end
@@ -195,6 +201,26 @@ def verifier_for(secret, digest)
key = secret.respond_to?(:call) ? secret.call : secret
ActiveSupport::MessageVerifier.new(key, digest)
end
+
+ def generate_sid
+ ActiveSupport::SecureRandom.hex(16)
+ end
+
+ def persistent_session_id!(data)
+ (data ||= {}).merge!(inject_persistent_session_id(data))
+ end
+
+ def inject_persistent_session_id(data)
+ requires_session_id?(data) ? { :session_id => generate_sid } : {}
+ end
+
+ def requires_session_id?(data)
+ if data
+ data.respond_to?(:key?) && !data.key?(:session_id)
+ else
+ true
+ end
+ end
end
end
end
View
29 actionpack/lib/action_controller/session_management.rb
@@ -6,35 +6,6 @@ def self.included(base)
end
end
- class Middleware
- DEFAULT_OPTIONS = {
- :path => "/",
- :key => "_session_id",
- :httponly => true,
- }.freeze
-
- def self.new(app)
- cgi_options = ActionController::Base.session_options
- options = cgi_options.symbolize_keys
- options = DEFAULT_OPTIONS.merge(options)
- if options.has_key?(:session_path)
- options[:path] = options.delete(:session_path)
- end
- if options.has_key?(:session_key)
- options[:key] = options.delete(:session_key)
- end
- if options.has_key?(:session_http_only)
- options[:httponly] = options.delete(:session_http_only)
- end
-
- if store = ActionController::Base.session_store
- store.new(app, options)
- else # Sessions disabled
- lambda { |env| app.call(env) }
- end
- end
- end
-
module ClassMethods
# Set the session store to be used for keeping the session data between requests.
# By default, sessions are stored in browser cookies (<tt>:cookie_store</tt>),
View
10 actionpack/lib/action_view/template.rb
@@ -98,6 +98,10 @@ def load!
end
private
+ def valid_extension?(extension)
+ Template.template_handler_extensions.include?(extension)
+ end
+
def find_full_path(path, load_paths)
load_paths = Array(load_paths) + [nil]
load_paths.each do |load_path|
@@ -111,11 +115,11 @@ def find_full_path(path, load_paths)
# [base_path, name, format, extension]
def split(file)
if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/)
- if Template.valid_extension?(m[5]) # Multipart formats
+ if valid_extension?(m[5]) # Multipart formats
[m[1], m[2], "#{m[3]}.#{m[4]}", m[5]]
- elsif Template.valid_extension?(m[4]) # Single format
+ elsif valid_extension?(m[4]) # Single format
[m[1], m[2], m[3], m[4]]
- elsif Template.valid_extension?(m[3]) # No format
+ elsif valid_extension?(m[3]) # No format
[m[1], m[2], nil, m[3]]
else # No extension
[m[1], m[2], m[3], nil]
View
25 actionpack/lib/action_view/template_handlers.rb
@@ -28,10 +28,6 @@ def register_template_handler(extension, klass)
@@template_handlers[extension.to_sym] = klass
end
- def valid_extension?(extension)
- template_handler_extensions.include?(extension) || init_path_for_extension(extension)
- end
-
def template_handler_extensions
@@template_handlers.keys.map(&:to_s).sort
end
@@ -42,26 +38,7 @@ def register_default_template_handler(extension, klass)
end
def handler_class_for_extension(extension)
- (extension && @@template_handlers[extension.to_sym] || autoload_handler_class(extension)) ||
- @@default_template_handlers
+ (extension && @@template_handlers[extension.to_sym]) || @@default_template_handlers
end
-
- private
- def autoload_handler_class(extension)
- return if Gem.loaded_specs[extension]
- return unless init_path = init_path_for_extension(extension)
- Gem.activate(extension)
- load(init_path)
- handler_class_for_extension(extension)
- end
-
- # Returns the path to the rails/init.rb file for the given extension,
- # or nil if no gem provides it.
- def init_path_for_extension(extension)
- return unless spec = Gem.searcher.find(extension.to_s)
- returning File.join(spec.full_gem_path, 'rails', 'init.rb') do |path|
- return unless File.file?(path)
- end
- end
end
end
View
6 actionpack/test/controller/dispatcher_test.rb
@@ -50,7 +50,7 @@ def log_failsafe_exception(status, exception); end
end
def test_failsafe_response
- Dispatcher.any_instance.expects(:dispatch_unlocked).raises('b00m')
+ Dispatcher.any_instance.expects(:dispatch).raises('b00m')
ActionController::Failsafe.any_instance.expects(:log_failsafe_exception)
assert_nothing_raised do
@@ -96,7 +96,9 @@ def test_to_prepare_with_identifier_replaces
private
def dispatch(cache_classes = true)
- Dispatcher.any_instance.stubs(:handle_request).returns([200, {}, 'response'])
+ controller = mock()
+ controller.stubs(:process).returns([200, {}, 'response'])
+ ActionController::Routing::Routes.stubs(:recognize).returns(controller)
Dispatcher.define_dispatcher_callbacks(cache_classes)
@dispatcher.call({})
end
View
31 actionpack/test/controller/integration_test.rb
@@ -373,4 +373,35 @@ def with_test_route_set
end
end
+class MetalTest < ActionController::IntegrationTest
+ class Poller
+ def self.call(env)
+ if env["PATH_INFO"] =~ /^\/success/
+ [200, {"Content-Type" => "text/plain"}, "Hello World!"]
+ else
+ [404, {"Content-Type" => "text/plain"}, '']
+ end
+ end
+ end
+
+ def setup
+ @integration_session = ActionController::Integration::Session.new(Poller)
+ end
+
+ def test_successful_get
+ get "/success"
+ assert_response 200
+ assert_response :success
+ assert_response :ok
+ assert_equal "Hello World!", response.body
+ end
+
+ def test_failed_get
+ get "/failure"
+ assert_response 404
+ assert_response :not_found
+ assert_equal '', response.body
+ end
+end
+
end
View
6 actionpack/test/controller/rack_test.rb
@@ -236,7 +236,7 @@ def test_simple_output
@response.body = "Hello, World!"
@response.prepare!
- status, headers, body = @response.out
+ status, headers, body = @response.to_a
assert_equal "200 OK", status
assert_equal({
"Content-Type" => "text/html; charset=utf-8",
@@ -257,7 +257,7 @@ def test_streaming_block
end
@response.prepare!
- status, headers, body = @response.out
+ status, headers, body = @response.to_a
assert_equal "200 OK", status
assert_equal({"Content-Type" => "text/html; charset=utf-8", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers)
@@ -293,6 +293,6 @@ def test_status
private
def response_headers
@response.prepare!
- @response.out[1]
+ @response.to_a[1]
end
end
View
26 actionpack/test/controller/session/cookie_store_test.rb
@@ -9,6 +9,8 @@ class CookieStoreTest < ActionController::IntegrationTest
CookieStoreApp = ActionController::Session::CookieStore.new(DispatcherApp,
:key => SessionKey, :secret => SessionSecret)
+ Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1')
+
SignedBar = "BAh7BjoIZm9vIghiYXI%3D--" +
"fef868465920f415f2c0652d6910d3af288a0367"
@@ -17,9 +19,13 @@ def no_session_access
head :ok
end
+ def persistent_session_id
+ render :text => session[:session_id]
+ end
+
def set_session_value
session[:foo] = "bar"
- head :ok
+ render :text => Marshal.dump(session.to_hash)
end
def get_session_value
@@ -83,7 +89,8 @@ def test_setting_session_value
with_test_route_set do
get '/set_session_value'
assert_response :success
- assert_equal ["_myapp_session=#{SignedBar}; path=/"],
+ session_payload = Verifier.generate( Marshal.load(response.body) )
+ assert_equal ["_myapp_session=#{session_payload}; path=/"],
headers['Set-Cookie']
end
end
@@ -132,6 +139,21 @@ def test_doesnt_write_session_cookie_if_session_is_unchanged
end
end
+ def test_persistent_session_id
+ with_test_route_set do
+ cookies[SessionKey] = SignedBar
+ get '/persistent_session_id'
+ assert_response :success
+ assert_equal response.body.size, 32
+ session_id = response.body
+ get '/persistent_session_id'
+ assert_equal session_id, response.body
+ reset!
+ get '/persistent_session_id'
+ assert_not_equal session_id, response.body
+ end
+ end
+
private
def with_test_route_set
with_routing do |set|
View
84 actionpack/test/controller/session_fixation_test.rb
@@ -1,84 +0,0 @@
-# require 'abstract_unit'
-#
-# class SessionFixationTest < ActionController::IntegrationTest
-# class TestController < ActionController::Base
-# session :session_key => '_myapp_session_id',
-# :secret => CGI::Session.generate_unique_id,
-# :except => :default_session_key
-#
-# session :cookie_only => false,
-# :only => :allow_session_fixation
-#
-# def default_session_key
-# render :text => "default_session_key"
-# end
-#
-# def custom_session_key
-# render :text => "custom_session_key: #{params[:id]}"
-# end
-#
-# def allow_session_fixation
-# render :text => "allow_session_fixation"
-# end
-#
-# def rescue_action(e) raise end
-# end
-#
-# def setup
-# @controller = TestController.new
-# end
-#
-# def test_should_be_able_to_make_a_successful_request
-# with_test_route_set do
-# assert_nothing_raised do
-# get '/custom_session_key', :id => "1"
-# end
-# assert_equal 'custom_session_key: 1', @controller.response.body
-# assert_not_nil @controller.session
-# end
-# end
-#
-# def test_should_catch_session_fixation_attempt
-# with_test_route_set do
-# assert_raises(ActionController::RackRequest::SessionFixationAttempt) do
-# get '/custom_session_key', :_myapp_session_id => "42"
-# end
-# assert_nil @controller.session
-# end
-# end
-#
-# def test_should_not_catch_session_fixation_attempt_when_cookie_only_setting_is_disabled
-# with_test_route_set do
-# assert_nothing_raised do
-# get '/allow_session_fixation', :_myapp_session_id => "42"
-# end
-# assert !@controller.response.body.blank?
-# assert_not_nil @controller.session
-# end
-# end
-#
-# def test_should_catch_session_fixation_attempt_with_default_session_key
-# # using the default session_key is not possible with cookie store
-# ActionController::Base.session_store = :p_store
-#
-# with_test_route_set do
-# assert_raises ActionController::RackRequest::SessionFixationAttempt do
-# get '/default_session_key', :_session_id => "42"
-# end
-# assert_nil @controller.response
-# assert_nil @controller.session
-# end
-# end
-#
-# private
-# def with_test_route_set
-# with_routing do |set|
-# set.draw do |map|
-# map.with_options :controller => "session_fixation_test/test" do |c|
-# c.connect "/:action"
-# end
-# end
-# yield
-# end
-# end
-# end
View
784 activerecord/CHANGELOG
392 additions, 392 deletions not shown
View
16 activerecord/lib/active_record/association_preload.rb
@@ -204,9 +204,18 @@ def preload_has_one_association(records, reflection, preload_options={})
unless through_records.empty?
source = reflection.source_reflection.name
through_records.first.class.preload_associations(through_records, source)
- through_records.each do |through_record|
- add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
- reflection.name, through_record.send(source))
+ if through_reflection.macro == :belongs_to
+ rev_id_to_record_map, rev_ids = construct_id_map(records, through_primary_key)
+ rev_primary_key = through_reflection.klass.primary_key
+ through_records.each do |through_record|
+ add_preloaded_record_to_collection(rev_id_to_record_map[through_record[rev_primary_key].to_s],
+ reflection.name, through_record.send(source))
+ end
+ else
+ through_records.each do |through_record|
+ add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
+ reflection.name, through_record.send(source))
+ end
end
end
else
@@ -307,6 +316,7 @@ def preload_belongs_to_association(records, reflection, preload_options={})
klasses_and_ids.each do |klass_and_id|
klass_name, id_map = *klass_and_id
+ next if id_map.empty?
klass = klass_name.constantize
table_name = klass.quoted_table_name
View
43 activerecord/lib/active_record/associations.rb
@@ -1731,6 +1731,11 @@ def construct_finder_sql_for_association_limiting(options, join_dependency)
return sanitize_sql(sql)
end
+ def tables_in_string(string)
+ return [] if string.blank?
+ string.scan(/([\.a-zA-Z_]+).?\./).flatten
+ end
+
def conditions_tables(options)
# look in both sets of conditions
conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
@@ -1741,37 +1746,55 @@ def conditions_tables(options)
else all << cond
end
end
- conditions.join(' ').scan(/([\.a-zA-Z_]+).?\./).flatten
+ tables_in_string(conditions.join(' '))
end
def order_tables(options)
order = [options[:order], scope(:find, :order) ].join(", ")
return [] unless order && order.is_a?(String)
- order.scan(/([\.a-zA-Z_]+).?\./).flatten
+ tables_in_string(order)
end
def selects_tables(options)
select = options[:select]
return [] unless select && select.is_a?(String)
- select.scan(/"?([\.a-zA-Z_]+)"?.?\./).flatten
+ tables_in_string(select)
+ end
+
+ def joined_tables(options)
+ scope = scope(:find)
+ joins = options[:joins]
+ merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
+ [table_name] + case merged_joins
+ when Symbol, Hash, Array
+ if array_of_strings?(merged_joins)
+ tables_in_string(merged_joins.join(' '))
+ else
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
+ join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact
+ end
+ else
+ tables_in_string(merged_joins)
+ end
end
# Checks if the conditions reference a table other than the current model table
- def include_eager_conditions?(options, tables = nil)
- ((tables || conditions_tables(options)) - [table_name]).any?
+ def include_eager_conditions?(options, tables = nil, joined_tables = nil)
+ ((tables || conditions_tables(options)) - (joined_tables || joined_tables(options))).any?
end
# Checks if the query order references a table other than the current model's table.
- def include_eager_order?(options, tables = nil)
- ((tables || order_tables(options)) - [table_name]).any?
+ def include_eager_order?(options, tables = nil, joined_tables = nil)
+ ((tables || order_tables(options)) - (joined_tables || joined_tables(options))).any?
end
- def include_eager_select?(options)
- (selects_tables(options) - [table_name]).any?
+ def include_eager_select?(options, joined_tables = nil)
+ (selects_tables(options) - (joined_tables || joined_tables(options))).any?
end
def references_eager_loaded_tables?(options)
- include_eager_order?(options) || include_eager_conditions?(options) || include_eager_select?(options)
+ joined_tables = joined_tables(options)
+ include_eager_order?(options, nil, joined_tables) || include_eager_conditions?(options, nil, joined_tables) || include_eager_select?(options, joined_tables)
end
def using_limitable_reflections?(reflections)
View
6 activerecord/lib/active_record/associations/association_collection.rb
@@ -83,7 +83,11 @@ def last(*args)
def to_ary
load_target
- @target.to_ary
+ if @target.is_a?(Array)
+ @target.to_ary
+ else
+ Array(@target)
+ end
end
def reset
View
2  activerecord/lib/active_record/base.rb
@@ -1828,7 +1828,7 @@ def self.#{method_id}(*args)
else
find(:#{finder}, options.merge(finder_options))
end
- #{'result || raise(RecordNotFound)' if bang}
+ #{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect {|pair| "#{pair.first} = #{pair.second}"}.join(\', \')}")' if bang}
end
}, __FILE__, __LINE__
send(method_id, *arguments)
View
17 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -308,6 +308,7 @@ def select_rows(sql, name = nil)
rows
end
+ # Executes a SQL query and returns a MySQL::Result object. Note that you have to free the Result object after you're done using it.
def execute(sql, name = nil) #:nodoc:
log(sql, name) { @connection.query(sql) }
rescue ActiveRecord::StatementInvalid => exception
@@ -414,7 +415,9 @@ def collation
def tables(name = nil) #:nodoc:
tables = []
- execute("SHOW TABLES", name).each { |field| tables << field[0] }
+ result = execute("SHOW TABLES", name)
+ result.each { |field| tables << field[0] }
+ result.free
tables
end
@@ -425,7 +428,8 @@ def drop_table(table_name, options = {})
def indexes(table_name, name = nil)#:nodoc:
indexes = []
current_index = nil
- execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name).each do |row|
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
+ result.each do |row|
if current_index != row[2]
next if row[2] == "PRIMARY" # skip the primary key
current_index = row[2]
@@ -434,13 +438,16 @@ def indexes(table_name, name = nil)#:nodoc:
indexes.last.columns << row[4]
end
+ result.free
indexes
end
def columns(table_name, name = nil)#:nodoc:
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
columns = []
- execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
+ result = execute(sql, name)
+ result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
+ result.free
columns
end
@@ -521,9 +528,11 @@ def show_variable(name)
# Returns a table's primary key and belonging sequence.
def pk_and_sequence_for(table) #:nodoc:
keys = []
- execute("describe #{quote_table_name(table)}").each_hash do |h|
+ result = execute("describe #{quote_table_name(table)}")
+ result.each_hash do |h|
keys << h["Field"]if h["Key"] == "PRI"
end
+ result.free
keys.length == 1 ? [keys.first, nil] : nil
end
View
68 activerecord/test/cases/associations/eager_test.rb
@@ -1,6 +1,7 @@
require "cases/helper"
require 'models/post'
require 'models/tagging'
+require 'models/tag'
require 'models/comment'
require 'models/author'
require 'models/category'
@@ -145,7 +146,7 @@ def test_finding_with_includes_on_belongs_to_association_with_same_include_inclu
def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once
post = posts(:welcome)
post.update_attributes!(:author => nil)
- post = assert_queries(2) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author which is null so no query for the address
+ post = assert_queries(1) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author which is null so no query for the author or address
assert_no_queries do
assert_equal nil, post.author_with_address
end
@@ -705,4 +706,69 @@ def test_conditions_on_join_table_with_include_and_limit
def test_order_on_join_table_with_include_and_limit
assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size
end
+
+ def test_eager_loading_with_order_on_joined_table_preloads
+ posts = assert_queries(2) do
+ Post.find(:all, :joins => :comments, :include => :author, :order => 'comments.id DESC')
+ end
+ assert_equal posts(:eager_other), posts[0]
+ assert_equal authors(:mary), assert_no_queries { posts[0].author}
+ end
+
+ def test_eager_loading_with_conditions_on_joined_table_preloads
+ posts = assert_queries(2) do
+ Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ end
+ assert_equal [posts(:welcome)], posts
+ assert_equal authors(:david), assert_no_queries { posts[0].author}
+
+ posts = assert_queries(2) do
+ Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ end
+ assert_equal [posts(:welcome)], posts
+ assert_equal authors(:david), assert_no_queries { posts[0].author}
+
+ posts = assert_queries(2) do
+ Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'")
+ end
+ assert_equal posts(:welcome, :thinking), posts
+
+ posts = assert_queries(2) do
+ Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2")
+ end
+ assert_equal posts(:welcome, :thinking), posts
+
+ end
+
+ def test_eager_loading_with_conditions_on_string_joined_table_preloads
+ posts = assert_queries(2) do
+ Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ end
+ assert_equal [posts(:welcome)], posts
+ assert_equal authors(:david), assert_no_queries { posts[0].author}
+
+ posts = assert_queries(2) do
+ Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ end
+ assert_equal [posts(:welcome)], posts
+ assert_equal authors(:david), assert_no_queries { posts[0].author}
+
+ end
+
+ def test_eager_loading_with_select_on_joined_table_preloads
+ posts = assert_queries(2) do
+ Post.find(:all, :select => 'posts.*, authors.name as author_name', :include => :comments, :joins => :author, :order => 'posts.id')
+ end
+ assert_equal 'David', posts[0].author_name
+ assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments}
+ end
+
+ def test_eager_loading_with_conditions_on_join_model_preloads
+ authors = assert_queries(2) do
+ Author.find(:all, :include => :author_address, :joins => :comments, :conditions => "posts.title like 'Welcome%'")
+ end
+ assert_equal authors(:david), authors[0]
+ assert_equal author_addresses(:david_address), authors[0].author_address
+ end
+
end
View
3  activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -3,6 +3,9 @@
require 'models/person'
require 'models/reader'
require 'models/comment'
+require 'models/tag'
+require 'models/tagging'
+require 'models/author'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
fixtures :posts, :readers, :people, :comments, :authors
View
17 activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'models/club'
+require 'models/member_type'
require 'models/member'
require 'models/membership'
require 'models/sponsor'
@@ -7,7 +8,7 @@
require 'models/member_detail'
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :members, :clubs, :memberships, :sponsors, :organizations
+ fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations
def setup
@member = members(:groucho)
@@ -158,4 +159,18 @@ def test_reassigning_has_one_through
assert @new_organization.members.include?(@member)
end
+ def test_preloading_has_one_through_on_belongs_to
+ assert_not_nil @member.member_type
+ @organization = organizations(:nsa)
+ @member_detail = MemberDetail.new
+ @member.member_detail = @member_detail
+ @member.organization = @organization
+ @member_details = assert_queries(3) do
+ MemberDetail.find(:all, :include => :member_type)
+ end
+ @new_detail = @member_details[0]
+ assert @new_detail.loaded_member_type?
+ assert_not_nil assert_no_queries { @new_detail.member_type }
+ end
+
end
View
4 activerecord/test/cases/helper.rb
@@ -9,6 +9,8 @@
require 'active_record/fixtures'
require 'connection'
+require 'cases/repair_helper'
+
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
@@ -60,6 +62,8 @@ def try_to_load_dependency_with_silence(*args)
class ActiveSupport::TestCase
include ActiveRecord::TestFixtures
+ include ActiveRecord::Testing::RepairHelper
+
self.fixture_path = FIXTURES_ROOT
self.use_instantiated_fixtures = false
self.use_transactional_fixtures = true
View
50 activerecord/test/cases/repair_helper.rb
@@ -0,0 +1,50 @@
+module ActiveRecord
+ module Testing
+ module RepairHelper
+ def self.included(base)
+ base.class_eval do
+ extend ClassMethods
+ end
+ end
+
+ module Toolbox
+ def self.record_validations(*model_classes)
+ model_classes.inject({}) do |repair, klass|
+ repair[klass] ||= {}
+ [:validate, :validate_on_create, :validate_on_update].each do |callback|
+ the_callback = klass.instance_variable_get("@#{callback.to_s}_callbacks")
+ repair[klass][callback] = (the_callback.nil? ? nil : the_callback.dup)
+ end
+ repair
+ end
+ end
+
+ def self.reset_validations(recorded)
+ recorded.each do |klass, repairs|
+ [:validate, :validate_on_create, :validate_on_update].each do |callback|
+ klass.instance_variable_set("@#{callback.to_s}_callbacks", repairs[callback])
+ end
+ end
+ end
+ end
+
+ module ClassMethods
+ def repair_validations(*model_classes)
+ setup do
+ @validation_repairs = ActiveRecord::Testing::RepairHelper::Toolbox.record_validations(*model_classes)
+ end
+ teardown do
+ ActiveRecord::Testing::RepairHelper::Toolbox.reset_validations(@validation_repairs)
+ end
+ end
+ end
+
+ def repair_validations(*model_classes, &block)
+ validation_repairs = ActiveRecord::Testing::RepairHelper::Toolbox.record_validations(*model_classes)
+ return block.call
+ ensure
+ ActiveRecord::Testing::RepairHelper::Toolbox.reset_validations(validation_repairs)
+ end
+ end
+ end
+end
View
351 activerecord/test/cases/validations_test.rb
@@ -6,6 +6,8 @@
require 'models/developer'
require 'models/warehouse_thing'
require 'models/guid'
+require 'models/owner'
+require 'models/pet'
# The following methods in Topic are used in test_conditional_validation_*
class Topic
@@ -31,10 +33,6 @@ class UniqueReply < Reply
validates_uniqueness_of :content, :scope => 'parent_id'
end
-class PlagiarizedReply < Reply
- validates_acceptance_of :author_name
-end
-
class SillyUniqueReply < UniqueReply
end
@@ -58,11 +56,9 @@ class Thaumaturgist < IneptWizard
class ValidationsTest < ActiveRecord::TestCase
fixtures :topics, :developers, 'warehouse-things'
- def setup
- Topic.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
- Topic.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
- Topic.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
- end
+ # Most of the tests mess with the validations of Topic, so lets repair it all the time.
+ # Other classes we mess with will be dealt with in the specific tests
+ repair_validations(Topic)
def test_single_field_validation
r = Reply.new
@@ -134,7 +130,7 @@ def test_exception_on_create_bang_many
Reply.create!([ { "title" => "OK" }, { "title" => "Wrong Create" }])
end
end
-
+
def test_exception_on_create_bang_with_block
assert_raises(ActiveRecord::RecordInvalid) do
Reply.create!({ "title" => "OK" }) do |r|
@@ -142,7 +138,7 @@ def test_exception_on_create_bang_with_block
end
end
end
-
+
def test_exception_on_create_bang_many_with_block
assert_raises(ActiveRecord::RecordInvalid) do
Reply.create!([{ "title" => "OK" }, { "title" => "Wrong Create" }]) do |r|
@@ -229,21 +225,16 @@ def test_create_without_validation_bang
end
def test_validates_each
- perform = true
hits = 0
Topic.validates_each(:title, :content, [:title, :content]) do |record, attr|
- if perform
- record.errors.add attr, 'gotcha'
- hits += 1
- end
+ record.errors.add attr, 'gotcha'
+ hits += 1
end
t = Topic.new("title" => "valid", "content" => "whatever")
assert !t.save
assert_equal 4, hits
assert_equal %w(gotcha gotcha), t.errors.on(:title)
assert_equal %w(gotcha gotcha), t.errors.on(:content)
- ensure
- perform = false
end
def test_no_title_confirmation
@@ -315,8 +306,12 @@ def test_terms_of_service_agreement_with_accept_value
end
def test_validates_acceptance_of_as_database_column
- reply = PlagiarizedReply.create("author_name" => "Dan Brown")
- assert_equal "Dan Brown", reply["author_name"]
+ repair_validations(Reply) do
+ Reply.validates_acceptance_of(:author_name)
+
+ reply = Reply.create("author_name" => "Dan Brown")
+ assert_equal "Dan Brown", reply["author_name"]
+ end
end
def test_validates_acceptance_of_with_non_existant_table
@@ -372,22 +367,24 @@ def test_validates_uniquness_with_newline_chars
end
def test_validate_uniqueness_with_scope
- Reply.validates_uniqueness_of(:content, :scope => "parent_id")
+ repair_validations(Reply) do
+ Reply.validates_uniqueness_of(:content, :scope => "parent_id")
- t = Topic.create("title" => "I'm unique!")
+ t = Topic.create("title" => "I'm unique!")
- r1 = t.replies.create "title" => "r1", "content" => "hello world"
- assert r1.valid?, "Saving r1"
+ r1 = t.replies.create "title" => "r1", "content" => "hello world"
+ assert r1.valid?, "Saving r1"
- r2 = t.replies.create "title" => "r2", "content" => "hello world"
- assert !r2.valid?, "Saving r2 first time"
+ r2 = t.replies.create "title" => "r2", "content" => "hello world"
+ assert !r2.valid?, "Saving r2 first time"
- r2.content = "something else"
- assert r2.save, "Saving r2 second time"
+ r2.content = "something else"
+ assert r2.save, "Saving r2 second time"
- t2 = Topic.create("title" => "I'm unique too!")
- r3 = t2.replies.create "title" => "r3", "content" => "hello world"
- assert r3.valid?, "Saving r3"
+ t2 = Topic.create("title" => "I'm unique too!")
+ r3 = t2.replies.create "title" => "r3", "content" => "hello world"
+ assert r3.valid?, "Saving r3"
+ end
end
def test_validate_uniqueness_scoped_to_defining_class
@@ -406,27 +403,29 @@ def test_validate_uniqueness_scoped_to_defining_class
end
def test_validate_uniqueness_with_scope_array
- Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id])
+ repair_validations(Reply) do
+ Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id])
- t = Topic.create("title" => "The earth is actually flat!")
+ t = Topic.create("title" => "The earth is actually flat!")
- r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply"
- assert r1.valid?, "Saving r1"
+ r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply"
+ assert r1.valid?, "Saving r1"
- r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..."
- assert !r2.valid?, "Saving r2. Double reply by same author."
+ r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..."
+ assert !r2.valid?, "Saving r2. Double reply by same author."
- r2.author_email_address = "jeremy_alt_email@rubyonrails.com"
- assert r2.save, "Saving r2 the second time."
+ r2.author_email_address = "jeremy_alt_email@rubyonrails.com"
+ assert r2.save, "Saving r2 the second time."
- r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic"
- assert !r3.valid?, "Saving r3"
+ r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic"
+ assert !r3.valid?, "Saving r3"
- r3.author_name = "jj"
- assert r3.save, "Saving r3 the second time."
+ r3.author_name = "jj"
+ assert r3.save, "Saving r3 the second time."
- r3.author_name = "jeremy"
- assert !r3.save, "Saving r3 the third time."
+ r3.author_name = "jeremy"
+ assert !r3.save, "Saving r3 the third time."
+ end
end
def test_validate_case_insensitive_uniqueness
@@ -523,10 +522,12 @@ def test_validates_uniqueness_inside_with_scope
end
def test_validate_uniqueness_with_columns_which_are_sql_keywords
- Guid.validates_uniqueness_of :key
- g = Guid.new
- g.key = "foo"
- assert_nothing_raised { !g.valid? }
+ repair_validations(Guid) do
+ Guid.validates_uniqueness_of :key
+ g = Guid.new
+ g.key = "foo"
+ assert_nothing_raised { !g.valid? }
+ end
end
def test_validate_straight_inheritance_uniqueness
@@ -648,10 +649,12 @@ def test_validates_inclusion_of_with_allow_nil
end
def test_numericality_with_getter_method
- Developer.validates_numericality_of( :salary )
- developer = Developer.new("name" => "michael", "salary" => nil)
- developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
- assert developer.valid?
+ repair_validations(Developer) do
+ Developer.validates_numericality_of( :salary )
+ developer = Developer.new("name" => "michael", "salary" => nil)
+ developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
+ assert developer.valid?
+ end
end
def test_validates_length_of_with_allow_nil
@@ -684,10 +687,12 @@ def test_validates_inclusion_of_with_formatted_message
end
def test_numericality_with_allow_nil_and_getter_method
- Developer.validates_numericality_of( :salary, :allow_nil => true)
- developer = Developer.new("name" => "michael", "salary" => nil)
- developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
- assert developer.valid?
+ repair_validations(Developer) do
+ Developer.validates_numericality_of( :salary, :allow_nil => true)
+ developer = Developer.new("name" => "michael", "salary" => nil)
+ developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
+ assert developer.valid?
+ end
end
def test_validates_exclusion_of
@@ -892,26 +897,30 @@ def test_validates_length_with_globally_modified_error_message
end
def test_validates_size_of_association
- assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
- t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
- assert !t.save
- assert t.errors.on(:replies)
- reply = t.replies.build('title' => 'areply', 'content' => 'whateveragain')
- assert t.valid?
+ repair_validations(Owner) do
+ assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
+ o = Owner.new('name' => 'nopets')
+ assert !o.save
+ assert o.errors.on(:pets)
+ pet = o.pets.build('name' => 'apet')
+ assert o.valid?
+ end
end
def test_validates_size_of_association_using_within
- assert_nothing_raised { Topic.validates_size_of :replies, :within => 1..2 }
- t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
- assert !t.save
- assert t.errors.on(:replies)
-
- reply = t.replies.build('title' => 'areply', 'content' => 'whateveragain')
- assert t.valid?
-
- 2.times { t.replies.build('title' => 'areply', 'content' => 'whateveragain') }
- assert !t.save
- assert t.errors.on(:replies)
+ repair_validations(Owner) do
+ assert_nothing_raised { Owner.validates_size_of :pets, :within => 1..2 }
+ o = Owner.new('name' => 'nopets')
+ assert !o.save
+ assert o.errors.on(:pets)
+
+ pet = o.pets.build('name' => 'apet')
+ assert o.valid?
+
+ 2.times { o.pets.build('name' => 'apet') }
+ assert !o.save
+ assert o.errors.on(:pets)
+ end
end
def test_validates_length_of_nasty_params
@@ -1102,13 +1111,15 @@ def test_validates_length_of_with_block
end
def test_validates_size_of_association_utf8
- with_kcode('UTF8') do
- assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
- t = Topic.new('title' => 'あいうえお', 'content' => 'かきくけこ')
- assert !t.save
- assert t.errors.on(:replies)
- t.replies.build('title' => 'あいうえお', 'content' => 'かきくけこ')
- assert t.valid?
+ repair_validations(Owner) do
+ with_kcode('UTF8') do
+ assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
+ o = Owner.new('name' => 'あいうえおかきくけこ')
+ assert !o.save
+ assert o.errors.on(:pets)
+ o.pets.build('name' => 'あいうえおかきくけこ')
+ assert o.valid?
+ end
end
end
@@ -1127,14 +1138,16 @@ def test_validates_associated_many
end
def test_validates_associated_one
- Reply.validates_associated( :topic )
- Topic.validates_presence_of( :content )
- r = Reply.new("title" => "A reply", "content" => "with content!")
- r.topic = Topic.create("title" => "uhohuhoh")
- assert !r.valid?
- assert r.errors.on(:topic)
- r.topic.content = "non-empty"
- assert r.valid?
+ repair_validations(Reply) do
+ Reply.validates_associated( :topic )
+ Topic.validates_presence_of( :content )
+ r = Reply.new("title" => "A reply", "content" => "with content!")
+ r.topic = Topic.create("title" => "uhohuhoh")
+ assert !r.valid?
+ assert r.errors.on(:topic)
+ r.topic.content = "non-empty"
+ assert r.valid?
+ end
end
def test_validate_block
@@ -1158,85 +1171,105 @@ def test_throw_away_typing
end
def test_validates_acceptance_of_with_custom_error_using_quotes
- Developer.validates_acceptance_of :salary, :message=> "This string contains 'single' and \"double\" quotes"
- d = Developer.new
- d.salary = "0"
- assert !d.valid?
- assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
+ repair_validations(Developer) do
+ Developer.validates_acceptance_of :salary, :message=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.salary = "0"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
+ end
end
def test_validates_confirmation_of_with_custom_error_using_quotes
- Developer.validates_confirmation_of :name, :message=> "confirm 'single' and \"double\" quotes"
- d = Developer.new
- d.name = "John"
- d.name_confirmation = "Johnny"
- assert !d.valid?
- assert_equal "confirm 'single' and \"double\" quotes", d.errors.on(:name)
+ repair_validations(Developer) do
+ Developer.validates_confirmation_of :name, :message=> "confirm 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "John"
+ d.name_confirmation = "Johnny"
+ assert !d.valid?
+ assert_equal "confirm 'single' and \"double\" quotes", d.errors.on(:name)
+ end
end
def test_validates_format_of_with_custom_error_using_quotes
- Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "format 'single' and \"double\" quotes"
- d = Developer.new
- d.name = d.name_confirmation = "John 32"
- assert !d.valid?
- assert_equal "format 'single' and \"double\" quotes", d.errors.on(:name)
+ repair_validations(Developer) do
+ Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "format 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = d.name_confirmation = "John 32"
+ assert !d.valid?
+ assert_equal "format 'single' and \"double\" quotes", d.errors.on(:name)
+ end
end
def test_validates_inclusion_of_with_custom_error_using_quotes
- Developer.validates_inclusion_of :salary, :in => 1000..80000, :message=> "This string contains 'single' and \"double\" quotes"
- d = Developer.new
- d.salary = "90,000"
- assert !d.valid?
- assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
+ repair_validations(Developer) do
+ Developer.validates_inclusion_of :salary, :in => 1000..80000, :message=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.salary = "90,000"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
+ end
end
def test_validates_length_of_with_custom_too_long_using_quotes
- Developer.validates_length_of :name, :maximum => 4, :too_long=> "This string contains 'single' and \"double\" quotes"
- d = Developer.new
- d.name = "Jeffrey"
- assert !d.valid?
- assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
+ repair_validations(Developer) do
+ Developer.validates_length_of :name, :maximum => 4, :too_long=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "Jeffrey"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
+ end
end
def test_validates_length_of_with_custom_too_short_using_quotes
- Developer.validates_length_of :name, :minimum => 4, :too_short=> "This string contains 'single' and \"double\" quotes"
- d = Developer.new
- d.name = "Joe"
- assert !d.valid?
- assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
+ repair_validations(Developer) do
+ Developer.validates_length_of :name, :minimum => 4, :too_short=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "Joe"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
+ end
end
def test_validates_length_of_with_custom_message_using_quotes
- Developer.validates_length_of :name, :minimum => 4, :message=> "This string contains 'single' and \"double\" quotes"
- d = Developer.new
- d.name = "Joe"
- assert !d.valid?
- assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
+ repair_validations(Developer) do
+ Developer.validates_length_of :name, :minimum => 4, :message=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "Joe"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
+ end
end
def test_validates_presence_of_with_custom_message_using_quotes
- Developer.validates_presence_of :non_existent, :message=> "This string contains 'single' and \"double\" quotes"
- d = Developer.new
- d.name = "Joe"
- assert !d.valid?
- assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:non_existent)
+ repair_validations(Developer) do
+ Developer.validates_presence_of :non_existent, :message=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "Joe"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:non_existent)
+ end
end
def test_validates_uniqueness_of_with_custom_message_using_quotes
- Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes"
- d = Developer.new
- d.name = "David"
- assert !d.valid?
- assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
+ repair_validations(Developer) do
+ Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "David"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
+ end
end
def test_validates_associated_with_custom_message_using_quotes
- Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
- Topic.validates_presence_of :content
- r = Reply.create("title" => "A reply", "content" => "with content!")
- r.topic = Topic.create("title" => "uhohuhoh")
- assert !r.valid?
- assert_equal "This string contains 'single' and \"double\" quotes", r.errors.on(:topic).last
+ repair_validations(Reply) do
+ Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
+ Topic.validates_presence_of :content
+ r = Reply.create("title" => "A reply", "content" => "with content!")
+ r.topic = Topic.create("title" => "uhohuhoh")
+ assert !r.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", r.errors.on(:topic)
+ end
end
def test_if_validation_using_method_true
@@ -1346,13 +1379,15 @@ def test_unless_validation_using_block_false
end
def test_validates_associated_missing
- Reply.validates_presence_of(:topic)
- r = Reply.create("title" => "A reply", "content" => "with content!")
- assert !r.valid?
- assert r.errors.on(:topic)
-
- r.topic = Topic.find :first
- assert r.valid?
+ repair_validations(Reply) do
+ Reply.validates_presence_of(:topic)
+ r = Reply.create("title" => "A reply", "content" => "with content!")
+ assert !r.valid?
+ assert r.errors.on(:topic)
+
+ r.topic = Topic.find :first
+ assert r.valid?
+ end
end
def test_errors_to_xml
@@ -1364,14 +1399,14 @@ def test_errors_to_xml
assert xml.include?("<error>Content Empty</error>")
end
- def test_validation_order
- Topic.validates_presence_of :title
- Topic.validates_length_of :title, :minimum => 2
+ def test_validation_order
+ Topic.validates_presence_of :title
+ Topic.validates_length_of :title, :minimum => 2
- t = Topic.new("title" => "")
- assert !t.valid?
- assert_equal "can't be blank", t.errors.on("title").first
- end
+ t = Topic.new("title" => "")
+ assert !t.valid?
+ assert_equal "can't be blank", t.errors.on("title").first
+ end
# previous implementation of validates_presence_of eval'd the
# string with the wrong binding, this regression test is to
@@ -1423,11 +1458,7 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase
JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"]
INFINITY = [1.0/0.0]
- def setup
- Topic.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
- Topic.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
- Topic.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
- end
+ repair_validations(Topic)
def test_default_validates_numericality_of
Topic.validates_numericality_of :approved
View
6 activerecord/test/fixtures/member_types.yml
@@ -0,0 +1,6 @@
+founding:
+ id: 1
+ name: Founding
+provisional:
+ id: 2
+ name: Provisional
View
4 activerecord/test/fixtures/members.yml
@@ -1,4 +1,6 @@
groucho:
name: Groucho Marx
+ member_type_id: 1
some_other_guy:
- name: Englebert Humperdink
+ name: Englebert Humperdink
+ member_type_id: 2
View
1  activerecord/test/models/member.rb
@@ -8,4 +8,5 @@ class Member < ActiveRecord::Base
has_one :sponsor_club, :through => :sponsor
has_one :member_detail
has_one :organization, :through => :member_detail
+ belongs_to :member_type
end
View
1  activerecord/test/models/member_detail.rb
@@ -1,4 +1,5 @@
class MemberDetail < ActiveRecord::Base
belongs_to :member
belongs_to :organization
+ has_one :member_type, :through => :member
end
View
3  activerecord/test/models/member_type.rb
@@ -0,0 +1,3 @@
+class MemberType < ActiveRecord::Base
+ has_many :members
+end
View
5 activerecord/test/schema/schema.rb
@@ -195,6 +195,7 @@ def create_table(*args, &block)
create_table :members, :force => true do |t|
t.string :name
+ t.integer :member_type_id
end
create_table :member_details, :force => true do |t|
@@ -210,6 +211,10 @@ def create_table(*args, &block)
t.string :type
end
+ create_table :member_types, :force => true do |t|
+ t.string :name
+ end
+
create_table :references, :force => true do |t|
t.integer :person_id
t.integer :job_id
View
42