aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.erb2
-rw-r--r--actionmailer/test/mail_service_test.rb9
-rw-r--r--actionpack/CHANGELOG3
-rw-r--r--actionpack/Rakefile15
-rw-r--r--actionpack/examples/minimal.rb17
-rw-r--r--actionpack/lib/action_controller/abstract.rb2
-rw-r--r--actionpack/lib/action_controller/abstract/base.rb38
-rw-r--r--actionpack/lib/action_controller/abstract/benchmarker.rb10
-rw-r--r--actionpack/lib/action_controller/abstract/callbacks.rb27
-rw-r--r--actionpack/lib/action_controller/abstract/exceptions.rb4
-rw-r--r--actionpack/lib/action_controller/abstract/helpers.rb13
-rw-r--r--actionpack/lib/action_controller/abstract/layouts.rb29
-rw-r--r--actionpack/lib/action_controller/abstract/logger.rb14
-rw-r--r--actionpack/lib/action_controller/abstract/renderer.rb32
-rw-r--r--actionpack/lib/action_controller/base/chained/filters.rb7
-rw-r--r--actionpack/lib/action_controller/base/chained/flash.rb85
-rw-r--r--actionpack/lib/action_controller/base/cookies.rb2
-rw-r--r--actionpack/lib/action_controller/base/filter_parameter_logging.rb8
-rw-r--r--actionpack/lib/action_controller/base/helpers.rb2
-rw-r--r--actionpack/lib/action_controller/base/request_forgery_protection.rb4
-rw-r--r--actionpack/lib/action_controller/base/streaming.rb4
-rw-r--r--actionpack/lib/action_controller/base/verification.rb4
-rw-r--r--actionpack/lib/action_controller/caching.rb2
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb2
-rw-r--r--actionpack/lib/action_controller/new_base.rb3
-rw-r--r--actionpack/lib/action_controller/new_base/base.rb23
-rw-r--r--actionpack/lib/action_controller/new_base/compatibility.rb62
-rw-r--r--actionpack/lib/action_controller/new_base/conditional_get.rb32
-rw-r--r--actionpack/lib/action_controller/new_base/helpers.rb53
-rw-r--r--actionpack/lib/action_controller/new_base/hide_actions.rb48
-rw-r--r--actionpack/lib/action_controller/new_base/http.rb80
-rw-r--r--actionpack/lib/action_controller/new_base/layouts.rb44
-rw-r--r--actionpack/lib/action_controller/new_base/rack_convenience.rb33
-rw-r--r--actionpack/lib/action_controller/new_base/redirector.rb8
-rw-r--r--actionpack/lib/action_controller/new_base/render_options.rb58
-rw-r--r--actionpack/lib/action_controller/new_base/renderer.rb96
-rw-r--r--actionpack/lib/action_controller/new_base/rescuable.rb31
-rw-r--r--actionpack/lib/action_controller/new_base/session.rb4
-rw-r--r--actionpack/lib/action_controller/new_base/testing.rb7
-rw-r--r--actionpack/lib/action_controller/new_base/url_for.rb10
-rw-r--r--actionpack/lib/action_controller/routing/resources.rb3
-rw-r--r--actionpack/lib/action_controller/routing/segments.rb2
-rw-r--r--actionpack/lib/action_controller/testing/integration.rb2
-rw-r--r--actionpack/lib/action_controller/testing/process2.rb3
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb16
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb2
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb13
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb3
-rw-r--r--actionpack/lib/action_view/render/partials.rb2
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb8
-rw-r--r--actionpack/lib/action_view/template/template.rb20
-rw-r--r--actionpack/test/controller/cookie_test.rb6
-rw-r--r--actionpack/test/controller/filters_test.rb102
-rw-r--r--actionpack/test/controller/flash_test.rb22
-rw-r--r--actionpack/test/controller/routing_test.rb13
-rw-r--r--actionpack/test/controller/view_paths_test.rb4
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb4
-rw-r--r--actionpack/test/fixtures/test/utf8.html.erb4
-rw-r--r--actionpack/test/fixtures/test/utf8_magic.html.erb5
-rw-r--r--actionpack/test/template/body_parts_test.rb5
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb6
-rw-r--r--actionpack/test/template/output_buffer_test.rb75
-rw-r--r--actionpack/test/template/render_test.rb25
-rwxr-xr-xactivemodel/Rakefile3
-rw-r--r--activemodel/lib/active_model.rb46
-rw-r--r--activemodel/lib/active_model/callbacks.rb7
-rw-r--r--activemodel/lib/active_model/core.rb3
-rw-r--r--activemodel/lib/active_model/deprecated_error_methods.rb21
-rw-r--r--activemodel/lib/active_model/errors.rb156
-rw-r--r--activemodel/lib/active_model/locale/en.yml24
-rw-r--r--activemodel/lib/active_model/observing.rb1
-rw-r--r--activemodel/lib/active_model/state_machine.rb5
-rw-r--r--activemodel/lib/active_model/test_case.rb18
-rw-r--r--activemodel/lib/active_model/validations.rb68
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb21
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb11
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb17
-rw-r--r--activemodel/lib/active_model/validations/format.rb16
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb17
-rw-r--r--activemodel/lib/active_model/validations/length.rb94
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb38
-rw-r--r--activemodel/lib/active_model/validations/presence.rb33
-rw-r--r--activemodel/lib/active_model/validations/uniqueness.rb104
-rw-r--r--activemodel/lib/active_model/validations_repair_helper.rb44
-rw-r--r--activemodel/test/cases/helper.rb20
-rw-r--r--activemodel/test/cases/observing_test.rb (renamed from activemodel/test/observing_test.rb)2
-rw-r--r--activemodel/test/cases/state_machine/event_test.rb (renamed from activemodel/test/state_machine/event_test.rb)3
-rw-r--r--activemodel/test/cases/state_machine/machine_test.rb (renamed from activemodel/test/state_machine/machine_test.rb)2
-rw-r--r--activemodel/test/cases/state_machine/state_test.rb (renamed from activemodel/test/state_machine/state_test.rb)2
-rw-r--r--activemodel/test/cases/state_machine/state_transition_test.rb (renamed from activemodel/test/state_machine/state_transition_test.rb)3
-rw-r--r--activemodel/test/cases/state_machine_test.rb (renamed from activemodel/test/state_machine_test.rb)2
-rw-r--r--activemodel/test/cases/tests_database.rb41
-rw-r--r--activemodel/test/cases/validations/acceptance_validation_test.rb80
-rw-r--r--activemodel/test/cases/validations/conditional_validation_test.rb140
-rw-r--r--activemodel/test/cases/validations/confirmation_validation_test.rb68
-rw-r--r--activemodel/test/cases/validations/exclusion_validation_test.rb46
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb98
-rw-r--r--activemodel/test/cases/validations/i18n_generate_message_validation_test.rb182
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb579
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb80
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb449
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb179
-rw-r--r--activemodel/test/cases/validations/presence_validation_test.rb57
-rw-r--r--activemodel/test/cases/validations_test.rb178
-rw-r--r--activemodel/test/config.rb3
-rw-r--r--activemodel/test/fixtures/topics.yml41
-rw-r--r--activemodel/test/models/developer.rb6
-rw-r--r--activemodel/test/models/person.rb5
-rw-r--r--activemodel/test/models/reply.rb34
-rw-r--r--activemodel/test/models/topic.rb9
-rw-r--r--activemodel/test/schema.rb14
-rw-r--r--activemodel/test/test_helper.rb21
-rw-r--r--activerecord/lib/active_record.rb10
-rw-r--r--activerecord/lib/active_record/aggregations.rb2
-rw-r--r--activerecord/lib/active_record/association_preload.rb2
-rwxr-xr-xactiverecord/lib/active_record/associations.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb4
-rw-r--r--activerecord/lib/active_record/batches.rb2
-rw-r--r--activerecord/lib/active_record/calculations.rb2
-rw-r--r--activerecord/lib/active_record/callbacks.rb2
-rw-r--r--activerecord/lib/active_record/dirty.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/named_scope.rb2
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb2
-rw-r--r--activerecord/lib/active_record/observer.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb11
-rw-r--r--activerecord/lib/active_record/serializers/json_serializer.rb2
-rw-r--r--activerecord/lib/active_record/timestamp.rb2
-rw-r--r--activerecord/lib/active_record/transactions.rb2
-rw-r--r--activerecord/lib/active_record/validations.rb1045
-rw-r--r--activerecord/lib/active_record/validations/associated.rb (renamed from activemodel/lib/active_model/validations/associated.rb)14
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb159
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb4
-rw-r--r--activerecord/test/cases/autosave_association_test.rb36
-rw-r--r--activerecord/test/cases/helper.rb2
-rw-r--r--activerecord/test/cases/repair_helper.rb2
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb5
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb101
-rw-r--r--activerecord/test/cases/validations/i18n_generate_message_validation_test.rb166
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb (renamed from activerecord/test/cases/validations_i18n_test.rb)348
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb278
-rw-r--r--activerecord/test/cases/validations_test.rb1489
-rw-r--r--activerecord/test/models/company.rb9
-rw-r--r--activerecord/test/models/company_in_module.rb9
-rw-r--r--activerecord/test/models/reply.rb20
-rw-r--r--activerecord/test/schema/schema.rb1
-rw-r--r--activeresource/lib/active_resource.rb12
-rw-r--r--activeresource/lib/active_resource/base.rb8
-rw-r--r--activeresource/lib/active_resource/validations.rb203
-rw-r--r--activeresource/test/base_errors_test.rb10
-rw-r--r--activesupport/lib/active_support/autoload.rb3
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb2
-rw-r--r--activesupport/lib/active_support/concern.rb25
-rw-r--r--activesupport/lib/active_support/dependency_module.rb12
-rw-r--r--activesupport/lib/active_support/new_callbacks.rb30
-rw-r--r--activesupport/test/caching_test.rb9
-rw-r--r--activesupport/test/concern_test.rb (renamed from activesupport/test/dependency_module_test.rb)35
-rw-r--r--railties/CHANGELOG2
-rw-r--r--railties/lib/commands/server.rb8
-rw-r--r--railties/lib/initializer.rb16
-rw-r--r--railties/lib/rails_generator/generators/components/model_subclass/USAGE13
-rw-r--r--railties/lib/rails_generator/generators/components/model_subclass/model_subclass_generator.rb32
-rw-r--r--railties/lib/rails_generator/generators/components/model_subclass/templates/model.rb3
-rw-r--r--railties/lib/rails_generator/generators/components/model_subclass/templates/unit_test.rb8
-rw-r--r--railties/lib/tasks/databases.rake26
-rw-r--r--railties/test/generators/rails_model_subclass_generator_test.rb15
-rw-r--r--railties/test/initializer_test.rb24
-rwxr-xr-xtools/profile (renamed from tools/profile_requires)0
174 files changed, 4743 insertions, 3869 deletions
diff --git a/.gitignore b/.gitignore
index d86af64569..5357fbe481 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ activesupport/doc
activeresource/pkg
activerecord/pkg
actionpack/pkg
+activemodel/test/fixtures/fixture_database.sqlite3
actionmailer/pkg
activesupport/pkg
railties/pkg
diff --git a/actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.erb b/actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.erb
index 897a5065cf..2d0cd5c124 100644
--- a/actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.erb
+++ b/actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.erb
@@ -1 +1 @@
-Have a lovely picture, from me. Enjoy! \ No newline at end of file
+Have some dots. Enjoy! \ No newline at end of file
diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb
index 919ab5de94..30d1b836c4 100644
--- a/actionmailer/test/mail_service_test.rb
+++ b/actionmailer/test/mail_service_test.rb
@@ -6,10 +6,10 @@ class FunkyPathMailer < ActionMailer::Base
def multipart_with_template_path_with_dots(recipient)
recipients recipient
- subject "Have a lovely picture"
+ subject "This path has dots"
from "Chad Fowler <chad@chadfowler.com>"
- attachment :content_type => "image/jpeg",
- :body => "not really a jpeg, we're only testing, after all"
+ attachment :content_type => "text/plain",
+ :body => "dots dots dots..."
end
end
@@ -939,7 +939,8 @@ EOF
def test_multipart_with_template_path_with_dots
mail = FunkyPathMailer.create_multipart_with_template_path_with_dots(@recipient)
assert_equal 2, mail.parts.length
- assert mail.parts.any? {|part| part.content_type == "text/plain" && part.charset == "utf-8"}
+ assert "text/plain", mail.parts[1].content_type
+ assert "utf-8", mail.parts[1].charset
end
def test_custom_content_type_attributes
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index e3ad19558e..e758983d7a 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,8 @@
*Edge*
+* Ruby 1.9: ERB template encoding using a magic comment at the top of the file. [Jeremy Kemper]
+ <%# encoding: utf-8 %>
+
* Change integration test helpers to accept Rack environment instead of just HTTP Headers [Pratik Naik]
Before : get '/path', {}, 'Accept' => 'text/javascript'
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 9ce897aae8..5f5614e58f 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -59,24 +59,21 @@ end
desc 'Old Controller Tests on New Base'
Rake::TestTask.new(:test_new_base_on_old_tests) do |t|
t.libs << "test/new_base" << "test/lib"
- # layout
- # Dir.glob( "test/{dispatch,template}/**/*_test.rb" ).sort +
+ t.verbose = true
# ==== Not ported
# * filters
- # * integration
- # * test
- # * view_paths
- t.test_files = %w(
+
+ t.test_files = Dir.glob( "test/{dispatch,template}/**/*_test.rb" ).sort + %w(
action_pack_assertions addresses_render assert_select
base benchmark caching capture content_type cookie dispatcher
filter_params flash helper http_basic_authentication
- http_digest_authentication layout logging mime_responds
+ http_digest_authentication integration layout logging mime_responds
record_identifier redirect render render_js render_json
render_other render_xml request_forgery_protection rescue
- resources routing selector send_file url_rewriter verification webservice
+ resources routing selector send_file test url_rewriter
+ verification view_paths webservice
).map { |name| "test/controller/#{name}_test.rb" }
- t.verbose = true
end
# Genereate the RDoc documentation
diff --git a/actionpack/examples/minimal.rb b/actionpack/examples/minimal.rb
index e2d112648c..9eb92cd8e7 100644
--- a/actionpack/examples/minimal.rb
+++ b/actionpack/examples/minimal.rb
@@ -24,10 +24,12 @@ class Runner
out.puts '---'
end
- def self.run(app, n)
+ def self.run(app, n, label = nil)
+ puts '=' * label.size, label, '=' * label.size if label
env = { 'n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout }
t = Benchmark.realtime { new(app).call(env) }
- puts "%d ms / %d req = %d usec/req" % [10**3 * t, n, 10**6 * t / n]
+ puts "%d ms / %d req = %.1f usec/req" % [10**3 * t, n, 10**6 * t / n]
+ puts
end
end
@@ -38,16 +40,19 @@ class BasePostController < ActionController::Base
def index
render :text => ''
end
-
- Runner.run(action(:index), N)
end
+OK = [200, {}, []]
+MetalPostController = lambda { OK }
+
if ActionController.const_defined?(:Http)
class HttpPostController < ActionController::Http
def index
self.response_body = ''
end
-
- Runner.run(action(:index), N)
end
end
+
+Runner.run(MetalPostController, N, 'metal')
+Runner.run(HttpPostController.action(:index), N, 'http') if defined? HttpPostController
+Runner.run(BasePostController.action(:index), N, 'base')
diff --git a/actionpack/lib/action_controller/abstract.rb b/actionpack/lib/action_controller/abstract.rb
index d6b7a44f5f..f46b91627f 100644
--- a/actionpack/lib/action_controller/abstract.rb
+++ b/actionpack/lib/action_controller/abstract.rb
@@ -11,4 +11,4 @@ module AbstractController
autoload :Renderer, "action_controller/abstract/renderer"
# === Exceptions
autoload :ActionNotFound, "action_controller/abstract/exceptions"
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/abstract/base.rb b/actionpack/lib/action_controller/abstract/base.rb
index 0e4803388a..87083a4d79 100644
--- a/actionpack/lib/action_controller/abstract/base.rb
+++ b/actionpack/lib/action_controller/abstract/base.rb
@@ -2,28 +2,27 @@ require 'active_support/core_ext/module/attr_internal'
module AbstractController
class Error < StandardError; end
-
+
class DoubleRenderError < Error
DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"."
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
end
- end
-
+ end
+
class Base
-
attr_internal :response_body
attr_internal :response_obj
attr_internal :action_name
class << self
- attr_reader :abstract
-
+ attr_reader :abstract
+
def abstract!
@abstract = true
end
-
+
alias_method :abstract?, :abstract
def inherited(klass)
@@ -34,13 +33,13 @@ module AbstractController
def subclasses
@subclasses ||= []
end
-
+
def internal_methods
controller = self
controller = controller.superclass until controller.abstract?
controller.public_instance_methods(true)
end
-
+
def process(action)
new.process(action.to_s)
end
@@ -48,7 +47,7 @@ module AbstractController
def hidden_actions
[]
end
-
+
def action_methods
@action_methods ||=
# All public instance methods of this class, including ancestors
@@ -61,13 +60,13 @@ module AbstractController
hidden_actions
end
end
-
+
abstract!
-
+
def initialize
self.response_obj = {}
end
-
+
def process(action)
@_action_name = action_name = action.to_s
@@ -78,26 +77,27 @@ module AbstractController
process_action(action_name)
self
end
-
+
private
-
def action_methods
self.class.action_methods
end
-
+
def action_method?(action)
action_methods.include?(action)
end
-
+
# It is possible for respond_to?(action_name) to be false and
# respond_to?(:action_missing) to be false if respond_to_action?
# is overridden in a subclass. For instance, ActionController::Base
# overrides it to include the case where a template matching the
# action_name is found.
def process_action(method_name)
- send(method_name)
+ send_action(method_name)
end
+ alias send_action send
+
def _handle_action_missing
action_missing(@_action_name)
end
@@ -112,4 +112,4 @@ module AbstractController
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/abstract/benchmarker.rb b/actionpack/lib/action_controller/abstract/benchmarker.rb
index 9f5889c704..07294cede3 100644
--- a/actionpack/lib/action_controller/abstract/benchmarker.rb
+++ b/actionpack/lib/action_controller/abstract/benchmarker.rb
@@ -1,9 +1,9 @@
module AbstractController
module Benchmarker
- extend ActiveSupport::DependencyModule
-
- depends_on Logger
-
+ extend ActiveSupport::Concern
+
+ include Logger
+
module ClassMethods
def benchmark(title, log_level = ::Logger::DEBUG, use_silence = true)
if logger && logger.level >= log_level
@@ -25,4 +25,4 @@ module AbstractController
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/abstract/callbacks.rb b/actionpack/lib/action_controller/abstract/callbacks.rb
index e4f9dd3112..affe053bac 100644
--- a/actionpack/lib/action_controller/abstract/callbacks.rb
+++ b/actionpack/lib/action_controller/abstract/callbacks.rb
@@ -1,8 +1,8 @@
module AbstractController
module Callbacks
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
- depends_on ActiveSupport::NewCallbacks
+ include ActiveSupport::NewCallbacks
included do
define_callbacks :process_action, "response_body"
@@ -13,7 +13,7 @@ module AbstractController
super
end
end
-
+
module ClassMethods
def _normalize_callback_options(options)
if only = options[:only]
@@ -21,11 +21,17 @@ module AbstractController
options[:per_key] = {:if => only}
end
if except = options[:except]
- except = Array(except).map {|e| "action_name == '#{e}'"}.join(" || ")
+ except = Array(except).map {|e| "action_name == '#{e}'"}.join(" || ")
options[:per_key] = {:unless => except}
end
end
-
+
+ def skip_filter(*names, &blk)
+ skip_before_filter(*names, &blk)
+ skip_after_filter(*names, &blk)
+ skip_around_filter(*names, &blk)
+ end
+
[:before, :after, :around].each do |filter|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{filter}_filter(*names, &blk)
@@ -37,6 +43,15 @@ module AbstractController
end
end
+ def prepend_#{filter}_filter(*names, &blk)
+ options = names.last.is_a?(Hash) ? names.pop : {}
+ _normalize_callback_options(options)
+ names.push(blk) if block_given?
+ names.each do |name|
+ process_action_callback(:#{filter}, name, options.merge(:prepend => true))
+ end
+ end
+
def skip_#{filter}_filter(*names, &blk)
options = names.last.is_a?(Hash) ? names.pop : {}
_normalize_callback_options(options)
@@ -51,4 +66,4 @@ module AbstractController
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/abstract/exceptions.rb b/actionpack/lib/action_controller/abstract/exceptions.rb
index ec4680629b..2f6c55f068 100644
--- a/actionpack/lib/action_controller/abstract/exceptions.rb
+++ b/actionpack/lib/action_controller/abstract/exceptions.rb
@@ -1,3 +1,3 @@
module AbstractController
- class ActionNotFound < StandardError ; end
-end \ No newline at end of file
+ class ActionNotFound < StandardError; end
+end
diff --git a/actionpack/lib/action_controller/abstract/helpers.rb b/actionpack/lib/action_controller/abstract/helpers.rb
index 41decfd0c7..0a2776de9c 100644
--- a/actionpack/lib/action_controller/abstract/helpers.rb
+++ b/actionpack/lib/action_controller/abstract/helpers.rb
@@ -1,8 +1,8 @@
module AbstractController
module Helpers
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
- depends_on Renderer
+ include Renderer
included do
extlib_inheritable_accessor :master_helper_module
@@ -16,12 +16,12 @@ module AbstractController
av
end
end
-
+
module ClassMethods
def inherited(klass)
klass.master_helper_module = Module.new
klass.master_helper_module.__send__ :include, master_helper_module
-
+
super
end
@@ -57,7 +57,7 @@ module AbstractController
ruby_eval
end
end
-
+
def helper(*args, &block)
args.flatten.each do |arg|
case arg
@@ -68,6 +68,5 @@ module AbstractController
master_helper_module.module_eval(&block) if block_given?
end
end
-
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/abstract/layouts.rb b/actionpack/lib/action_controller/abstract/layouts.rb
index 3d6810bda9..273063f74b 100644
--- a/actionpack/lib/action_controller/abstract/layouts.rb
+++ b/actionpack/lib/action_controller/abstract/layouts.rb
@@ -1,8 +1,8 @@
module AbstractController
module Layouts
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
- depends_on Renderer
+ include Renderer
included do
extlib_inheritable_accessor :_layout_conditions
@@ -17,18 +17,18 @@ module AbstractController
conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
self._layout_conditions = conditions
-
+
@_layout = layout || false # Converts nil to false
_write_layout_method
end
-
+
def _implied_layout_name
name.underscore
end
-
+
# Takes the specified layout and creates a _layout method to be called
# by _default_layout
- #
+ #
# If the specified layout is a:
# String:: return the string
# Symbol:: call the method specified by the symbol
@@ -57,11 +57,12 @@ module AbstractController
end
end
end
-
+
private
-
- def _layout(details) end # This will be overwritten
-
+ # This will be overwritten
+ def _layout(details)
+ end
+
# :api: plugin
# ====
# Override this to mutate the inbound layout name
@@ -69,7 +70,7 @@ module AbstractController
unless [String, FalseClass, NilClass].include?(name.class)
raise ArgumentError, "String, false, or nil expected; you passed #{name.inspect}"
end
-
+
name && view_paths.find_by_parts(name, details, _layout_prefix(name))
end
@@ -77,7 +78,7 @@ module AbstractController
def _layout_prefix(name)
"layouts"
end
-
+
def _default_layout(require_layout = false, details = {:formats => formats})
if require_layout && _action_has_layout? && !_layout(details)
raise ArgumentError,
@@ -87,7 +88,7 @@ module AbstractController
begin
_layout_for_name(_layout(details), details) if _action_has_layout?
rescue NameError => e
- raise NoMethodError,
+ raise NoMethodError,
"You specified #{@_layout.inspect} as the layout, but no such method was found"
end
end
@@ -103,4 +104,4 @@ module AbstractController
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/abstract/logger.rb b/actionpack/lib/action_controller/abstract/logger.rb
index 980ede0bed..d6fa843485 100644
--- a/actionpack/lib/action_controller/abstract/logger.rb
+++ b/actionpack/lib/action_controller/abstract/logger.rb
@@ -3,13 +3,13 @@ require 'active_support/core_ext/logger'
module AbstractController
module Logger
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
class DelayedLog
def initialize(&blk)
@blk = blk
end
-
+
def to_s
@blk.call
end
@@ -19,10 +19,10 @@ module AbstractController
included do
cattr_accessor :logger
end
-
+
def process(action)
ret = super
-
+
if logger
log = DelayedLog.new do
"\n\nProcessing #{self.class.name}\##{action_name} " \
@@ -32,14 +32,14 @@ module AbstractController
logger.info(log)
end
-
+
ret
end
-
+
def request_origin
# this *needs* to be cached!
# otherwise you'd get different results if calling it more than once
@request_origin ||= "#{request.remote_ip} at #{Time.now.to_s(:db)}"
- end
+ end
end
end
diff --git a/actionpack/lib/action_controller/abstract/renderer.rb b/actionpack/lib/action_controller/abstract/renderer.rb
index d7c68549e1..dd58c7cb64 100644
--- a/actionpack/lib/action_controller/abstract/renderer.rb
+++ b/actionpack/lib/action_controller/abstract/renderer.rb
@@ -2,9 +2,9 @@ require "action_controller/abstract/logger"
module AbstractController
module Renderer
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
- depends_on AbstractController::Logger
+ include AbstractController::Logger
included do
attr_internal :formats
@@ -15,22 +15,22 @@ module AbstractController
end
def _action_view
- @_action_view ||= ActionView::Base.new(self.class.view_paths, {}, self)
+ @_action_view ||= ActionView::Base.new(self.class.view_paths, {}, self)
end
-
+
def render(*args)
if response_body
raise AbstractController::DoubleRenderError, "OMG"
end
-
+
self.response_body = render_to_body(*args)
end
-
+
# Raw rendering of a template to a Rack-compatible body.
# ====
# @option _prefix<String> The template's path prefix
# @option _layout<String> The relative path to the layout template to use
- #
+ #
# :api: plugin
def render_to_body(options = {})
# TODO: Refactor so we can just use the normal template logic for this
@@ -46,7 +46,7 @@ module AbstractController
# ====
# @option _prefix<String> The template's path prefix
# @option _layout<String> The relative path to the layout template to use
- #
+ #
# :api: plugin
def render_to_string(options = {})
AbstractController::Renderer.body_to_s(render_to_body(options))
@@ -55,8 +55,10 @@ module AbstractController
def _render_template(options)
_action_view._render_template_from_controller(options[:_template], options[:_layout], options, options[:_partial])
end
-
- def view_paths() _view_paths end
+
+ def view_paths()
+ _view_paths
+ end
# Return a string representation of a Rack-compatible response body.
def self.body_to_s(body)
@@ -71,7 +73,6 @@ module AbstractController
end
private
-
def _determine_template(options)
name = (options[:_template_name] || action_name).to_s
@@ -81,15 +82,18 @@ module AbstractController
end
module ClassMethods
-
def append_view_path(path)
self.view_paths << path
end
-
+
+ def prepend_view_path(path)
+ self.view_paths.unshift(path)
+ end
+
def view_paths
self._view_paths
end
-
+
def view_paths=(paths)
self._view_paths = paths.is_a?(ActionView::PathSet) ?
paths : ActionView::Base.process_view_paths(paths)
diff --git a/actionpack/lib/action_controller/base/chained/filters.rb b/actionpack/lib/action_controller/base/chained/filters.rb
index e121c0129d..f528dd0686 100644
--- a/actionpack/lib/action_controller/base/chained/filters.rb
+++ b/actionpack/lib/action_controller/base/chained/filters.rb
@@ -1,11 +1,6 @@
module ActionController #:nodoc:
module Filters #:nodoc:
- def self.included(base)
- base.class_eval do
- extend ClassMethods
- include ActionController::Filters::InstanceMethods
- end
- end
+ extend ActiveSupport::Concern
class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc:
def append_filter_to_chain(filters, filter_type, &block)
diff --git a/actionpack/lib/action_controller/base/chained/flash.rb b/actionpack/lib/action_controller/base/chained/flash.rb
index 6bd482d85a..42c6e430ca 100644
--- a/actionpack/lib/action_controller/base/chained/flash.rb
+++ b/actionpack/lib/action_controller/base/chained/flash.rb
@@ -26,11 +26,11 @@ module ActionController #:nodoc:
#
# See docs on the FlashHash class for more details about the flash.
module Flash
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
# TODO : Remove the defined? check when new base is the main base
- depends_on Session if defined?(ActionController::Http)
-
+ include Session if defined?(ActionController::Http)
+
included do
# TODO : Remove the defined? check when new base is the main base
if defined?(ActionController::Http)
@@ -129,65 +129,68 @@ module ActionController #:nodoc:
(@used.keys - keys).each{ |k| @used.delete(k) }
end
+ def store(session, key = "flash")
+ return if self.empty?
+ session[key] = self
+ end
+
private
# Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
# use() # marks the entire flash as used
# use('msg') # marks the "msg" entry as used
# use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
# use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
- def use(k=nil, v=true)
- unless k.nil?
- @used[k] = v
- else
- keys.each{ |key| use(key, v) }
- end
+ # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
+ # if no key is passed.
+ def use(key = nil, used = true)
+ Array(key || keys).each { |k| @used[k] = used }
+ return key ? self[key] : self
end
end
module InstanceMethodsForBase #:nodoc:
protected
+ def perform_action_with_flash
+ perform_action_without_flash
+ if defined? @_flash
+ @_flash.store(session)
+ remove_instance_variable(:@_flash)
+ end
+ end
- def perform_action_with_flash
- perform_action_without_flash
- remove_instance_variable(:@_flash) if defined?(@_flash)
- end
-
- def reset_session_with_flash
- reset_session_without_flash
- remove_instance_variable(:@_flash) if defined?(@_flash)
- end
+ def reset_session_with_flash
+ reset_session_without_flash
+ remove_instance_variable(:@_flash) if defined?(@_flash)
+ end
end
module InstanceMethodsForNewBase #:nodoc:
protected
+ def process_action(method_name)
+ super
+ if defined? @_flash
+ @_flash.store(session)
+ remove_instance_variable(:@_flash)
+ end
+ end
- def reset_session
- super
- remove_flash_instance_variable
- end
-
- def process_action(method_name)
- super
- remove_flash_instance_variable
- end
-
- def remove_flash_instance_variable
- remove_instance_variable(:@_flash) if defined?(@_flash)
- end
+ def reset_session
+ super
+ remove_instance_variable(:@_flash) if defined?(@_flash)
+ end
end
protected
+ # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
+ # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
+ # to put a new one.
+ def flash #:doc:
+ if !defined?(@_flash)
+ @_flash = session["flash"] || FlashHash.new
+ @_flash.sweep
+ end
- # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
- # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
- # to put a new one.
- def flash #:doc:
- unless defined?(@_flash)
- @_flash = session["flash"] ||= FlashHash.new
- @_flash.sweep
+ @_flash
end
-
- @_flash
- end
end
end
diff --git a/actionpack/lib/action_controller/base/cookies.rb b/actionpack/lib/action_controller/base/cookies.rb
index ca380e98d0..d4806623c3 100644
--- a/actionpack/lib/action_controller/base/cookies.rb
+++ b/actionpack/lib/action_controller/base/cookies.rb
@@ -51,7 +51,7 @@ module ActionController #:nodoc:
protected
# Returns the cookie container, which operates as described above.
def cookies
- CookieJar.new(self)
+ @cookies ||= CookieJar.new(self)
end
end
diff --git a/actionpack/lib/action_controller/base/filter_parameter_logging.rb b/actionpack/lib/action_controller/base/filter_parameter_logging.rb
index f5a678ca03..8370ba6fc0 100644
--- a/actionpack/lib/action_controller/base/filter_parameter_logging.rb
+++ b/actionpack/lib/action_controller/base/filter_parameter_logging.rb
@@ -1,16 +1,14 @@
module ActionController
module FilterParameterLogging
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
# TODO : Remove the defined? check when new base is the main base
if defined?(ActionController::Http)
- depends_on AbstractController::Logger
+ include AbstractController::Logger
end
included do
- if defined?(ActionController::Http)
- include InstanceMethodsForNewBase
- end
+ include InstanceMethodsForNewBase
end
module ClassMethods
diff --git a/actionpack/lib/action_controller/base/helpers.rb b/actionpack/lib/action_controller/base/helpers.rb
index 96fa7896a9..f74158bc13 100644
--- a/actionpack/lib/action_controller/base/helpers.rb
+++ b/actionpack/lib/action_controller/base/helpers.rb
@@ -3,7 +3,7 @@ require 'active_support/dependencies'
# FIXME: helper { ... } is broken on Ruby 1.9
module ActionController #:nodoc:
module Helpers #:nodoc:
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
included do
# Initialize the base module to aggregate its helpers.
diff --git a/actionpack/lib/action_controller/base/request_forgery_protection.rb b/actionpack/lib/action_controller/base/request_forgery_protection.rb
index 0a0e20e1f1..a470c8eec1 100644
--- a/actionpack/lib/action_controller/base/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/base/request_forgery_protection.rb
@@ -3,11 +3,11 @@ module ActionController #:nodoc:
end
module RequestForgeryProtection
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
# TODO : Remove the defined? check when new base is the main base
if defined?(ActionController::Http)
- depends_on AbstractController::Helpers, Session
+ include AbstractController::Helpers, Session
end
included do
diff --git a/actionpack/lib/action_controller/base/streaming.rb b/actionpack/lib/action_controller/base/streaming.rb
index 5872ba99a2..73d4bde6c1 100644
--- a/actionpack/lib/action_controller/base/streaming.rb
+++ b/actionpack/lib/action_controller/base/streaming.rb
@@ -2,11 +2,11 @@ module ActionController #:nodoc:
# Methods for sending arbitrary data and for streaming files to the browser,
# instead of rendering.
module Streaming
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
# TODO : Remove the defined? check when new base is the main base
if defined?(ActionController::Http)
- depends_on ActionController::Renderer
+ include ActionController::Renderer
end
DEFAULT_SEND_FILE_OPTIONS = {
diff --git a/actionpack/lib/action_controller/base/verification.rb b/actionpack/lib/action_controller/base/verification.rb
index 3fa5a105b1..d87b348ed4 100644
--- a/actionpack/lib/action_controller/base/verification.rb
+++ b/actionpack/lib/action_controller/base/verification.rb
@@ -1,10 +1,10 @@
module ActionController #:nodoc:
module Verification #:nodoc:
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
# TODO : Remove the defined? check when new base is the main base
if defined?(ActionController::Http)
- depends_on AbstractController::Callbacks, Session, Flash, Renderer
+ include AbstractController::Callbacks, Session, Flash, Renderer
end
# This module provides a class-level method for specifying that certain
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index 2f2eec10f6..38cf1da6a8 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -24,7 +24,7 @@ module ActionController #:nodoc:
# ActionController::Base.cache_store = :mem_cache_store, "localhost"
# ActionController::Base.cache_store = MyOwnStore.new("parameter")
module Caching
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
autoload :Actions, 'action_controller/caching/actions'
autoload :Fragments, 'action_controller/caching/fragments'
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index 3646ff1af9..54148b55d8 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -93,7 +93,7 @@ module ActionController #:nodoc:
# TODO: Remove once New Base is merged
if defined?(ActionController::Http)
- def around_process_action(controller)
+ def filter(controller)
should_continue = before(controller)
yield if should_continue
after(controller)
diff --git a/actionpack/lib/action_controller/new_base.rb b/actionpack/lib/action_controller/new_base.rb
index 276be50614..df256985ac 100644
--- a/actionpack/lib/action_controller/new_base.rb
+++ b/actionpack/lib/action_controller/new_base.rb
@@ -4,6 +4,7 @@ module ActionController
autoload :HideActions, "action_controller/new_base/hide_actions"
autoload :Http, "action_controller/new_base/http"
autoload :Layouts, "action_controller/new_base/layouts"
+ autoload :RackConvenience, "action_controller/new_base/rack_convenience"
autoload :Rails2Compatibility, "action_controller/new_base/compatibility"
autoload :Redirector, "action_controller/new_base/redirector"
autoload :Renderer, "action_controller/new_base/renderer"
@@ -40,5 +41,7 @@ module ActionController
require 'action_controller/routing'
end
+autoload :HTML, 'action_controller/vendor/html-scanner'
+
require 'action_dispatch'
require 'action_view'
diff --git a/actionpack/lib/action_controller/new_base/base.rb b/actionpack/lib/action_controller/new_base/base.rb
index ffe608ade4..d7b65d37fa 100644
--- a/actionpack/lib/action_controller/new_base/base.rb
+++ b/actionpack/lib/action_controller/new_base/base.rb
@@ -1,7 +1,7 @@
module ActionController
class Base < Http
abstract!
-
+
include AbstractController::Benchmarker
include AbstractController::Callbacks
include AbstractController::Logger
@@ -14,6 +14,7 @@ module ActionController
include ActionController::Renderers::All
include ActionController::Layouts
include ActionController::ConditionalGet
+ include ActionController::RackConvenience
# Legacy modules
include SessionManagement
@@ -38,9 +39,9 @@ module ActionController
# TODO: Extract into its own module
# This should be moved together with other normalizing behavior
module ImplicitRender
- def process_action(method_name)
+ def send_action(method_name)
ret = super
- default_render if response_body.nil?
+ default_render unless performed?
ret
end
@@ -65,31 +66,31 @@ module ActionController
::ActionController::Base.subclasses << klass.to_s
super
end
-
+
def self.subclasses
@subclasses ||= []
end
-
+
def self.app_loaded!
@subclasses.each do |subclass|
subclass.constantize._write_layout_method
end
end
-
+
def _normalize_options(action = nil, options = {}, &blk)
if action.is_a?(Hash)
- options, action = action, nil
+ options, action = action, nil
elsif action.is_a?(String) || action.is_a?(Symbol)
key = case action = action.to_s
when %r{^/} then :file
when %r{/} then :template
else :action
- end
+ end
options.merge! key => action
elsif action
options.merge! :partial => action
end
-
+
if options.key?(:action) && options[:action].to_s.index("/")
options[:template] = options.delete(:action)
end
@@ -139,7 +140,7 @@ module ActionController
#
# When using <tt>redirect_to :back</tt>, if there is no referrer,
# RedirectBackError will be raised. You may specify some fallback
- # behavior for this case by rescuing RedirectBackError.
+ # behavior for this case by rescuing RedirectBackError.
def redirect_to(options = {}, response_status = {}) #:doc:
raise ActionControllerError.new("Cannot redirect to nil!") if options.nil?
@@ -165,7 +166,7 @@ module ActionController
else
url_for(options)
end
-
+
super(url, status)
end
end
diff --git a/actionpack/lib/action_controller/new_base/compatibility.rb b/actionpack/lib/action_controller/new_base/compatibility.rb
index 4245ba982b..f278c2da14 100644
--- a/actionpack/lib/action_controller/new_base/compatibility.rb
+++ b/actionpack/lib/action_controller/new_base/compatibility.rb
@@ -1,6 +1,6 @@
module ActionController
module Rails2Compatibility
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
class ::ActionController::ActionControllerError < StandardError #:nodoc:
end
@@ -9,43 +9,43 @@ module ActionController
included do
::ActionController::UnknownAction = ::AbstractController::ActionNotFound
::ActionController::DoubleRenderError = ::AbstractController::DoubleRenderError
-
+
cattr_accessor :session_options
self.session_options = {}
-
+
cattr_accessor :allow_concurrency
self.allow_concurrency = false
-
+
cattr_accessor :param_parsers
self.param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
Mime::URL_ENCODED_FORM => :url_encoded_form,
Mime::XML => :xml_simple,
Mime::JSON => :json }
-
+
cattr_accessor :relative_url_root
self.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT']
-
+
cattr_accessor :default_charset
self.default_charset = "utf-8"
-
+
# cattr_reader :protected_instance_variables
cattr_accessor :protected_instance_variables
self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller
@action_name @before_filter_chain_aborted @action_cache_path @_headers @_params
@_flash @_response)
-
+
# Indicates whether or not optimise the generated named
# route helper methods
cattr_accessor :optimise_named_routes
self.optimise_named_routes = true
-
+
cattr_accessor :resources_path_names
self.resources_path_names = { :new => 'new', :edit => 'edit' }
-
+
# Controls the resource action separator
cattr_accessor :resource_action_separator
self.resource_action_separator = "/"
-
+
cattr_accessor :use_accept_header
self.use_accept_header = true
@@ -61,19 +61,29 @@ module ActionController
# and images to a dedicated asset server away from the main web server. Example:
# ActionController::Base.asset_host = "http://assets.example.com"
cattr_accessor :asset_host
+
+ cattr_accessor :ip_spoofing_check
+ self.ip_spoofing_check = true
end
-
+
# For old tests
def initialize_template_class(*) end
def assign_shortcuts(*) end
# TODO: Remove this after we flip
def template
- _action_view
+ @template ||= _action_view
+ end
+
+ def process_action(*)
+ template
+ super
end
module ClassMethods
- def consider_all_requests_local() end
+ def consider_all_requests_local
+ end
+
def rescue_action(env)
raise env["action_dispatch.rescue.exception"]
end
@@ -84,16 +94,11 @@ module ActionController
end
end
- def initialize(*)
- super
- @template = _action_view
- end
-
def render_to_body(options)
if options.is_a?(Hash) && options.key?(:template)
options[:template].sub!(/^\//, '')
end
-
+
options[:text] = nil if options[:nothing] == true
body = super
@@ -107,8 +112,8 @@ module ActionController
def method_for_action(action_name)
super || (respond_to?(:method_missing) && "_handle_method_missing")
- end
-
+ end
+
def _layout_prefix(name)
super unless name =~ /\blayouts/
end
@@ -116,5 +121,18 @@ module ActionController
def performed?
response_body
end
+
+ # ==== Request only view path switching ====
+ def append_view_path(path)
+ view_paths.push(*path)
+ end
+
+ def prepend_view_path(path)
+ view_paths.unshift(*path)
+ end
+
+ def view_paths
+ _action_view.view_paths
+ end
end
end
diff --git a/actionpack/lib/action_controller/new_base/conditional_get.rb b/actionpack/lib/action_controller/new_base/conditional_get.rb
index 116ce34494..d287ec4994 100644
--- a/actionpack/lib/action_controller/new_base/conditional_get.rb
+++ b/actionpack/lib/action_controller/new_base/conditional_get.rb
@@ -1,12 +1,15 @@
module ActionController
module ConditionalGet
-
+ extend ActiveSupport::Concern
+
+ include RackConvenience
+
# Sets the etag, last_modified, or both on the response and renders a
# "304 Not Modified" response if the request is already fresh.
#
# Parameters:
# * <tt>:etag</tt>
- # * <tt>:last_modified</tt>
+ # * <tt>:last_modified</tt>
# * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches).
#
# Example:
@@ -18,14 +21,14 @@ module ActionController
#
# This will render the show template if the request isn't sending a matching etag or
# If-Modified-Since header and just a "304 Not Modified" response if there's a match.
- #
+ #
def fresh_when(options)
options.assert_valid_keys(:etag, :last_modified, :public)
response.etag = options[:etag] if options[:etag]
response.last_modified = options[:last_modified] if options[:last_modified]
-
- if options[:public]
+
+ if options[:public]
cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip }
cache_control.delete("private")
cache_control.delete("no-cache")
@@ -36,8 +39,8 @@ module ActionController
if request.fresh?(response)
head :not_modified
end
- end
-
+ end
+
# Return a response that has no content (merely headers). The options
# argument is interpreted to be a hash of header names and values.
# This allows you to easily return a response that consists only of
@@ -64,8 +67,8 @@ module ActionController
end
render :nothing => true, :status => status
- end
-
+ end
+
# Sets the etag and/or last_modified on the response and checks it against
# the client request. If the request doesn't match the options provided, the
# request is considered stale and should be generated from scratch. Otherwise,
@@ -73,7 +76,7 @@ module ActionController
#
# Parameters:
# * <tt>:etag</tt>
- # * <tt>:last_modified</tt>
+ # * <tt>:last_modified</tt>
# * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches).
#
# Example:
@@ -92,7 +95,7 @@ module ActionController
fresh_when(options)
!request.fresh?(response)
end
-
+
# Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that
# intermediate caches shouldn't cache the response.
#
@@ -114,10 +117,10 @@ module ActionController
else
cache_control << "private"
end
-
+
# This allows for additional headers to be passed through like 'max-stale' => 5.hours
cache_control += options.symbolize_keys.reject{|k,v| k == :public || k == :private }.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}
-
+
response.headers["Cache-Control"] = cache_control.join(', ')
end
@@ -126,6 +129,5 @@ module ActionController
def expires_now #:doc:
response.headers["Cache-Control"] = "no-cache"
end
-
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/new_base/helpers.rb b/actionpack/lib/action_controller/new_base/helpers.rb
index e00c3c338b..e8000be87b 100644
--- a/actionpack/lib/action_controller/new_base/helpers.rb
+++ b/actionpack/lib/action_controller/new_base/helpers.rb
@@ -4,9 +4,9 @@ require 'active_support/dependencies'
module ActionController
module Helpers
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
- depends_on AbstractController::Helpers
+ include AbstractController::Helpers
included do
# Set the default directory for helpers
@@ -24,10 +24,10 @@ module ActionController
#
# * <tt>*args</tt>: One or more modules, strings or symbols, or the special symbol <tt>:all</tt>.
# * <tt>&block</tt>: A block defining helper methods.
- #
+ #
# ==== Examples
- # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file
- # and include the module in the template class. The second form illustrates how to include custom helpers
+ # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file
+ # and include the module in the template class. The second form illustrates how to include custom helpers
# when working with namespaced controllers, or other cases where the file containing the helper definition is not
# in one of Rails' standard load paths:
# helper :foo # => requires 'foo_helper' and includes FooHelper
@@ -40,17 +40,17 @@ module ActionController
# <tt>ActionController::Base.helpers_dir</tt> (defaults to <tt>app/helpers/**/*.rb</tt> under RAILS_ROOT).
# helper :all
#
- # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available
+ # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available
# to the template.
# # One line
# helper { def hello() "Hello, world!" end }
# # Multi-line
# helper do
- # def foo(bar)
- # "#{bar} is the very best"
+ # def foo(bar)
+ # "#{bar} is the very best"
# end
# end
- #
+ #
# Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of
# +symbols+, +strings+, +modules+ and blocks.
# helper(:three, BlindHelper) { def mice() 'mice' end }
@@ -106,25 +106,24 @@ module ActionController
end
private
-
- def default_helper_module!
- unless name.blank?
- module_name = name.sub(/Controller$|$/, 'Helper')
- module_path = module_name.split('::').map { |m| m.underscore }.join('/')
- require_dependency module_path
- helper module_name.constantize
+ def default_helper_module!
+ unless name.blank?
+ module_name = name.sub(/Controller$|$/, 'Helper')
+ module_path = module_name.split('::').map { |m| m.underscore }.join('/')
+ require_dependency module_path
+ helper module_name.constantize
+ end
+ rescue MissingSourceFile => e
+ raise unless e.is_missing? module_path
+ rescue NameError => e
+ raise unless e.missing_name? module_name
end
- rescue MissingSourceFile => e
- raise unless e.is_missing? module_path
- rescue NameError => e
- raise unless e.missing_name? module_name
- end
- # Extract helper names from files in app/helpers/**/*.rb
- def all_application_helpers
- extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/
- Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
- end
- end # ClassMethods
+ # Extract helper names from files in app/helpers/**/*.rb
+ def all_application_helpers
+ extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/
+ Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/new_base/hide_actions.rb b/actionpack/lib/action_controller/new_base/hide_actions.rb
index a29b09a893..b45e520bee 100644
--- a/actionpack/lib/action_controller/new_base/hide_actions.rb
+++ b/actionpack/lib/action_controller/new_base/hide_actions.rb
@@ -1,33 +1,39 @@
module ActionController
module HideActions
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
included do
extlib_inheritable_accessor :hidden_actions
self.hidden_actions ||= Set.new
end
- def action_methods() self.class.action_names end
- def action_names() action_methods end
-
- private
-
- def action_method?(action_name)
- !hidden_actions.include?(action_name) && super
+ def action_methods
+ self.class.action_names
end
-
- module ClassMethods
- def hide_action(*args)
- args.each do |arg|
- self.hidden_actions << arg.to_s
- end
- end
-
- def action_methods
- @action_names ||= Set.new(super.reject {|name| self.hidden_actions.include?(name.to_s)})
- end
- def self.action_names() action_methods end
+ def action_names
+ action_methods
end
+
+ private
+ def action_method?(action_name)
+ !hidden_actions.include?(action_name) && super
+ end
+
+ module ClassMethods
+ def hide_action(*args)
+ args.each do |arg|
+ self.hidden_actions << arg.to_s
+ end
+ end
+
+ def action_methods
+ @action_names ||= Set.new(super.reject {|name| self.hidden_actions.include?(name.to_s)})
+ end
+
+ def self.action_names
+ action_methods
+ end
+ end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/new_base/http.rb b/actionpack/lib/action_controller/new_base/http.rb
index 2525e221a6..c96aaaa865 100644
--- a/actionpack/lib/action_controller/new_base/http.rb
+++ b/actionpack/lib/action_controller/new_base/http.rb
@@ -4,9 +4,9 @@ require 'active_support/core_ext/module/delegation'
module ActionController
class Http < AbstractController::Base
abstract!
-
+
# :api: public
- attr_internal :request, :response, :params
+ attr_internal :params, :env
# :api: public
def self.controller_name
@@ -14,54 +14,78 @@ module ActionController
end
# :api: public
- def controller_name() self.class.controller_name end
+ def controller_name
+ self.class.controller_name
+ end
- # :api: public
+ # :api: public
def self.controller_path
@controller_path ||= self.name.sub(/Controller$/, '').underscore
end
-
- # :api: public
- def controller_path() self.class.controller_path end
-
- # :api: private
- def self.action_names() action_methods end
-
+
+ # :api: public
+ def controller_path
+ self.class.controller_path
+ end
+
+ # :api: private
+ def self.action_names
+ action_methods
+ end
+
# :api: private
- def action_names() action_methods end
-
+ def action_names
+ action_methods
+ end
+
# :api: plugin
def self.call(env)
controller = new
controller.call(env).to_rack
end
-
- delegate :headers, :to => "@_response"
- def params
- @_params ||= @_request.parameters
+ # The details below can be overridden to support a specific
+ # Request and Response object. The default ActionController::Base
+ # implementation includes RackConvenience, which makes a request
+ # and response object available. You might wish to control the
+ # environment and response manually for performance reasons.
+
+ attr_internal :status, :headers, :content_type
+
+ def initialize(*)
+ @_headers = {}
+ super
+ end
+
+ # Basic implements for content_type=, location=, and headers are
+ # provided to reduce the dependency on the RackConvenience module
+ # in Renderer and Redirector.
+
+ def content_type=(type)
+ headers["Content-Type"] = type.to_s
end
-
+
+ def location=(url)
+ headers["Location"] = url
+ end
+
# :api: private
def call(name, env)
- @_request = ActionDispatch::Request.new(env)
- @_response = ActionDispatch::Response.new
- @_response.request = request
+ @_env = env
process(name)
to_rack
end
-
+
+ # :api: private
+ def to_rack
+ [status, headers, response_body]
+ end
+
def self.action(name)
@actions ||= {}
@actions[name.to_s] ||= proc do |env|
new.call(name, env)
end
end
-
- # :api: private
- def to_rack
- @_response.prepare!
- @_response.to_a
- end
end
end
diff --git a/actionpack/lib/action_controller/new_base/layouts.rb b/actionpack/lib/action_controller/new_base/layouts.rb
index 35068db770..0ff71587d6 100644
--- a/actionpack/lib/action_controller/new_base/layouts.rb
+++ b/actionpack/lib/action_controller/new_base/layouts.rb
@@ -1,36 +1,34 @@
module ActionController
module Layouts
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
+
+ include ActionController::Renderer
+ include AbstractController::Layouts
- depends_on ActionController::Renderer
- depends_on AbstractController::Layouts
-
module ClassMethods
def _implied_layout_name
controller_path
end
end
-
- private
- def _determine_template(options)
- super
- if (!options.key?(:text) && !options.key?(:inline) && !options.key?(:partial)) || options.key?(:layout)
- options[:_layout] = _layout_for_option(options.key?(:layout) ? options[:layout] : :none, options[:_template].details)
+ private
+ def _determine_template(options)
+ super
+ if (!options.key?(:text) && !options.key?(:inline) && !options.key?(:partial)) || options.key?(:layout)
+ options[:_layout] = _layout_for_option(options.key?(:layout) ? options[:layout] : :none, options[:_template].details)
+ end
end
- end
-
- def _layout_for_option(name, details)
- case name
- when String then _layout_for_name(name, details)
- when true then _default_layout(true, details)
- when :none then _default_layout(false, details)
- when false, nil then nil
- else
- raise ArgumentError,
- "String, true, or false, expected for `layout'; you passed #{name.inspect}"
+
+ def _layout_for_option(name, details)
+ case name
+ when String then _layout_for_name(name, details)
+ when true then _default_layout(true, details)
+ when :none then _default_layout(false, details)
+ when false, nil then nil
+ else
+ raise ArgumentError,
+ "String, true, or false, expected for `layout'; you passed #{name.inspect}"
+ end
end
- end
-
end
end
diff --git a/actionpack/lib/action_controller/new_base/rack_convenience.rb b/actionpack/lib/action_controller/new_base/rack_convenience.rb
new file mode 100644
index 0000000000..5dfa7d12f3
--- /dev/null
+++ b/actionpack/lib/action_controller/new_base/rack_convenience.rb
@@ -0,0 +1,33 @@
+module ActionController
+ module RackConvenience
+ extend ActiveSupport::Concern
+
+ included do
+ delegate :headers, :status=, :location=,
+ :status, :location, :content_type, :to => "@_response"
+ attr_internal :request, :response
+ end
+
+ def call(name, env)
+ @_request = ActionDispatch::Request.new(env)
+ @_response = ActionDispatch::Response.new
+ @_response.request = request
+ super
+ end
+
+ def params
+ @_params ||= @_request.parameters
+ end
+
+ # :api: private
+ def to_rack
+ @_response.prepare!
+ @_response.to_a
+ end
+
+ def response_body=(body)
+ response.body = body if response
+ super
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/new_base/redirector.rb b/actionpack/lib/action_controller/new_base/redirector.rb
index ff7b74341c..20060b001f 100644
--- a/actionpack/lib/action_controller/new_base/redirector.rb
+++ b/actionpack/lib/action_controller/new_base/redirector.rb
@@ -6,14 +6,14 @@ module ActionController
super(message || DEFAULT_MESSAGE)
end
end
-
+
module Redirector
def redirect_to(url, status) #:doc:
raise AbstractController::DoubleRenderError if response_body
logger.info("Redirected to #{url}") if logger && logger.info?
- response.status = status
- response.location = url.gsub(/[\r\n]/, '')
+ self.status = status
+ self.location = url.gsub(/[\r\n]/, '')
self.response_body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>"
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/new_base/render_options.rb b/actionpack/lib/action_controller/new_base/render_options.rb
index 581a92cb7b..fc9a02626f 100644
--- a/actionpack/lib/action_controller/new_base/render_options.rb
+++ b/actionpack/lib/action_controller/new_base/render_options.rb
@@ -1,12 +1,12 @@
module ActionController
module RenderOptions
- extend ActiveSupport::DependencyModule
-
+ extend ActiveSupport::Concern
+
included do
extlib_inheritable_accessor :_renderers
self._renderers = []
end
-
+
module ClassMethods
def _write_render_options
renderers = _renderers.map do |r|
@@ -17,33 +17,31 @@ module ActionController
end
RUBY_EVAL
end
-
+
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def _handle_render_options(options)
#{renderers.join}
end
RUBY_EVAL
end
-
+
def _add_render_option(name)
_renderers << name
_write_render_options
end
end
-
+
def render_to_body(options)
_handle_render_options(options) || super
end
end
-
- module RenderOption
- extend ActiveSupport::DependencyModule
- included do
- extend ActiveSupport::DependencyModule
- depends_on ::ActionController::RenderOptions
+ module RenderOption #:nodoc:
+ def self.extended(base)
+ base.extend ActiveSupport::Concern
+ base.send :include, ::ActionController::RenderOptions
- def self.register_renderer(name)
+ def base.register_renderer(name)
included { _add_render_option(name) }
end
end
@@ -51,57 +49,55 @@ module ActionController
module Renderers
module Json
- include RenderOption
+ extend RenderOption
register_renderer :json
-
+
def _render_json(json, options)
json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str)
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
- response.content_type ||= Mime::JSON
+ self.content_type ||= Mime::JSON
self.response_body = json
- end
+ end
end
module Js
- include RenderOption
+ extend RenderOption
register_renderer :js
def _render_js(js, options)
- response.content_type ||= Mime::JS
+ self.content_type ||= Mime::JS
self.response_body = js
end
end
module Xml
- include RenderOption
+ extend RenderOption
register_renderer :xml
def _render_xml(xml, options)
- response.content_type ||= Mime::XML
+ self.content_type ||= Mime::XML
self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml : xml
end
end
- module Rjs
- include RenderOption
+ module RJS
+ extend RenderOption
register_renderer :update
def _render_update(proc, options)
generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(_action_view, &proc)
- response.content_type = Mime::JS
+ self.content_type = Mime::JS
self.response_body = generator.to_s
end
end
module All
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
- included do
- include ::ActionController::Renderers::Json
- include ::ActionController::Renderers::Js
- include ::ActionController::Renderers::Xml
- include ::ActionController::Renderers::Rjs
- end
+ include ActionController::Renderers::Json
+ include ActionController::Renderers::Js
+ include ActionController::Renderers::Xml
+ include ActionController::Renderers::RJS
end
end
end
diff --git a/actionpack/lib/action_controller/new_base/renderer.rb b/actionpack/lib/action_controller/new_base/renderer.rb
index 987751a601..2fab501302 100644
--- a/actionpack/lib/action_controller/new_base/renderer.rb
+++ b/actionpack/lib/action_controller/new_base/renderer.rb
@@ -1,23 +1,18 @@
module ActionController
module Renderer
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
+
+ include AbstractController::Renderer
- depends_on AbstractController::Renderer
-
def process_action(*)
self.formats = request.formats.map {|x| x.to_sym}
super
end
-
- def response_body=(body)
- response.body = body if response
- super
- end
def render(options)
super
options[:_template] ||= _action_view._partial
- response.content_type ||= begin
+ self.content_type ||= begin
mime = options[:_template].mime_type
formats.include?(mime && mime.to_sym) || formats.include?(:all) ? mime : Mime::Type.lookup_by_extension(formats.first)
end
@@ -26,7 +21,7 @@ module ActionController
def render_to_body(options)
_process_options(options)
-
+
if options.key?(:partial)
_render_partial(options[:partial], options)
end
@@ -34,51 +29,50 @@ module ActionController
super
end
- private
+ private
+ def _prefix
+ controller_path
+ end
- def _prefix
- controller_path
- end
+ def _determine_template(options)
+ if options.key?(:text)
+ options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first)
+ elsif options.key?(:inline)
+ handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb")
+ template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {})
+ options[:_template] = template
+ elsif options.key?(:template)
+ options[:_template_name] = options[:template]
+ elsif options.key?(:file)
+ options[:_template_name] = options[:file]
+ elsif !options.key?(:partial)
+ options[:_template_name] = (options[:action] || action_name).to_s
+ options[:_prefix] = _prefix
+ end
- def _determine_template(options)
- if options.key?(:text)
- options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first)
- elsif options.key?(:inline)
- handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb")
- template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {})
- options[:_template] = template
- elsif options.key?(:template)
- options[:_template_name] = options[:template]
- elsif options.key?(:file)
- options[:_template_name] = options[:file]
- elsif !options.key?(:partial)
- options[:_template_name] = (options[:action] || action_name).to_s
- options[:_prefix] = _prefix
+ super
end
-
- super
- end
- def _render_partial(partial, options)
- case partial
- when true
- options[:_prefix] = _prefix
- when String
- options[:_prefix] = _prefix unless partial.index('/')
- options[:_template_name] = partial
- else
- options[:_partial_object] = true
- return
+ def _render_partial(partial, options)
+ case partial
+ when true
+ options[:_prefix] = _prefix
+ when String
+ options[:_prefix] = _prefix unless partial.index('/')
+ options[:_template_name] = partial
+ else
+ options[:_partial_object] = true
+ return
+ end
+
+ options[:_partial] = options[:object] || true
+ end
+
+ def _process_options(options)
+ status, content_type, location = options.values_at(:status, :content_type, :location)
+ self.status = status if status
+ self.content_type = content_type if content_type
+ self.headers["Location"] = url_for(location) if location
end
-
- options[:_partial] = options[:object] || true
- end
-
- def _process_options(options)
- status, content_type, location = options.values_at(:status, :content_type, :location)
- response.status = status if status
- response.content_type = content_type if content_type
- response.headers["Location"] = url_for(location) if location
- end
end
end
diff --git a/actionpack/lib/action_controller/new_base/rescuable.rb b/actionpack/lib/action_controller/new_base/rescuable.rb
index 29ffe19d5f..029e643d93 100644
--- a/actionpack/lib/action_controller/new_base/rescuable.rb
+++ b/actionpack/lib/action_controller/new_base/rescuable.rb
@@ -15,7 +15,7 @@ module ActionController #:nodoc:
# behavior is achieved by overriding the <tt>rescue_action_in_public</tt>
# and <tt>rescue_action_locally</tt> methods.
module Rescue
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
included do
include ActiveSupport::Rescuable
@@ -32,22 +32,21 @@ module ActionController #:nodoc:
attr_internal :rescued_exception
- private
-
- def method_for_action(action_name)
- return action_name if self.rescued_exception = request.env.delete("action_dispatch.rescue.exception")
- super
- end
+ private
+ def method_for_action(action_name)
+ return action_name if self.rescued_exception = request.env.delete("action_dispatch.rescue.exception")
+ super
+ end
- def _rescue_action
- rescue_with_handler(rescued_exception) || raise(rescued_exception)
- end
+ def _rescue_action
+ rescue_with_handler(rescued_exception) || raise(rescued_exception)
+ end
- def process_action(*)
- super
- rescue Exception => exception
- self.rescued_exception = exception
- _rescue_action
- end
+ def process_action(*)
+ super
+ rescue Exception => exception
+ self.rescued_exception = exception
+ _rescue_action
+ end
end
end
diff --git a/actionpack/lib/action_controller/new_base/session.rb b/actionpack/lib/action_controller/new_base/session.rb
index a8715555fb..bcedd6e1c7 100644
--- a/actionpack/lib/action_controller/new_base/session.rb
+++ b/actionpack/lib/action_controller/new_base/session.rb
@@ -1,5 +1,9 @@
module ActionController
module Session
+ extend ActiveSupport::Concern
+
+ include RackConvenience
+
def session
@_request.session
end
diff --git a/actionpack/lib/action_controller/new_base/testing.rb b/actionpack/lib/action_controller/new_base/testing.rb
index 78051a6252..a4a1116d9e 100644
--- a/actionpack/lib/action_controller/new_base/testing.rb
+++ b/actionpack/lib/action_controller/new_base/testing.rb
@@ -1,6 +1,8 @@
module ActionController
module Testing
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
+
+ include RackConvenience
# OMG MEGA HAX
def process_with_new_base_test(request, response)
@@ -13,7 +15,7 @@ module ActionController
set_test_assigns
ret
end
-
+
def set_test_assigns
@assigns = {}
(instance_variable_names - self.class.protected_instance_variables).each do |var|
@@ -33,6 +35,5 @@ module ActionController
_process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name}
end
end
-
end
end
diff --git a/actionpack/lib/action_controller/new_base/url_for.rb b/actionpack/lib/action_controller/new_base/url_for.rb
index 94de9fab50..7119c14cd3 100644
--- a/actionpack/lib/action_controller/new_base/url_for.rb
+++ b/actionpack/lib/action_controller/new_base/url_for.rb
@@ -1,5 +1,9 @@
module ActionController
module UrlFor
+ extend ActiveSupport::Concern
+
+ include RackConvenience
+
def process_action(*)
initialize_current_url
super
@@ -21,7 +25,7 @@ module ActionController
# by this method.
def default_url_options(options = nil)
end
-
+
def rewrite_options(options) #:nodoc:
if defaults = default_url_options(options)
defaults.merge(options)
@@ -29,7 +33,7 @@ module ActionController
options
end
end
-
+
def url_for(options = {})
options ||= {}
case options
@@ -42,4 +46,4 @@ module ActionController
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/routing/resources.rb b/actionpack/lib/action_controller/routing/resources.rb
index 86abb7b2f4..05c782d226 100644
--- a/actionpack/lib/action_controller/routing/resources.rb
+++ b/actionpack/lib/action_controller/routing/resources.rb
@@ -1,3 +1,6 @@
+require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/object/try'
+
module ActionController
# == Overview
#
diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb
index 4f936d51d2..2603855476 100644
--- a/actionpack/lib/action_controller/routing/segments.rb
+++ b/actionpack/lib/action_controller/routing/segments.rb
@@ -1,7 +1,7 @@
module ActionController
module Routing
class Segment #:nodoc:
- RESERVED_PCHAR = ':@&=+$,;'
+ RESERVED_PCHAR = ':@&=+$,;%'
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
if RUBY_VERSION >= '1.9'
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze
diff --git a/actionpack/lib/action_controller/testing/integration.rb b/actionpack/lib/action_controller/testing/integration.rb
index cc157816e2..af4ccb7837 100644
--- a/actionpack/lib/action_controller/testing/integration.rb
+++ b/actionpack/lib/action_controller/testing/integration.rb
@@ -301,7 +301,7 @@ module ActionController
# A module used to extend ActionController::Base, so that integration tests
# can capture the controller used to satisfy a request.
module ControllerCapture #:nodoc:
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
included do
alias_method_chain :initialize, :capture
diff --git a/actionpack/lib/action_controller/testing/process2.rb b/actionpack/lib/action_controller/testing/process2.rb
index 677dd41781..1c6fd2d80a 100644
--- a/actionpack/lib/action_controller/testing/process2.rb
+++ b/actionpack/lib/action_controller/testing/process2.rb
@@ -51,9 +51,10 @@ module ActionController
@request.session = ActionController::TestSession.new(session) unless session.nil?
@request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
- build_request_uri(action, parameters)
+
@controller.request = @request
@controller.params.merge!(parameters)
+ build_request_uri(action, parameters)
# Base.class_eval { include ProcessWithTest } unless Base < ProcessWithTest
@controller.process_with_new_base_test(@request, @response)
@response
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
index a992f7d912..51e0868995 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
@@ -1,3 +1,6 @@
+require 'set'
+require 'active_support/core_ext/class/inheritable_attributes'
+
module HTML
class Sanitizer
def sanitize(text, options = {})
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 4d598669c7..bfff307669 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -10,7 +10,8 @@ module ActionDispatch
@@rescue_responses = Hash.new(:internal_server_error)
@@rescue_responses.update({
'ActionController::RoutingError' => :not_found,
- 'ActionController::UnknownAction' => :not_found,
+ # TODO: Clean this up after the switch
+ ActionController::UnknownAction.name => :not_found,
'ActiveRecord::RecordNotFound' => :not_found,
'ActiveRecord::StaleObjectError' => :conflict,
'ActiveRecord::RecordInvalid' => :unprocessable_entity,
@@ -23,14 +24,17 @@ module ActionDispatch
cattr_accessor :rescue_templates
@@rescue_templates = Hash.new('diagnostics')
@@rescue_templates.update({
- 'ActionView::MissingTemplate' => 'missing_template',
- 'ActionController::RoutingError' => 'routing_error',
- 'ActionController::UnknownAction' => 'unknown_action',
- 'ActionView::TemplateError' => 'template_error'
+ 'ActionView::MissingTemplate' => 'missing_template',
+ 'ActionController::RoutingError' => 'routing_error',
+ ActionController::UnknownAction.name => 'unknown_action',
+ 'ActionView::TemplateError' => 'template_error'
})
FAILSAFE_RESPONSE = [500, {'Content-Type' => 'text/html'},
- ['<html><body><h1>500 Internal Server Error</h1></body></html>']]
+ ["<html><body><h1>500 Internal Server Error</h1>" <<
+ "If you are the administrator of this website, then please read this web " <<
+ "application's log file and/or the web server's log file to find out what " <<
+ "went wrong.</body></html>"]]
def initialize(app, consider_all_requests_local = false)
@app = app
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 89d1a49403..e6d6b5a3ef 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/hash/diff'
+
module ActionDispatch
module Assertions
# Suite of assertions to test routes generated by Rails and the handling of requests made to them.
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index 9e39536653..b4197479a0 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -124,7 +124,11 @@ module ActionView
# Use an alternate output buffer for the duration of the block.
# Defaults to a new empty string.
- def with_output_buffer(buf = '') #:nodoc:
+ def with_output_buffer(buf = nil) #:nodoc:
+ unless buf
+ buf = ''
+ buf.force_encoding(output_buffer.encoding) if buf.respond_to?(:force_encoding)
+ end
self.output_buffer, old_buffer = buf, output_buffer
yield
output_buffer
@@ -134,9 +138,12 @@ module ActionView
# Add the output buffer to the response body and start a new one.
def flush_output_buffer #:nodoc:
- if output_buffer && output_buffer != ''
+ if output_buffer && !output_buffer.empty?
response.body_parts << output_buffer
- self.output_buffer = ''
+ new = ''
+ new.force_encoding(output_buffer.encoding) if new.respond_to?(:force_encoding)
+ self.output_buffer = new
+ nil
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index daf38fe3da..c96b1fc8d2 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -353,7 +353,8 @@ module ActionView
disable_with << ";#{options.delete('onclick')}" if options['onclick']
options["onclick"] = "if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }"
- options["onclick"] << "else { hiddenCommit = this.cloneNode(false);hiddenCommit.setAttribute('type', 'hidden');this.form.appendChild(hiddenCommit); }"
+ options["onclick"] << "else { hiddenCommit = document.createElement('input');hiddenCommit.type = 'hidden';"
+ options["onclick"] << "hiddenCommit.value = this.value;hiddenCommit.name = this.name;this.form.appendChild(hiddenCommit); }"
options["onclick"] << "this.setAttribute('originalValue', this.value);this.disabled = true;#{disable_with};"
options["onclick"] << "result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());"
options["onclick"] << "if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;"
diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb
index f2429e8cef..eacf117bea 100644
--- a/actionpack/lib/action_view/render/partials.rb
+++ b/actionpack/lib/action_view/render/partials.rb
@@ -171,7 +171,7 @@ module ActionView
# <% end %>
module Partials
extend ActiveSupport::Memoizable
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
included do
attr_accessor :_partial
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index 95f11d6490..21272ef089 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -16,11 +16,9 @@ module ActionView
self.default_format = Mime::HTML
def compile(template)
- src = ::ERB.new("<% __in_erb_template=true %>#{template.source}", nil, erb_trim_mode, '@output_buffer').src
-
- # Ruby 1.9 prepends an encoding to the source. However this is
- # useless because you can only set an encoding on the first line
- RUBY_VERSION >= '1.9' ? src.sub(/\A#coding:.*\n/, '') : src
+ magic = $1 if template.source =~ /\A(<%#.*coding:\s*(\S+)\s*-?%>)/
+ erb = "#{magic}<% __in_erb_template=true %>#{template.source}"
+ ::ERB.new(erb, nil, erb_trim_mode, '@output_buffer').src
end
end
end
diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb
index f61dd591a5..e7ea42c2eb 100644
--- a/actionpack/lib/action_view/template/template.rb
+++ b/actionpack/lib/action_view/template/template.rb
@@ -52,16 +52,30 @@ module ActionView
locals_code = locals.keys.map! { |key| "#{key} = local_assigns[:#{key}];" }.join
+ code = @handler.call(self)
+ if code.sub!(/\A(#.*coding.*)\n/, '')
+ encoding_comment = $1
+ elsif defined?(Encoding) && Encoding.respond_to?(:default_external)
+ encoding_comment = "#coding:#{Encoding.default_external}"
+ end
+
source = <<-end_src
def #{method_name}(local_assigns)
- old_output_buffer = output_buffer;#{locals_code};#{@handler.call(self)}
+ old_output_buffer = output_buffer;#{locals_code};#{code}
ensure
self.output_buffer = old_output_buffer
end
end_src
+ if encoding_comment
+ source = "#{encoding_comment}\n#{source}"
+ line = -1
+ else
+ line = 0
+ end
+
begin
- ActionView::Base::CompiledTemplates.module_eval(source, identifier, 0)
+ ActionView::Base::CompiledTemplates.module_eval(source, identifier, line)
method_name
rescue Exception => e # errors from template code
if logger = (view && view.logger)
@@ -79,4 +93,4 @@ module ActionView
"_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb
index 39d0017dd8..7199da3441 100644
--- a/actionpack/test/controller/cookie_test.rb
+++ b/actionpack/test/controller/cookie_test.rb
@@ -123,6 +123,12 @@ class CookieTest < ActionController::TestCase
assert_cookie_header "user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT"
end
+ def test_cookies_persist_throughout_request
+ get :authenticate
+ cookies = @controller.send(:cookies)
+ assert_equal 'david', cookies['user_name']
+ end
+
private
def assert_cookie_header(expected)
header = @response.headers["Set-Cookie"]
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index bdcc24b371..df0a9dbfb0 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -1,10 +1,32 @@
require 'abstract_unit'
+require 'active_support/core_ext/symbol'
-class << ActionController::Base
- %w(append_around_filter prepend_after_filter prepend_around_filter prepend_before_filter skip_after_filter skip_before_filter skip_filter).each do |pending|
- define_method(pending) do |*args|
- $stderr.puts "#{pending} unimplemented: #{args.inspect}"
- end unless method_defined?(pending)
+class ActionController::Base
+ class << self
+ %w(append_around_filter prepend_after_filter prepend_around_filter prepend_before_filter skip_after_filter skip_before_filter skip_filter).each do |pending|
+ define_method(pending) do |*args|
+ $stderr.puts "#{pending} unimplemented: #{args.inspect}"
+ end unless method_defined?(pending)
+ end
+
+ if defined?(ActionController::Http)
+ def before_filters
+ filters = _process_action_callbacks.select { |c| c.kind == :before }
+ filters.map! { |c| c.instance_variable_get(:@raw_filter) }
+ end
+ end
+ end
+
+ if defined?(ActionController::Http)
+ def assigns(key = nil)
+ assigns = {}
+ instance_variable_names.each do |ivar|
+ next if ActionController::Base.protected_instance_variables.include?(ivar)
+ assigns[ivar[1..-1]] = instance_variable_get(ivar)
+ end
+
+ key.nil? ? assigns : assigns[key.to_s]
+ end
end
end
@@ -209,24 +231,29 @@ class FilterTest < ActionController::TestCase
end
class ConditionalParentOfConditionalSkippingController < ConditionalFilterController
- before_filter :conditional_in_parent, :only => [:show, :another_action]
- after_filter :conditional_in_parent, :only => [:show, :another_action]
+ before_filter :conditional_in_parent_before, :only => [:show, :another_action]
+ after_filter :conditional_in_parent_after, :only => [:show, :another_action]
private
- def conditional_in_parent
+ def conditional_in_parent_before
@ran_filter ||= []
- @ran_filter << 'conditional_in_parent'
+ @ran_filter << 'conditional_in_parent_before'
+ end
+
+ def conditional_in_parent_after
+ @ran_filter ||= []
+ @ran_filter << 'conditional_in_parent_after'
end
end
class ChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController
- skip_before_filter :conditional_in_parent, :only => :another_action
- skip_after_filter :conditional_in_parent, :only => :another_action
+ skip_before_filter :conditional_in_parent_before, :only => :another_action
+ skip_after_filter :conditional_in_parent_after, :only => :another_action
end
class AnotherChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController
- skip_before_filter :conditional_in_parent, :only => :show
+ skip_before_filter :conditional_in_parent_before, :only => :show
end
class ProcController < PrependingController
@@ -571,11 +598,22 @@ class FilterTest < ActionController::TestCase
assert_equal "before and after", assigns["execution_log"]
end
- def test_prepending_and_appending_around_filter
- controller = test_process(MixedFilterController)
- assert_equal " before aroundfilter before procfilter before appended aroundfilter " +
- " after appended aroundfilter after aroundfilter after procfilter ",
- MixedFilterController.execution_log
+ for_tag(:old_base) do
+ def test_prepending_and_appending_around_filter
+ controller = test_process(MixedFilterController)
+ assert_equal " before aroundfilter before procfilter before appended aroundfilter " +
+ " after appended aroundfilter after aroundfilter after procfilter ",
+ MixedFilterController.execution_log
+ end
+ end
+
+ for_tag(:new_base) do
+ def test_prepending_and_appending_around_filter
+ controller = test_process(MixedFilterController)
+ assert_equal " before aroundfilter before procfilter before appended aroundfilter " +
+ " after appended aroundfilter after procfilter after aroundfilter ",
+ MixedFilterController.execution_log
+ end
end
def test_rendering_breaks_filtering_chain
@@ -600,7 +638,7 @@ class FilterTest < ActionController::TestCase
%w(foo bar baz).each do |action|
request = ActionController::TestRequest.new
request.query_parameters[:choose] = action
- response = DynamicDispatchController.action.call(request.env).last
+ response = DynamicDispatchController.action(action).call(request.env).last
assert_equal action, response.body
end
end
@@ -636,18 +674,18 @@ class FilterTest < ActionController::TestCase
def test_conditional_skipping_of_filters_when_parent_filter_is_also_conditional
test_process(ChildOfConditionalParentController)
- assert_equal %w( conditional_in_parent conditional_in_parent ), assigns['ran_filter']
+ assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter']
test_process(ChildOfConditionalParentController, 'another_action')
assert_nil assigns['ran_filter']
end
def test_condition_skipping_of_filters_when_siblings_also_have_conditions
test_process(ChildOfConditionalParentController)
- assert_equal %w( conditional_in_parent conditional_in_parent ), assigns['ran_filter'], "1"
+ assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter']
test_process(AnotherChildOfConditionalParentController)
- assert_equal nil, assigns['ran_filter']
+ assert_equal %w( conditional_in_parent_after ), assigns['ran_filter']
test_process(ChildOfConditionalParentController)
- assert_equal %w( conditional_in_parent conditional_in_parent ), assigns['ran_filter']
+ assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter']
end
def test_changing_the_requirements
@@ -801,7 +839,9 @@ class ControllerWithAllTypesOfFilters < PostsController
end
class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters
+ $vbf = true
skip_filter :around_again
+ $vbf = false
skip_filter :after
end
@@ -864,9 +904,18 @@ class YieldingAroundFiltersTest < ActionController::TestCase
end
end
- def test_filter_order_with_all_filter_types
- test_process(ControllerWithAllTypesOfFilters,'no_raise')
- assert_equal 'before around (before yield) around_again (before yield) around_again (after yield) around (after yield) after', assigns['ran_filter'].join(' ')
+ for_tag(:old_base) do
+ def test_filter_order_with_all_filter_types
+ test_process(ControllerWithAllTypesOfFilters,'no_raise')
+ assert_equal 'before around (before yield) around_again (before yield) around_again (after yield) around (after yield) after', assigns['ran_filter'].join(' ')
+ end
+ end
+
+ for_tag(:new_base) do
+ def test_filter_order_with_all_filter_types
+ test_process(ControllerWithAllTypesOfFilters,'no_raise')
+ assert_equal 'before around (before yield) around_again (before yield) around_again (after yield) after around (after yield)', assigns['ran_filter'].join(' ')
+ end
end
def test_filter_order_with_skip_filter_method
@@ -879,7 +928,6 @@ class YieldingAroundFiltersTest < ActionController::TestCase
response = test_process(controller, 'fail_1')
assert_equal ' ', response.body
assert_equal 1, controller.instance_variable_get(:@try)
- assert controller.instance_variable_get(:@before_filter_chain_aborted)
end
def test_second_filter_in_multiple_before_filter_chain_halts
@@ -887,7 +935,6 @@ class YieldingAroundFiltersTest < ActionController::TestCase
response = test_process(controller, 'fail_2')
assert_equal ' ', response.body
assert_equal 2, controller.instance_variable_get(:@try)
- assert controller.instance_variable_get(:@before_filter_chain_aborted)
end
def test_last_filter_in_multiple_before_filter_chain_halts
@@ -895,7 +942,6 @@ class YieldingAroundFiltersTest < ActionController::TestCase
response = test_process(controller, 'fail_3')
assert_equal ' ', response.body
assert_equal 3, controller.instance_variable_get(:@try)
- assert controller.instance_variable_get(:@before_filter_chain_aborted)
end
protected
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 84e27d7779..c448f36cb3 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -122,7 +122,7 @@ class FlashTest < ActionController::TestCase
assert_nil assigns["flash_copy"]["that"], "On second flash"
assert_equal "hello again", assigns["flash_copy"]["this"], "On second flash"
end
-
+
def test_flash_after_reset_session
get :use_flash_after_reset_session
assert_equal "hello", assigns["flashy_that"]
@@ -130,6 +130,11 @@ class FlashTest < ActionController::TestCase
assert_nil assigns["flashy_that_reset"]
end
+ def test_does_not_set_the_session_if_the_flash_is_empty
+ get :std_action
+ assert_nil session["flash"]
+ end
+
def test_sweep_after_halted_filter_chain
get :std_action
assert_nil assigns["flash_copy"]["foo"]
@@ -140,4 +145,19 @@ class FlashTest < ActionController::TestCase
get :std_action
assert_nil assigns["flash_copy"]["foo"]
end
+
+ def test_keep_and_discard_return_values
+ flash = ActionController::Flash::FlashHash.new
+ flash.update(:foo => :foo_indeed, :bar => :bar_indeed)
+
+ assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed
+ assert_nil flash.discard(:unknown) # non existant key passed
+ assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard()) # nothing passed
+ assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard(nil)) # nothing passed
+
+ assert_equal(:foo_indeed, flash.keep(:foo)) # valid key passed
+ assert_nil flash.keep(:unknown) # non existant key passed
+ assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep()) # nothing passed
+ assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep(nil)) # nothing passed
+ end
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 11bffdb42e..6b08a04b10 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -90,6 +90,11 @@ class StaticSegmentTest < Test::Unit::TestCase
assert_equal 'Hello World', s.interpolation_chunk
end
+ def test_value_should_not_be_double_unescaped
+ s = ROUTING::StaticSegment.new('%D0%9A%D0%B0%D1%80%D1%82%D0%B0') # Карта
+ assert_equal '%D0%9A%D0%B0%D1%80%D1%82%D0%B0', s.interpolation_chunk
+ end
+
def test_regexp_chunk_should_escape_specials
s = ROUTING::StaticSegment.new('Hello*World')
assert_equal 'Hello\*World', s.regexp_chunk
@@ -644,9 +649,8 @@ class RoutingTest < Test::Unit::TestCase
ActionController::Routing.use_controllers! nil
- silence_warnings do
- Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + '/controller_fixtures')
- end
+ Object.send(:remove_const, :RAILS_ROOT) if defined?(::RAILS_ROOT)
+ Object.const_set(:RAILS_ROOT, File.dirname(__FILE__) + '/controller_fixtures')
ActionController::Routing.controller_paths = [
RAILS_ROOT, RAILS_ROOT + '/app/controllers', RAILS_ROOT + '/vendor/plugins/bad_plugin/lib'
@@ -2482,7 +2486,8 @@ end
class RouteLoadingTest < Test::Unit::TestCase
def setup
routes.instance_variable_set '@routes_last_modified', nil
- silence_warnings { Object.const_set :RAILS_ROOT, '.' }
+ Object.remove_const(:RAILS_ROOT) if defined?(::RAILS_ROOT)
+ Object.const_set :RAILS_ROOT, '.'
routes.add_configuration_file(File.join(RAILS_ROOT, 'config', 'routes.rb'))
@stat = stub_everything
diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb
index 0ac10634b2..c732d1c910 100644
--- a/actionpack/test/controller/view_paths_test.rb
+++ b/actionpack/test/controller/view_paths_test.rb
@@ -22,7 +22,7 @@ class ViewLoadPathsTest < ActionController::TestCase
end
def setup
- TestController.view_paths = nil
+ # TestController.view_paths = nil
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@@ -48,7 +48,7 @@ class ViewLoadPathsTest < ActionController::TestCase
def assert_paths(*paths)
controller = paths.first.is_a?(Class) ? paths.shift : @controller
- assert_equal expand(paths), controller.view_paths.map(&:to_s)
+ assert_equal expand(paths), controller.view_paths.map { |p| p.to_s }
end
def test_template_load_path_was_set_correctly
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 0c0c087340..ce1973853e 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -61,7 +61,7 @@ class ShowExceptionsTest < ActionController::IntegrationTest
get "/not_found"
assert_response 404
- assert_match /ActionController::UnknownAction/, body
+ assert_match /#{ActionController::UnknownAction.name}/, body
get "/method_not_allowed"
assert_response 405
@@ -99,7 +99,7 @@ class ShowExceptionsTest < ActionController::IntegrationTest
get "/not_found"
assert_response 404
- assert_match /ActionController::UnknownAction/, body
+ assert_match /#{ActionController::UnknownAction.name}/, body
get "/method_not_allowed"
assert_response 405
diff --git a/actionpack/test/fixtures/test/utf8.html.erb b/actionpack/test/fixtures/test/utf8.html.erb
index 0b4d19aa0e..14fe12debc 100644
--- a/actionpack/test/fixtures/test/utf8.html.erb
+++ b/actionpack/test/fixtures/test/utf8.html.erb
@@ -1,2 +1,4 @@
Русский текст
-日本語のテキスト \ No newline at end of file
+<%= "日".encoding %>
+<%= @output_buffer.encoding %>
+<%= __ENCODING__ %>
diff --git a/actionpack/test/fixtures/test/utf8_magic.html.erb b/actionpack/test/fixtures/test/utf8_magic.html.erb
new file mode 100644
index 0000000000..58cd03b439
--- /dev/null
+++ b/actionpack/test/fixtures/test/utf8_magic.html.erb
@@ -0,0 +1,5 @@
+<%# encoding: utf-8 -%>
+Русский текст
+<%= "日".encoding %>
+<%= @output_buffer.encoding %>
+<%= __ENCODING__ %>
diff --git a/actionpack/test/template/body_parts_test.rb b/actionpack/test/template/body_parts_test.rb
index e17092a452..4e7aa63f96 100644
--- a/actionpack/test/template/body_parts_test.rb
+++ b/actionpack/test/template/body_parts_test.rb
@@ -4,6 +4,9 @@ class BodyPartsTest < ActionController::TestCase
RENDERINGS = [Object.new, Object.new, Object.new]
class TestController < ActionController::Base
+ def performed?
+ defined?(ActionController::Http) ? true : super
+ end
def index
RENDERINGS.each do |rendering|
@template.punctuate_body! rendering
@@ -16,7 +19,7 @@ class BodyPartsTest < ActionController::TestCase
def test_body_parts
get :index
- pending do
+ pending(:old_base) do
# TestProcess buffers body_parts into body
# TODO: Rewrite test w/o going through process
assert_equal RENDERINGS, @response.body_parts
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index ea0be4a27a..5ca4d4d6ea 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -253,14 +253,14 @@ class FormTagHelperTest < ActionView::TestCase
def test_submit_tag
assert_dom_equal(
- %(<input name='commit' type='submit' value='Save' onclick="if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }else { hiddenCommit = this.cloneNode(false);hiddenCommit.setAttribute('type', 'hidden');this.form.appendChild(hiddenCommit); }this.setAttribute('originalValue', this.value);this.disabled = true;this.value='Saving...';alert('hello!');result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;" />),
+ %(<input name='commit' onclick="if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }else { hiddenCommit = document.createElement('input');hiddenCommit.type = 'hidden';hiddenCommit.value = this.value;hiddenCommit.name = this.name;this.form.appendChild(hiddenCommit); }this.setAttribute('originalValue', this.value);this.disabled = true;this.value='Saving...';alert('hello!');result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;" type="submit" value="Save" />),
submit_tag("Save", :disable_with => "Saving...", :onclick => "alert('hello!')")
)
end
def test_submit_tag_with_no_onclick_options
assert_dom_equal(
- %(<input name='commit' type='submit' value='Save' onclick="if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }else { hiddenCommit = this.cloneNode(false);hiddenCommit.setAttribute('type', 'hidden');this.form.appendChild(hiddenCommit); }this.setAttribute('originalValue', this.value);this.disabled = true;this.value='Saving...';result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;" />),
+ %(<input name='commit' onclick="if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }else { hiddenCommit = document.createElement('input');hiddenCommit.type = 'hidden';hiddenCommit.value = this.value;hiddenCommit.name = this.name;this.form.appendChild(hiddenCommit); }this.setAttribute('originalValue', this.value);this.disabled = true;this.value='Saving...';result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;" type="submit" value="Save" />),
submit_tag("Save", :disable_with => "Saving...")
)
end
@@ -274,7 +274,7 @@ class FormTagHelperTest < ActionView::TestCase
def test_submit_tag_with_confirmation_and_with_disable_with
assert_dom_equal(
- %(<input name="commit" type="submit" value="Save" onclick="if (!confirm('Are you sure?')) return false; if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }else { hiddenCommit = this.cloneNode(false);hiddenCommit.setAttribute('type', 'hidden');this.form.appendChild(hiddenCommit); }this.setAttribute('originalValue', this.value);this.disabled = true;this.value='Saving...';result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;" />),
+ %(<input name="commit" onclick="if (!confirm('Are you sure?')) return false; if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }else { hiddenCommit = document.createElement('input');hiddenCommit.type = 'hidden';hiddenCommit.value = this.value;hiddenCommit.name = this.name;this.form.appendChild(hiddenCommit); }this.setAttribute('originalValue', this.value);this.disabled = true;this.value='Saving...';result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;" type="submit" value="Save" />),
submit_tag("Save", :disable_with => "Saving...", :confirm => "Are you sure?")
)
end
diff --git a/actionpack/test/template/output_buffer_test.rb b/actionpack/test/template/output_buffer_test.rb
index 171cfb63e1..36bbaf9099 100644
--- a/actionpack/test/template/output_buffer_test.rb
+++ b/actionpack/test/template/output_buffer_test.rb
@@ -9,33 +9,52 @@ class OutputBufferTest < ActionController::TestCase
tests TestController
- def test_flush_output_buffer
- pending
- # TODO: This tests needs to be rewritten due
- # The @response is not the same response object assigned
- # to the @controller.template
-
- # Start with the default body parts
- # ---
- # get :index
- # assert_equal ['foo'], @response.body_parts
- # assert_nil @controller.template.output_buffer
- #
- # # Nil output buffer is skipped
- # @controller.template.flush_output_buffer
- # assert_nil @controller.template.output_buffer
- # assert_equal ['foo'], @response.body_parts
- #
- # # Empty output buffer is skipped
- # @controller.template.output_buffer = ''
- # @controller.template.flush_output_buffer
- # assert_equal '', @controller.template.output_buffer
- # assert_equal ['foo'], @response.body_parts
- #
- # # Flushing appends the output buffer to the body parts
- # @controller.template.output_buffer = 'bar'
- # @controller.template.flush_output_buffer
- # assert_equal '', @controller.template.output_buffer
- # assert_equal ['foo', 'bar'], @response.body_parts
+ def setup
+ get :index
+ assert_equal ['foo'], body_parts
end
+
+ test 'output buffer is nil after rendering' do
+ assert_nil output_buffer
+ end
+
+ test 'flushing ignores nil output buffer' do
+ @controller.template.flush_output_buffer
+ assert_nil output_buffer
+ assert_equal ['foo'], body_parts
+ end
+
+ test 'flushing ignores empty output buffer' do
+ @controller.template.output_buffer = ''
+ @controller.template.flush_output_buffer
+ assert_equal '', output_buffer
+ assert_equal ['foo'], body_parts
+ end
+
+ test 'flushing appends the output buffer to the body parts' do
+ @controller.template.output_buffer = 'bar'
+ @controller.template.flush_output_buffer
+ assert_equal '', output_buffer
+ assert_equal ['foo', 'bar'], body_parts
+ end
+
+ if '1.9'.respond_to?(:force_encoding)
+ test 'flushing preserves output buffer encoding' do
+ original_buffer = ' '.force_encoding(Encoding::EUC_JP)
+ @controller.template.output_buffer = original_buffer
+ @controller.template.flush_output_buffer
+ assert_equal ['foo', original_buffer], body_parts
+ assert_not_equal original_buffer, output_buffer
+ assert_equal Encoding::EUC_JP, output_buffer.encoding
+ end
+ end
+
+ protected
+ def output_buffer
+ @controller.template.output_buffer
+ end
+
+ def body_parts
+ @controller.template.response.body_parts
+ end
end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 71291f009c..20cd4cc1d4 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -247,10 +247,27 @@ module RenderTestCases
end
if '1.9'.respond_to?(:force_encoding)
- def test_render_utf8_template
- result = @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield")
- assert_equal "Русский текст\n日本語のテキスト", result
- assert_equal Encoding::UTF_8, result.encoding
+ def test_render_utf8_template_with_magic_comment
+ with_external_encoding Encoding::ASCII_8BIT do
+ result = @view.render(:file => "test/utf8_magic.html.erb", :layouts => "layouts/yield")
+ assert_equal "Русский текст\nUTF-8\nUTF-8\nUTF-8\n", result
+ assert_equal Encoding::UTF_8, result.encoding
+ end
+ end
+
+ def test_render_utf8_template_with_default_external_encoding
+ with_external_encoding Encoding::UTF_8 do
+ result = @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield")
+ assert_equal "Русский текст\nUTF-8\nUTF-8\nUTF-8\n", result
+ assert_equal Encoding::UTF_8, result.encoding
+ end
+ end
+
+ def with_external_encoding(encoding)
+ old, Encoding.default_external = Encoding.default_external, encoding
+ yield
+ ensure
+ Encoding.default_external = old
end
end
end
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index 544f49310a..ae2fbdb002 100755
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -7,9 +7,8 @@ task :default => :test
Rake::TestTask.new do |t|
t.libs << "test"
- t.pattern = 'test/**/*_test.rb'
+ t.test_files = Dir.glob("test/cases/**/*_test.rb").sort
t.verbose = true
- t.warning = true
end
task :isolated_test do
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 4ed7b0889d..803f5b0157 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -1,5 +1,41 @@
-require 'active_model/observing'
-# disabled until they're tested
-# require 'active_model/callbacks'
-# require 'active_model/validations'
-require 'active_model/base' \ No newline at end of file
+#--
+# Copyright (c) 2004-2009 David Heinemeier Hansson
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#++
+
+activesupport_path = "#{File.dirname(__FILE__)}/../../../activesupport/lib"
+$:.unshift(activesupport_path) if File.directory?(activesupport_path)
+require 'active_support'
+
+require 'active_support/inflector'
+
+module ActiveModel
+ autoload :Base, 'active_model/base'
+ autoload :Observing, 'active_model/observing'
+ autoload :Validations, 'active_model/validations'
+ autoload :Errors, 'active_model/errors'
+ autoload :DeprecatedErrorMethods, 'active_model/deprecated_error_methods'
+ autoload :TestCase, 'active_model/test_case'
+ autoload :StateMachine, 'active_model/state_machine'
+ autoload :ValidationsRepairHelper, 'active_model/validations_repair_helper'
+end
+
+I18n.load_path << File.dirname(__FILE__) + '/active_model/locale/en.yml'
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
deleted file mode 100644
index c94f76109f..0000000000
--- a/activemodel/lib/active_model/callbacks.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'active_model/core'
-
-module ActiveModel
- module Callbacks
-
- end
-end \ No newline at end of file
diff --git a/activemodel/lib/active_model/core.rb b/activemodel/lib/active_model/core.rb
deleted file mode 100644
index 4201bcf158..0000000000
--- a/activemodel/lib/active_model/core.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-activesupport_path = "#{File.dirname(__FILE__)}/../../../activesupport/lib"
-$:.unshift(activesupport_path) if File.directory?(activesupport_path)
-require 'active_support/inflector'
diff --git a/activemodel/lib/active_model/deprecated_error_methods.rb b/activemodel/lib/active_model/deprecated_error_methods.rb
index e0cbd9ba29..433de8931a 100644
--- a/activemodel/lib/active_model/deprecated_error_methods.rb
+++ b/activemodel/lib/active_model/deprecated_error_methods.rb
@@ -1,18 +1,18 @@
module ActiveModel
module DeprecatedErrorMethods
def on(attribute)
- ActiveSupport::Deprecation.warn "Errors#on have been deprecated, use Errors#[] instead"
- self[attribute]
+ message = "Errors#on have been deprecated, use Errors#[] instead.\n"
+ message << "Also note that the behaviour of Errors#[] has changed. Errors#[] now always returns an Array. An empty Array is "
+ message << "returned when there are no errors on the specified attribute."
+ ActiveSupport::Deprecation.warn(message)
+
+ errors = self[attribute]
+ errors.size < 2 ? errors.first : errors
end
def on_base
ActiveSupport::Deprecation.warn "Errors#on_base have been deprecated, use Errors#[:base] instead"
- on(:base)
- end
-
- def add(attribute, msg = Errors.default_error_messages[:invalid])
- ActiveSupport::Deprecation.warn "Errors#add(attribute, msg) has been deprecated, use Errors#[attribute] << msg instead"
- self[attribute] << msg
+ ActiveSupport::Deprecation.silence { on(:base) }
end
def add_to_base(msg)
@@ -25,11 +25,6 @@ module ActiveModel
self[attribute].any?
end
- def full_messages
- ActiveSupport::Deprecation.warn "Errors#full_messages has been deprecated, use Errors#to_a instead"
- to_a
- end
-
def each_full
ActiveSupport::Deprecation.warn "Errors#each_full has been deprecated, use Errors#to_a.each instead"
to_a.each { |error| yield error }
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index bcf0810290..4be91d0505 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -1,40 +1,18 @@
module ActiveModel
class Errors < Hash
include DeprecatedErrorMethods
-
- @@default_error_messages = {
- :inclusion => "is not included in the list",
- :exclusion => "is reserved",
- :invalid => "is invalid",
- :confirmation => "doesn't match confirmation",
- :accepted => "must be accepted",
- :empty => "can't be empty",
- :blank => "can't be blank",
- :too_long => "is too long (maximum is %d characters)",
- :too_short => "is too short (minimum is %d characters)",
- :wrong_length => "is the wrong length (should be %d characters)",
- :taken => "has already been taken",
- :not_a_number => "is not a number",
- :greater_than => "must be greater than %d",
- :greater_than_or_equal_to => "must be greater than or equal to %d",
- :equal_to => "must be equal to %d",
- :less_than => "must be less than %d",
- :less_than_or_equal_to => "must be less than or equal to %d",
- :odd => "must be odd",
- :even => "must be even"
- }
-
- ##
- # :singleton-method:
- # Holds a hash with all the default error messages that can be replaced by your own copy or localizations.
- cattr_accessor :default_error_messages
+
+ def initialize(base)
+ @base = base
+ super()
+ end
alias_method :get, :[]
alias_method :set, :[]=
def [](attribute)
if errors = get(attribute.to_sym)
- errors.size == 1 ? errors.first : errors
+ errors
else
set(attribute.to_sym, [])
end
@@ -55,28 +33,126 @@ module ActiveModel
end
def to_a
- inject([]) do |errors_with_attributes, (attribute, errors)|
- if error.blank?
- errors_with_attributes
- else
- if attr == :base
- errors_with_attributes << error
- else
- errors_with_attributes << (attribute.to_s.humanize + " " + error)
- end
- end
- end
+ full_messages
+ end
+
+ def count
+ to_a.size
end
def to_xml(options={})
+ require 'builder' unless defined? ::Builder
options[:root] ||= "errors"
options[:indent] ||= 2
- options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
+ options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
options[:builder].instruct! unless options.delete(:skip_instruct)
options[:builder].errors do |e|
to_a.each { |error| e.error(error) }
end
end
+
+ # Adds an error message (+messsage+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
+ # for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
+ # 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 +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
+ def add(attribute, message = nil, options = {})
+ message ||= :invalid
+ message = generate_message(attribute, message, options) if message.is_a?(Symbol)
+ self[attribute] << message
+ end
+
+ # Will add an error message to each of the attributes in +attributes+ that is empty.
+ def add_on_empty(attributes, custom_message = nil)
+ [attributes].flatten.each do |attribute|
+ value = @base.get_attribute_value(attribute)
+ is_empty = value.respond_to?(:empty?) ? value.empty? : false
+ add(attribute, :empty, :default => custom_message) unless !value.nil? && !is_empty
+ end
+ end
+
+ # Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
+ def add_on_blank(attributes, custom_message = nil)
+ [attributes].flatten.each do |attribute|
+ value = @base.get_attribute_value(attribute)
+ add(attribute, :blank, :default => custom_message) if value.blank?
+ end
+ end
+
+ # Returns all the full error messages in an array.
+ #
+ # class Company
+ # validates_presence_of :name, :address, :email
+ # validates_length_of :name, :in => 5..30
+ # end
+ #
+ # company = Company.create(:address => '123 First St.')
+ # company.errors.full_messages # =>
+ # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
+ def full_messages(options = {})
+ full_messages = []
+
+ each do |attribute, messages|
+ next if messages.empty?
+
+ if attribute == :base
+ messages.each {|m| full_messages << m }
+ else
+ attr_name = attribute.to_s.humanize
+ prefix = attr_name + I18n.t('activemodel.errors.format.separator', :default => ' ')
+ messages.each do |m|
+ full_messages << "#{prefix}#{m}"
+ end
+ end
+ end
+
+ full_messages
+ end
+
+ # Translates an error message in it's default scope (<tt>activemodel.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>activemodel.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>activemodel.errors.models.admin.attributes.title.blank</tt></li>
+ # <li><tt>activemodel.errors.models.admin.blank</tt></li>
+ # <li><tt>activemodel.errors.models.user.attributes.title.blank</tt></li>
+ # <li><tt>activemodel.errors.models.user.blank</tt></li>
+ # <li><tt>activemodel.errors.messages.blank</tt></li>
+ # <li>any default you provided through the +options+ hash (in the activemodel.errors scope)</li>
+ # </ol>
+ def generate_message(attribute, message = :invalid, options = {})
+ message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
+
+ klass_ancestors = [@base.class]
+ klass_ancestors += @base.class.ancestors.reject {|x| x.is_a?(Module)}
+
+ defaults = klass_ancestors.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.get_attribute_value(attribute)
+
+ options = { :default => defaults,
+ :model => @base.class.name.humanize,
+ :attribute => attribute.to_s.humanize,
+ :value => value,
+ :scope => [:activemodel, :errors]
+ }.merge(options)
+
+ I18n.translate(key, options)
+ end
end
end \ No newline at end of file
diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml
new file mode 100644
index 0000000000..0c2cf9ea33
--- /dev/null
+++ b/activemodel/lib/active_model/locale/en.yml
@@ -0,0 +1,24 @@
+en:
+ activemodel:
+ errors:
+ # The values :model, :attribute and :value are always available for interpolation
+ # The value :count is available when applicable. Can be used for pluralization.
+ messages:
+ inclusion: "is not included in the list"
+ exclusion: "is reserved"
+ invalid: "is invalid"
+ confirmation: "doesn't match confirmation"
+ accepted: "must be accepted"
+ empty: "can't be empty"
+ blank: "can't be blank"
+ too_long: "is too long (maximum is {{count}} characters)"
+ too_short: "is too short (minimum is {{count}} characters)"
+ wrong_length: "is the wrong length (should be {{count}} characters)"
+ not_a_number: "is not a number"
+ greater_than: "must be greater than {{count}}"
+ greater_than_or_equal_to: "must be greater than or equal to {{count}}"
+ equal_to: "must be equal to {{count}}"
+ less_than: "must be less than {{count}}"
+ less_than_or_equal_to: "must be less than or equal to {{count}}"
+ odd: "must be odd"
+ even: "must be even"
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index 9e99d7472c..d3c6d8e482 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -1,6 +1,5 @@
require 'observer'
require 'singleton'
-require 'active_model/core'
module ActiveModel
module Observing
diff --git a/activemodel/lib/active_model/state_machine.rb b/activemodel/lib/active_model/state_machine.rb
index bce90fd743..1172d31ea3 100644
--- a/activemodel/lib/active_model/state_machine.rb
+++ b/activemodel/lib/active_model/state_machine.rb
@@ -1,5 +1,10 @@
module ActiveModel
module StateMachine
+ autoload :Event, 'active_model/state_machine/event'
+ autoload :Machine, 'active_model/state_machine/machine'
+ autoload :State, 'active_model/state_machine/state'
+ autoload :StateTransition, 'active_model/state_machine/state_transition'
+
class InvalidTransition < Exception
end
diff --git a/activemodel/lib/active_model/test_case.rb b/activemodel/lib/active_model/test_case.rb
new file mode 100644
index 0000000000..4cb5c9cbc0
--- /dev/null
+++ b/activemodel/lib/active_model/test_case.rb
@@ -0,0 +1,18 @@
+require "active_support/test_case"
+
+module ActiveModel #:nodoc:
+ class TestCase < ActiveSupport::TestCase #:nodoc:
+ def with_kcode(kcode)
+ if RUBY_VERSION < '1.9'
+ orig_kcode, $KCODE = $KCODE, kcode
+ begin
+ yield
+ ensure
+ $KCODE = orig_kcode
+ end
+ else
+ yield
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 460d2d82e5..336c2757fc 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -1,16 +1,13 @@
-require 'active_model/core'
-
module ActiveModel
module Validations
- def self.included(base) # :nodoc:
- base.extend(ClassMethods)
- base.__send__(:include, ActiveSupport::Callbacks)
- base.define_callbacks :validate, :validate_on_create, :validate_on_update
+ extend ActiveSupport::Concern
+ include ActiveSupport::Callbacks
+
+ included do
+ define_callbacks :validate
end
module ClassMethods
- DEFAULT_VALIDATION_OPTIONS = { :on => :save, :allow_nil => false, :allow_blank => false, :message => nil }.freeze
-
# Adds a validation method or block to the class. This is useful when
# overriding the +validate+ instance method becomes too unwieldly and
# you're looking for more descriptive declaration of your validations.
@@ -38,7 +35,7 @@ module ActiveModel
# end
#
# This usage applies to +validate_on_create+ and +validate_on_update as well+.
- #
+
# Validates each attribute against a block.
#
# class Person < ActiveRecord::Base
@@ -48,7 +45,7 @@ module ActiveModel
# end
#
# Options:
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
+ # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
@@ -62,9 +59,9 @@ module ActiveModel
attrs = attrs.flatten
# Declare the validation.
- send(validation_method(options[:on] || :save), options) do |record|
+ send(validation_method(options[:on]), options) do |record|
attrs.each do |attr|
- value = record.send(attr)
+ value = record.get_attribute_value(attr)
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
yield record, attr, value
end
@@ -72,50 +69,31 @@ module ActiveModel
end
private
- def validation_method(on)
- case on
- when :save then :validate
- when :create then :validate_on_create
- when :update then :validate_on_update
- end
- end
+
+ def validation_method(on)
+ :validate
+ end
end
# Returns the Errors object that holds all information about attribute error messages.
def errors
- @errors ||= Errors.new
+ @errors ||= Errors.new(self)
end
# Runs all the specified validations and returns true if no errors were added otherwise false.
def valid?
errors.clear
-
run_callbacks(:validate)
-
- if responds_to?(:validate)
- ActiveSupport::Deprecations.warn "Base#validate has been deprecated, please use Base.validate :method instead"
- validate
- end
-
- if new_record?
- run_callbacks(:validate_on_create)
-
- if responds_to?(:validate_on_create)
- ActiveSupport::Deprecations.warn(
- "Base#validate_on_create has been deprecated, please use Base.validate_on_create :method instead")
- validate_on_create
- end
- else
- run_callbacks(:validate_on_update)
+ errors.empty?
+ end
- if responds_to?(:validate_on_update)
- ActiveSupport::Deprecations.warn(
- "Base#validate_on_update has been deprecated, please use Base.validate_on_update :method instead")
- validate_on_update
- end
- end
+ # Performs the opposite of <tt>valid?</tt>. Returns true if errors were added, false otherwise.
+ def invalid?
+ !valid?
+ end
- errors.empty?
+ def get_attribute_value(attribute)
+ respond_to?(attribute.to_sym) ? send(attribute.to_sym) : instance_variable_get(:"@#{attribute}")
end
end
end
@@ -123,4 +101,4 @@ end
Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
filename = File.basename(path)
require "active_model/validations/#{filename}"
-end \ No newline at end of file
+end
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 9be7d51ffb..0c9ef51726 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -8,16 +8,16 @@ module ActiveModel
# validates_acceptance_of :eula, :message => "must be abided"
# end
#
- # If the database column does not exist, the <tt>:terms_of_service</tt> attribute is entirely virtual. This check is
- # performed only if <tt>:terms_of_service</tt> is not +nil+ and by default on save.
+ # If the database column does not exist, the +terms_of_service+ attribute is entirely virtual. This check is
+ # performed only if +terms_of_service+ is not +nil+ and by default on save.
#
# Configuration options:
- # * <tt>:message</tt> - A custom error message (default is: "must be accepted")
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
- # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+. (default is +true+)
+ # * <tt>:message</tt> - A custom error message (default is: "must be accepted").
+ # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
+ # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is true).
# * <tt>:accept</tt> - Specifies value that is considered accepted. The default value is a string "1", which
# makes it easy to relate to an HTML checkbox. This should be set to +true+ if you are validating a database
- # column, since the attribute is typecasted from "1" to +true+ before validation.
+ # column, since the attribute is typecast from "1" to +true+ before validation.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
@@ -25,19 +25,22 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_acceptance_of(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:accepted], :on => :save, :allow_nil => true, :accept => "1" }
+ configuration = { :allow_nil => true, :accept => "1" }
configuration.update(attr_names.extract_options!)
db_cols = begin
column_names
- rescue ActiveRecord::StatementInvalid
+ rescue Exception # To ignore both statement and connection errors
[]
end
+
names = attr_names.reject { |name| db_cols.include?(name.to_s) }
attr_accessor(*names)
validates_each(attr_names,configuration) do |record, attr_name, value|
- record.errors.add(attr_name, configuration[:message]) unless value == configuration[:accept]
+ unless value == configuration[:accept]
+ record.errors.add(attr_name, :accepted, :default => configuration[:message])
+ end
end
end
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index ba4a18adb7..b9823172f7 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -21,8 +21,8 @@ module ActiveModel
# validates_presence_of :password_confirmation, :if => :password_changed?
#
# Configuration options:
- # * <tt>:message</tt> - A custom error message (default is: "doesn't match confirmation")
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
+ # * <tt>:message</tt> - A custom error message (default is: "doesn't match confirmation").
+ # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
@@ -30,13 +30,14 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_confirmation_of(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save }
- configuration.update(attr_names.extract_options!)
+ configuration = attr_names.extract_options!
attr_accessor(*(attr_names.map { |n| "#{n}_confirmation" }))
validates_each(attr_names, configuration) do |record, attr_name, value|
- record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
+ unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
+ record.errors.add(attr_name, :confirmation, :default => configuration[:message])
+ end
end
end
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 1592000fc7..0aa9848ee1 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -6,14 +6,14 @@ module ActiveModel
# class Person < ActiveRecord::Base
# validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
# validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
- # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %s is not allowed"
+ # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension {{value}} is not allowed"
# end
#
# Configuration options:
- # * <tt>:in</tt> - An enumerable object of items that the value shouldn't be part of
- # * <tt>:message</tt> - Specifies a custom error message (default is: "is reserved")
- # * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the attribute is +nil+ (default is: +false+)
- # * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the attribute is blank (default is: +false+)
+ # * <tt>:in</tt> - An enumerable object of items that the value shouldn't be part of.
+ # * <tt>:message</tt> - Specifies a custom error message (default is: "is reserved").
+ # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
+ # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
@@ -21,15 +21,16 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_exclusion_of(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:exclusion], :on => :save }
- configuration.update(attr_names.extract_options!)
+ configuration = attr_names.extract_options!
enum = configuration[:in] || configuration[:within]
raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?(:include?)
validates_each(attr_names, configuration) do |record, attr_name, value|
- record.errors.add(attr_name, configuration[:message] % value) if enum.include?(value)
+ if enum.include?(value)
+ record.errors.add(attr_name, :exclusion, :default => configuration[:message], :value => value)
+ end
end
end
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index 1320ef646a..8efce8ba2b 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -13,11 +13,11 @@ module ActiveModel
# A regular expression must be provided or else an exception will be raised.
#
# Configuration options:
- # * <tt>:message</tt> - A custom error message (default is: "is invalid")
- # * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the attribute is +nil+ (default is: +false+)
- # * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the attribute is blank (default is: +false+)
- # * <tt>:with</tt> - The regular expression used to validate the format with (note: must be supplied!)
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
+ # * <tt>:message</tt> - A custom error message (default is: "is invalid").
+ # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
+ # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
+ # * <tt>:with</tt> - The regular expression used to validate the format with (note: must be supplied!).
+ # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
@@ -25,13 +25,15 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_format_of(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
+ configuration = { :with => nil }
configuration.update(attr_names.extract_options!)
raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
validates_each(attr_names, configuration) do |record, attr_name, value|
- record.errors.add(attr_name, configuration[:message]) unless value.to_s =~ configuration[:with]
+ unless value.to_s =~ configuration[:with]
+ record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
+ end
end
end
end
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index f288810d90..a4bc8fe035 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -6,14 +6,14 @@ module ActiveModel
# class Person < ActiveRecord::Base
# validates_inclusion_of :gender, :in => %w( m f )
# validates_inclusion_of :age, :in => 0..99
- # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %s is not included in the list"
+ # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension {{value}} is not included in the list"
# end
#
# Configuration options:
- # * <tt>:in</tt> - An enumerable object of available items
- # * <tt>:message</tt> - Specifies a custom error message (default is: "is not included in the list")
- # * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the attribute is null (default is: +false+)
- # * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the attribute is blank (default is: +false+)
+ # * <tt>:in</tt> - An enumerable object of available items.
+ # * <tt>:message</tt> - Specifies a custom error message (default is: "is not included in the list").
+ # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
+ # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
@@ -21,15 +21,16 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_inclusion_of(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:inclusion], :on => :save }
- configuration.update(attr_names.extract_options!)
+ configuration = attr_names.extract_options!
enum = configuration[:in] || configuration[:within]
raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?(:include?)
validates_each(attr_names, configuration) do |record, attr_name, value|
- record.errors.add(attr_name, configuration[:message] % value) unless enum.include?(value)
+ unless enum.include?(value)
+ record.errors.add(attr_name, :inclusion, :default => configuration[:message], :value => value)
+ end
end
end
end
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index 673ad33974..bb9a269a02 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -2,90 +2,88 @@ module ActiveModel
module Validations
module ClassMethods
ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
-
+
# Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
#
# class Person < ActiveRecord::Base
- # validates_length_of :first_name, :maximum => 30
- # validates_length_of :last_name, :maximum => 30, :message => "less than %d if you don't mind"
+ # validates_length_of :first_name, :maximum=>30
+ # validates_length_of :last_name, :maximum=>30, :message=>"less than {{count}} if you don't mind"
# validates_length_of :fax, :in => 7..32, :allow_nil => true
# validates_length_of :phone, :in => 7..32, :allow_blank => true
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
- # validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least %d character"
- # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with %d characters... don't play me."
+ # validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least {{count}} character"
+ # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with {{count}} characters... don't play me."
+ # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least {{count}} words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
# end
#
# Configuration options:
- # * <tt>:minimum</tt> - The minimum size of the attribute
- # * <tt>:maximum</tt> - The maximum size of the attribute
- # * <tt>:is</tt> - The exact size of the attribute
- # * <tt>:within</tt> - A range specifying the minimum and maximum size of the attribute
- # * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>
+ # * <tt>:minimum</tt> - The minimum size of the attribute.
+ # * <tt>:maximum</tt> - The maximum size of the attribute.
+ # * <tt>:is</tt> - The exact size of the attribute.
+ # * <tt>:within</tt> - A range specifying the minimum and maximum size of the attribute.
+ # * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
- # * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)")
- # * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)")
- # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)")
- # * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate <tt>:too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
+ # * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is {{count}} characters)").
+ # * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is {{count}} characters)").
+ # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be {{count}} characters)").
+ # * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
+ # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
+ # count words as in above example.)
+ # Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
def validates_length_of(*attrs)
# Merge given options with defaults.
- options = {
- :too_long => ActiveRecord::Errors.default_error_messages[:too_long],
- :too_short => ActiveRecord::Errors.default_error_messages[:too_short],
- :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
- }.merge(DEFAULT_VALIDATION_OPTIONS)
+ options = { :tokenizer => lambda {|value| value.split(//)} }
options.update(attrs.extract_options!.symbolize_keys)
# Ensure that one and only one range option is specified.
range_options = ALL_RANGE_OPTIONS & options.keys
case range_options.size
- when 0
- raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
- when 1
- # Valid number of options; do nothing.
- else
- raise ArgumentError, 'Too many range options specified. Choose only one.'
+ when 0
+ raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
+ when 1
+ # Valid number of options; do nothing.
+ else
+ raise ArgumentError, 'Too many range options specified. Choose only one.'
end
# Get range option and value.
option = range_options.first
option_value = options[range_options.first]
+ key = {:is => :wrong_length, :minimum => :too_short, :maximum => :too_long}[option]
+ custom_message = options[:message] || options[key]
case option
- when :within, :in
- raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
-
- too_short = options[:too_short] % option_value.begin
- too_long = options[:too_long] % option_value.end
+ when :within, :in
+ raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
- validates_each(attrs, options) do |record, attr, value|
- value = value.split(//) if value.kind_of?(String)
- if value.nil? or value.size < option_value.begin
- record.errors.add(attr, too_short)
- elsif value.size > option_value.end
- record.errors.add(attr, too_long)
- end
+ validates_each(attrs, options) do |record, attr, value|
+ value = options[:tokenizer].call(value) if value.kind_of?(String)
+ if value.nil? or value.size < option_value.begin
+ record.errors.add(attr, :too_short, :default => custom_message || options[:too_short], :count => option_value.begin)
+ elsif value.size > option_value.end
+ record.errors.add(attr, :too_long, :default => custom_message || options[:too_long], :count => option_value.end)
end
- when :is, :minimum, :maximum
- raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
-
- # Declare different validations per option.
- validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
- message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
+ end
+ when :is, :minimum, :maximum
+ raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
- message = (options[:message] || options[message_options[option]]) % option_value
+ # Declare different validations per option.
+ validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
- validates_each(attrs, options) do |record, attr, value|
- value = value.split(//) if value.kind_of?(String)
- record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
+ validates_each(attrs, options) do |record, attr, value|
+ value = options[:tokenizer].call(value) if value.kind_of?(String)
+ unless !value.nil? and value.size.method(validity_checks[option])[option_value]
+ record.errors.add(attr, key, :default => custom_message, :count => option_value)
end
+ end
end
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 92ca5f4082..79fca2f1ea 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -5,10 +5,9 @@ module ActiveModel
:equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
:odd => 'odd?', :even => 'even?' }.freeze
-
# Validates whether the value of the specified attribute is numeric by trying to convert it to
- # a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
- # <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>integer</tt> is true).
+ # a float with Kernel.Float (if <tt>only_integer</tt> is false) or applying it to the regular expression
+ # <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>only_integer</tt> is set to true).
#
# class Person < ActiveRecord::Base
# validates_numericality_of :value, :on => :create
@@ -33,10 +32,9 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_numericality_of(*attr_names)
- configuration = { :on => :save, :only_integer => false, :allow_nil => false }
+ configuration = { :only_integer => false, :allow_nil => false }
configuration.update(attr_names.extract_options!)
-
numericality_options = ALL_NUMERICALITY_CHECKS.keys & configuration.keys
(numericality_options - [ :odd, :even ]).each do |option|
@@ -44,33 +42,41 @@ module ActiveModel
end
validates_each(attr_names,configuration) do |record, attr_name, value|
- raw_value = record.send("#{attr_name}_before_type_cast") || value
+ before_type_cast = "#{attr_name}_before_type_cast"
+
+ if record.respond_to?(before_type_cast.to_sym)
+ raw_value = record.send("#{attr_name}_before_type_cast") || value
+ else
+ raw_value = value
+ end
next if configuration[:allow_nil] and raw_value.nil?
if configuration[:only_integer]
unless raw_value.to_s =~ /\A[+-]?\d+\Z/
- record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
+ record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
next
end
raw_value = raw_value.to_i
else
- begin
- raw_value = Kernel.Float(raw_value.to_s)
+ begin
+ raw_value = Kernel.Float(raw_value)
rescue ArgumentError, TypeError
- record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
+ record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
next
end
end
numericality_options.each do |option|
case option
- when :odd, :even
- record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[option]) unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
- else
- message = configuration[:message] || ActiveRecord::Errors.default_error_messages[option]
- message = message % configuration[option] if configuration[option]
- record.errors.add(attr_name, message) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
+ when :odd, :even
+ unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
+ record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message])
+ end
+ else
+ unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
+ record.errors.add(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option])
+ end
end
end
end
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index 62e466901b..518bc8a952 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -7,29 +7,26 @@ module ActiveModel
# validates_presence_of :first_name
# end
#
- # The +first_name+ attribute must be in the object and it cannot be blank.
+ # The first_name attribute must be in the object and it cannot be blank.
#
- # If you want to validate the presence of a boolean field (where the real values are +true+ and +false+),
- # you will want to use
+ # If you want to validate the presence of a boolean field (where the real values are true and false),
+ # you will want to use <tt>validates_inclusion_of :field_name, :in => [true, false]</tt>.
#
- # validates_inclusion_of :field_name, :in => [true, false]
- #
- # This is due to the way Object#blank? handles boolean values:
- #
- # false.blank? # => true
+ # This is due to the way Object#blank? handles boolean values: <tt>false.blank? # => true</tt>.
#
# Configuration options:
- # * <tt>:message</tt> - A custom error message (default is: "can't be blank")
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
+ # * <tt>message</tt> - A custom error message (default is: "can't be blank").
+ # * <tt>on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>,
+ # <tt>:update</tt>).
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
+ # The method, proc or string should return or evaluate to a true or false value.
+ # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
+ # The method, proc or string should return or evaluate to a true or false value.
+ #
def validates_presence_of(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save }
- configuration.update(attr_names.extract_options!)
+ configuration = attr_names.extract_options!
# can't use validates_each here, because it cannot cope with nonexistent attributes,
# while errors.add_on_empty can
diff --git a/activemodel/lib/active_model/validations/uniqueness.rb b/activemodel/lib/active_model/validations/uniqueness.rb
deleted file mode 100644
index 593d2cfd54..0000000000
--- a/activemodel/lib/active_model/validations/uniqueness.rb
+++ /dev/null
@@ -1,104 +0,0 @@
-module ActiveModel
- module Validations
- module ClassMethods
- # Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
- # can be named "davidhh".
- #
- # class Person < ActiveRecord::Base
- # validates_uniqueness_of :user_name, :scope => :account_id
- # end
- #
- # It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example,
- # making sure that a teacher can only be on the schedule once per semester for a particular class.
- #
- # class TeacherSchedule < ActiveRecord::Base
- # validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
- # end
- #
- # When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified
- # attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
- #
- # Because this check is performed outside the database there is still a chance that duplicate values
- # will be inserted in two parallel transactions. To guarantee against this you should create a
- # unique index on the field. See +add_index+ for more information.
- #
- # Configuration options:
- # * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken")
- # * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
- # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
- # * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the attribute is +nil+ (default is: +false+)
- # * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the attribute is blank (default is: +false+)
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- def validates_uniqueness_of(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] }
- configuration.update(attr_names.extract_options!)
-
- validates_each(attr_names,configuration) do |record, attr_name, value|
- # The check for an existing value should be run from a class that
- # isn't abstract. This means working down from the current class
- # (self), to the first non-abstract class. Since classes don't know
- # their subclasses, we have to build the hierarchy between self and
- # the record's class.
- class_hierarchy = [record.class]
- while class_hierarchy.first != self
- class_hierarchy.insert(0, class_hierarchy.first.superclass)
- end
-
- # Now we can work our way down the tree to the first non-abstract
- # class (which has a database table to query from).
- finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }
-
- if value.nil? || (configuration[:case_sensitive] || !finder_class.columns_hash[attr_name.to_s].text?)
- condition_sql = "#{record.class.quoted_table_name}.#{attr_name} #{attribute_condition(value)}"
- condition_params = [value]
- else
- # sqlite has case sensitive SELECT query, while MySQL/Postgresql don't.
- # Hence, this is needed only for sqlite.
- condition_sql = "LOWER(#{record.class.quoted_table_name}.#{attr_name}) #{attribute_condition(value)}"
- condition_params = [value.downcase]
- end
-
- if scope = configuration[:scope]
- Array(scope).map do |scope_item|
- scope_value = record.send(scope_item)
- condition_sql << " AND #{record.class.quoted_table_name}.#{scope_item} #{attribute_condition(scope_value)}"
- condition_params << scope_value
- end
- end
-
- unless record.new_record?
- condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?"
- condition_params << record.send(:id)
- end
-
- results = finder_class.with_exclusive_scope do
- connection.select_all(
- construct_finder_sql(
- :select => attr_name,
- :from => finder_class.quoted_table_name,
- :conditions => [condition_sql, *condition_params]
- )
- )
- end
-
- unless results.length.zero?
- found = true
-
- # As MySQL/Postgres don't have case sensitive SELECT queries, we try to find duplicate
- # column in ruby when case sensitive option
- if configuration[:case_sensitive] && finder_class.columns_hash[attr_name.to_s].text?
- found = results.any? { |a| a[attr_name.to_s] == value }
- end
-
- record.errors.add(attr_name, configuration[:message]) if found
- end
- end
- end
- end
- end
-end
diff --git a/activemodel/lib/active_model/validations_repair_helper.rb b/activemodel/lib/active_model/validations_repair_helper.rb
new file mode 100644
index 0000000000..432e411308
--- /dev/null
+++ b/activemodel/lib/active_model/validations_repair_helper.rb
@@ -0,0 +1,44 @@
+module ActiveModel
+ module ValidationsRepairHelper
+ extend ActiveSupport::Concern
+
+ 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 = Toolbox.record_validations(*model_classes)
+ end
+ teardown do
+ Toolbox.reset_validations(@validation_repairs)
+ end
+ end
+ end
+
+ def repair_validations(*model_classes, &block)
+ validation_repairs = Toolbox.record_validations(*model_classes)
+ return block.call
+ ensure
+ Toolbox.reset_validations(validation_repairs)
+ end
+ end
+end
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
new file mode 100644
index 0000000000..f023109a60
--- /dev/null
+++ b/activemodel/test/cases/helper.rb
@@ -0,0 +1,20 @@
+$:.unshift(File.dirname(__FILE__) + '/../../lib')
+$:.unshift(File.dirname(__FILE__) + '/../../../activesupport/lib')
+
+require 'config'
+
+require 'active_model'
+
+# Show backtraces for deprecated behavior for quicker cleanup.
+ActiveSupport::Deprecation.debug = true
+
+require 'rubygems'
+require 'test/unit'
+gem 'mocha', '>= 0.9.5'
+require 'mocha'
+
+begin
+ require 'ruby-debug'
+ Debugger.start
+rescue LoadError
+end
diff --git a/activemodel/test/observing_test.rb b/activemodel/test/cases/observing_test.rb
index dc41c9f881..421ac4b4f8 100644
--- a/activemodel/test/observing_test.rb
+++ b/activemodel/test/cases/observing_test.rb
@@ -1,4 +1,4 @@
-require 'test_helper'
+require 'cases/helper'
class ObservedModel < ActiveModel::Base
class Observer
diff --git a/activemodel/test/state_machine/event_test.rb b/activemodel/test/cases/state_machine/event_test.rb
index 05e7c78e8a..2a0ef53a3f 100644
--- a/activemodel/test/state_machine/event_test.rb
+++ b/activemodel/test/cases/state_machine/event_test.rb
@@ -1,5 +1,4 @@
-require 'test_helper'
-require 'active_model/state_machine/event'
+require 'cases/helper'
class EventTest < ActiveModel::TestCase
def setup
diff --git a/activemodel/test/state_machine/machine_test.rb b/activemodel/test/cases/state_machine/machine_test.rb
index d23c223160..2aa954d80c 100644
--- a/activemodel/test/state_machine/machine_test.rb
+++ b/activemodel/test/cases/state_machine/machine_test.rb
@@ -1,4 +1,4 @@
-require 'test_helper'
+require 'cases/helper'
class MachineTestSubject
include ActiveModel::StateMachine
diff --git a/activemodel/test/state_machine/state_test.rb b/activemodel/test/cases/state_machine/state_test.rb
index fbf9ce7b0a..527bfd4c04 100644
--- a/activemodel/test/state_machine/state_test.rb
+++ b/activemodel/test/cases/state_machine/state_test.rb
@@ -1,4 +1,4 @@
-require 'test_helper'
+require 'cases/helper'
class StateTestSubject
include ActiveModel::StateMachine
diff --git a/activemodel/test/state_machine/state_transition_test.rb b/activemodel/test/cases/state_machine/state_transition_test.rb
index 966396fab1..17f9d88be7 100644
--- a/activemodel/test/state_machine/state_transition_test.rb
+++ b/activemodel/test/cases/state_machine/state_transition_test.rb
@@ -1,5 +1,4 @@
-require 'test_helper'
-require 'active_model/state_machine/state_transition'
+require 'cases/helper'
class StateTransitionTest < ActiveModel::TestCase
test 'should set from, to, and opts attr readers' do
diff --git a/activemodel/test/state_machine_test.rb b/activemodel/test/cases/state_machine_test.rb
index 312d8728ba..f66299741e 100644
--- a/activemodel/test/state_machine_test.rb
+++ b/activemodel/test/cases/state_machine_test.rb
@@ -1,4 +1,4 @@
-require 'test_helper'
+require 'cases/helper'
class StateMachineSubject
include ActiveModel::StateMachine
diff --git a/activemodel/test/cases/tests_database.rb b/activemodel/test/cases/tests_database.rb
new file mode 100644
index 0000000000..0f4475fa2d
--- /dev/null
+++ b/activemodel/test/cases/tests_database.rb
@@ -0,0 +1,41 @@
+require 'logger'
+
+$:.unshift(File.dirname(__FILE__) + '/../../../activerecord/lib')
+require 'active_record'
+require 'active_record/fixtures'
+
+module ActiveModel
+ module TestsDatabase
+ mattr_accessor :connected
+
+ def self.included(base)
+ unless self.connected
+ setup_connection
+ setup_schema
+ end
+
+ base.send :include, ActiveRecord::TestFixtures
+ end
+
+ def self.setup_schema
+ original, $stdout = $stdout, StringIO.new
+ load(SCHEMA_FILE)
+ ensure
+ $stdout = original
+ self.connected = true
+ end
+
+ def self.setup_connection
+ defaults = { :database => ':memory:' }
+ begin
+ adapter = defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'
+ options = defaults.merge :adapter => adapter, :timeout => 500
+ ActiveRecord::Base.establish_connection(options)
+ rescue Exception
+ $stderr.puts 'SQLite 3 unavailable; trying SQLite 2.'
+ options = defaults.merge :adapter => 'sqlite'
+ ActiveRecord::Base.establish_connection(options)
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb
new file mode 100644
index 0000000000..88e5fdb358
--- /dev/null
+++ b/activemodel/test/cases/validations/acceptance_validation_test.rb
@@ -0,0 +1,80 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/reply'
+require 'models/developer'
+require 'models/person'
+
+class AcceptanceValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_terms_of_service_agreement_no_acceptance
+ Topic.validates_acceptance_of(:terms_of_service, :on => :create)
+
+ t = Topic.create("title" => "We should not be confirmed")
+ assert t.save
+ end
+
+ def test_terms_of_service_agreement
+ Topic.validates_acceptance_of(:terms_of_service, :on => :create)
+
+ t = Topic.create("title" => "We should be confirmed","terms_of_service" => "")
+ assert !t.save
+ assert_equal ["must be accepted"], t.errors[:terms_of_service]
+
+ t.terms_of_service = "1"
+ assert t.save
+ end
+
+ def test_eula
+ Topic.validates_acceptance_of(:eula, :message => "must be abided", :on => :create)
+
+ t = Topic.create("title" => "We should be confirmed","eula" => "")
+ assert !t.save
+ assert_equal ["must be abided"], t.errors[:eula]
+
+ t.eula = "1"
+ assert t.save
+ end
+
+ def test_terms_of_service_agreement_with_accept_value
+ Topic.validates_acceptance_of(:terms_of_service, :on => :create, :accept => "I agree.")
+
+ t = Topic.create("title" => "We should be confirmed", "terms_of_service" => "")
+ assert !t.save
+ assert_equal ["must be accepted"], t.errors[:terms_of_service]
+
+ t.terms_of_service = "I agree."
+ assert t.save
+ end
+
+ def test_validates_acceptance_of_with_custom_error_using_quotes
+ 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[:salary].last
+ end
+ end
+
+ def test_validates_acceptance_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_acceptance_of :karma
+
+ p = Person.new
+ p.karma = ""
+
+ assert p.invalid?
+ assert_equal ["must be accepted"], p.errors[:karma]
+
+ p.karma = "1"
+ assert p.valid?
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb
new file mode 100644
index 0000000000..4c716d5d48
--- /dev/null
+++ b/activemodel/test/cases/validations/conditional_validation_test.rb
@@ -0,0 +1,140 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+
+class ConditionalValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_if_validation_using_method_true
+ # When the method returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => :condition_is_true )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ def test_unless_validation_using_method_true
+ # When the method returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => :condition_is_true )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert !t.errors[:title].any?
+ end
+
+ def test_if_validation_using_method_false
+ # When the method returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => :condition_is_true_but_its_not )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert t.errors[:title].empty?
+ end
+
+ def test_unless_validation_using_method_false
+ # When the method returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => :condition_is_true_but_its_not )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ def test_if_validation_using_string_true
+ # When the evaluated string returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => "a = 1; a == 1" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ def test_unless_validation_using_string_true
+ # When the evaluated string returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => "a = 1; a == 1" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert t.errors[:title].empty?
+ end
+
+ def test_if_validation_using_string_false
+ # When the evaluated string returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => "false")
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert t.errors[:title].empty?
+ end
+
+ def test_unless_validation_using_string_false
+ # When the evaluated string returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => "false")
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ def test_if_validation_using_block_true
+ # When the block returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
+ :if => Proc.new { |r| r.content.size > 4 } )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ def test_unless_validation_using_block_true
+ # When the block returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
+ :unless => Proc.new { |r| r.content.size > 4 } )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert t.errors[:title].empty?
+ end
+
+ def test_if_validation_using_block_false
+ # When the block returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
+ :if => Proc.new { |r| r.title != "uhohuhoh"} )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert t.errors[:title].empty?
+ end
+
+ def test_unless_validation_using_block_false
+ # When the block returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
+ :unless => Proc.new { |r| r.title != "uhohuhoh"} )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ # previous implementation of validates_presence_of eval'd the
+ # string with the wrong binding, this regression test is to
+ # ensure that it works correctly
+ def test_validation_with_if_as_string
+ Topic.validates_presence_of(:title)
+ Topic.validates_presence_of(:author_name, :if => "title.to_s.match('important')")
+
+ t = Topic.new
+ assert t.invalid?, "A topic without a title should not be valid"
+ assert t.errors[:author_name].empty?, "A topic without an 'important' title should not require an author"
+
+ t.title = "Just a title"
+ assert t.valid?, "A topic with a basic title should be valid"
+
+ t.title = "A very important title"
+ assert !t.valid?, "A topic with an important title, but without an author, should not be valid"
+ assert t.errors[:author_name].any?, "A topic with an 'important' title should require an author"
+
+ t.author_name = "Hubert J. Farnsworth"
+ assert t.valid?, "A topic with an important title and author should be valid"
+ end
+end
diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb
new file mode 100644
index 0000000000..1d6f2a6ec5
--- /dev/null
+++ b/activemodel/test/cases/validations/confirmation_validation_test.rb
@@ -0,0 +1,68 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/developer'
+require 'models/person'
+
+class ConfirmationValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_no_title_confirmation
+ Topic.validates_confirmation_of(:title)
+
+ t = Topic.new(:author_name => "Plutarch")
+ assert t.valid?
+
+ t.title_confirmation = "Parallel Lives"
+ assert !t.valid?
+
+ t.title_confirmation = nil
+ t.title = "Parallel Lives"
+ assert t.valid?
+
+ t.title_confirmation = "Parallel Lives"
+ assert t.valid?
+ end
+
+ def test_title_confirmation
+ Topic.validates_confirmation_of(:title)
+
+ t = Topic.create("title" => "We should be confirmed","title_confirmation" => "")
+ assert !t.save
+
+ t.title_confirmation = "We should be confirmed"
+ assert t.save
+ end
+
+ def test_validates_confirmation_of_with_custom_error_using_quotes
+ 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[:name]
+ end
+ end
+
+ def test_validates_confirmation_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_confirmation_of :karma
+
+ p = Person.new
+ p.karma_confirmation = "None"
+ assert p.invalid?
+
+ assert_equal ["doesn't match confirmation"], p.errors[:karma]
+
+ p.karma = "None"
+ assert p.valid?
+ end
+ end
+
+end
diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb
new file mode 100644
index 0000000000..584f009e84
--- /dev/null
+++ b/activemodel/test/cases/validations/exclusion_validation_test.rb
@@ -0,0 +1,46 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/person'
+
+class ExclusionValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_validates_exclusion_of
+ Topic.validates_exclusion_of( :title, :in => %w( abe monkey ) )
+
+ assert Topic.create("title" => "something", "content" => "abc").valid?
+ assert !Topic.create("title" => "monkey", "content" => "abc").valid?
+ end
+
+ def test_validates_exclusion_of_with_formatted_message
+ Topic.validates_exclusion_of( :title, :in => %w( abe monkey ), :message => "option {{value}} is restricted" )
+
+ assert Topic.create("title" => "something", "content" => "abc")
+
+ t = Topic.create("title" => "monkey")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["option monkey is restricted"], t.errors[:title]
+ end
+
+ def test_validates_exclusion_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_exclusion_of :karma, :in => %w( abe monkey )
+
+ p = Person.new
+ p.karma = "abe"
+ assert p.invalid?
+
+ assert_equal ["is reserved"], p.errors[:karma]
+
+ p.karma = "Lifo"
+ assert p.valid?
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
new file mode 100644
index 0000000000..2c06a9dd02
--- /dev/null
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -0,0 +1,98 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/developer'
+require 'models/person'
+
+class PresenceValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_validate_format
+ Topic.validates_format_of(:title, :content, :with => /^Validation\smacros \w+!$/, :message => "is bad data")
+
+ t = Topic.create("title" => "i'm incorrect", "content" => "Validation macros rule!")
+ assert !t.valid?, "Shouldn't be valid"
+ assert !t.save, "Shouldn't save because it's invalid"
+ assert_equal ["is bad data"], t.errors[:title]
+ assert t.errors[:content].empty?
+
+ t.title = "Validation macros rule!"
+
+ assert t.save
+ assert t.errors[:title].empty?
+
+ assert_raise(ArgumentError) { Topic.validates_format_of(:title, :content) }
+ end
+
+ def test_validate_format_with_allow_blank
+ Topic.validates_format_of(:title, :with => /^Validation\smacros \w+!$/, :allow_blank=>true)
+ assert !Topic.create("title" => "Shouldn't be valid").valid?
+ assert Topic.create("title" => "").valid?
+ assert Topic.create("title" => nil).valid?
+ assert Topic.create("title" => "Validation macros rule!").valid?
+ end
+
+ # testing ticket #3142
+ def test_validate_format_numeric
+ Topic.validates_format_of(:title, :content, :with => /^[1-9][0-9]*$/, :message => "is bad data")
+
+ t = Topic.create("title" => "72x", "content" => "6789")
+ assert !t.valid?, "Shouldn't be valid"
+ assert !t.save, "Shouldn't save because it's invalid"
+ assert_equal ["is bad data"], t.errors[:title]
+ assert t.errors[:content].empty?
+
+ t.title = "-11"
+ assert !t.valid?, "Shouldn't be valid"
+
+ t.title = "03"
+ assert !t.valid?, "Shouldn't be valid"
+
+ t.title = "z44"
+ assert !t.valid?, "Shouldn't be valid"
+
+ t.title = "5v7"
+ assert !t.valid?, "Shouldn't be valid"
+
+ t.title = "1"
+
+ assert t.save
+ assert t.errors[:title].empty?
+ end
+
+ def test_validate_format_with_formatted_message
+ Topic.validates_format_of(:title, :with => /^Valid Title$/, :message => "can't be {{value}}")
+ t = Topic.create(:title => 'Invalid title')
+ assert_equal ["can't be Invalid title"], t.errors[:title]
+ end
+
+ def test_validates_format_of_with_custom_error_using_quotes
+ 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[:name]
+ end
+ end
+
+ def test_validates_format_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_format_of :karma, :with => /\A\d+\Z/
+
+ p = Person.new
+ p.karma = "Pixies"
+ assert p.invalid?
+
+ assert_equal ["is invalid"], p.errors[:karma]
+
+ p.karma = "1234"
+ assert p.valid?
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
new file mode 100644
index 0000000000..37bba5e95e
--- /dev/null
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -0,0 +1,182 @@
+require "cases/helper"
+require 'cases/tests_database'
+
+require 'models/person'
+
+class I18nGenerateMessageValidationTest < Test::Unit::TestCase
+ def setup
+ reset_callbacks Person
+ @person = Person.new
+
+ @old_load_path, @old_backend = I18n.load_path, I18n.backend
+ I18n.load_path.clear
+ I18n.backend = I18n::Backend::Simple.new
+
+ I18n.backend.store_translations :'en', {
+ :activemodel => {
+ :errors => {
+ :messages => {
+ :inclusion => "is not included in the list",
+ :exclusion => "is reserved",
+ :invalid => "is invalid",
+ :confirmation => "doesn't match confirmation",
+ :accepted => "must be accepted",
+ :empty => "can't be empty",
+ :blank => "can't be blank",
+ :too_long => "is too long (maximum is {{count}} characters)",
+ :too_short => "is too short (minimum is {{count}} characters)",
+ :wrong_length => "is the wrong length (should be {{count}} characters)",
+ :not_a_number => "is not a number",
+ :greater_than => "must be greater than {{count}}",
+ :greater_than_or_equal_to => "must be greater than or equal to {{count}}",
+ :equal_to => "must be equal to {{count}}",
+ :less_than => "must be less than {{count}}",
+ :less_than_or_equal_to => "must be less than or equal to {{count}}",
+ :odd => "must be odd",
+ :even => "must be even"
+ }
+ }
+ }
+ }
+ end
+
+ def teardown
+ I18n.load_path.replace @old_load_path
+ I18n.backend = @old_backend
+ end
+
+ def reset_callbacks(*models)
+ models.each do |model|
+ model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ end
+ end
+
+ # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
+ def test_generate_message_inclusion_with_default_message
+ assert_equal 'is not included in the list', @person.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_inclusion_with_custom_message
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :inclusion, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value)
+ def test_generate_message_exclusion_with_default_message
+ assert_equal 'is reserved', @person.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_exclusion_with_custom_message
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
+ # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
+ def test_generate_message_invalid_with_default_message
+ assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_invalid_with_custom_message
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :invalid, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message])
+ def test_generate_message_confirmation_with_default_message
+ assert_equal "doesn't match confirmation", @person.errors.generate_message(:title, :confirmation, :default => nil)
+ end
+
+ def test_generate_message_confirmation_with_custom_message
+ assert_equal 'custom message', @person.errors.generate_message(:title, :confirmation, :default => 'custom message')
+ end
+
+ # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message])
+ def test_generate_message_accepted_with_default_message
+ assert_equal "must be accepted", @person.errors.generate_message(:title, :accepted, :default => nil)
+ end
+
+ def test_generate_message_accepted_with_custom_message
+ assert_equal 'custom message', @person.errors.generate_message(:title, :accepted, :default => 'custom message')
+ end
+
+ # add_on_empty: generate_message(attr, :empty, :default => custom_message)
+ def test_generate_message_empty_with_default_message
+ assert_equal "can't be empty", @person.errors.generate_message(:title, :empty, :default => nil)
+ end
+
+ def test_generate_message_empty_with_custom_message
+ assert_equal 'custom message', @person.errors.generate_message(:title, :empty, :default => 'custom message')
+ end
+
+ # add_on_blank: generate_message(attr, :blank, :default => custom_message)
+ def test_generate_message_blank_with_default_message
+ assert_equal "can't be blank", @person.errors.generate_message(:title, :blank, :default => nil)
+ end
+
+ def test_generate_message_blank_with_custom_message
+ assert_equal 'custom message', @person.errors.generate_message(:title, :blank, :default => 'custom message')
+ end
+
+ # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end)
+ def test_generate_message_too_long_with_default_message
+ assert_equal "is too long (maximum is 10 characters)", @person.errors.generate_message(:title, :too_long, :default => nil, :count => 10)
+ end
+
+ def test_generate_message_too_long_with_custom_message
+ assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_long, :default => 'custom message {{count}}', :count => 10)
+ end
+
+ # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
+ def test_generate_message_too_short_with_default_message
+ assert_equal "is too short (minimum is 10 characters)", @person.errors.generate_message(:title, :too_short, :default => nil, :count => 10)
+ end
+
+ def test_generate_message_too_short_with_custom_message
+ assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_short, :default => 'custom message {{count}}', :count => 10)
+ end
+
+ # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value)
+ def test_generate_message_wrong_length_with_default_message
+ assert_equal "is the wrong length (should be 10 characters)", @person.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10)
+ end
+
+ def test_generate_message_wrong_length_with_custom_message
+ assert_equal 'custom message 10', @person.errors.generate_message(:title, :wrong_length, :default => 'custom message {{count}}', :count => 10)
+ end
+
+ # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
+ def test_generate_message_not_a_number_with_default_message
+ assert_equal "is not a number", @person.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_not_a_number_with_custom_message
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :not_a_number, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message])
+ def test_generate_message_greater_than_with_default_message
+ assert_equal "must be greater than 10", @person.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_greater_than_or_equal_to_with_default_message
+ assert_equal "must be greater than or equal to 10", @person.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_equal_to_with_default_message
+ assert_equal "must be equal to 10", @person.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_less_than_with_default_message
+ assert_equal "must be less than 10", @person.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_less_than_or_equal_to_with_default_message
+ assert_equal "must be less than or equal to 10", @person.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_odd_with_default_message
+ assert_equal "must be odd", @person.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_even_with_default_message
+ assert_equal "must be even", @person.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10)
+ end
+end
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
new file mode 100644
index 0000000000..cc68d847a2
--- /dev/null
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -0,0 +1,579 @@
+require "cases/helper"
+require 'cases/tests_database'
+
+require 'models/person'
+
+class I18nValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+
+ def setup
+ reset_callbacks Person
+
+ @person = Person.new
+
+ @old_load_path, @old_backend = I18n.load_path, I18n.backend
+ I18n.load_path.clear
+ I18n.backend = I18n::Backend::Simple.new
+ I18n.backend.store_translations('en', :activemodel => {:errors => {:messages => {:custom => nil}}})
+ end
+
+ def teardown
+ reset_callbacks Person
+ I18n.load_path.replace @old_load_path
+ I18n.backend = @old_backend
+ end
+
+ def reset_callbacks(*models)
+ models.each do |model|
+ model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ end
+ end
+
+ def test_percent_s_interpolation_syntax_in_error_messages_was_deprecated
+ assert_not_deprecated do
+ default = "%s interpolation syntax was deprecated"
+ assert_equal default, I18n.t(:does_not_exist, :default => default, :value => 'this')
+ end
+ end
+
+ def test_percent_d_interpolation_syntax_in_error_messages_was_deprecated
+ assert_not_deprecated do
+ default = "%d interpolation syntaxes are deprecated"
+ assert_equal default, I18n.t(:does_not_exist, :default => default, :count => 2)
+ end
+ end
+
+ def test_errors_add_on_empty_generates_message
+ @person.errors.expects(:generate_message).with(:title, :empty, {:default => nil})
+ @person.errors.add_on_empty :title
+ end
+
+ def test_errors_add_on_empty_generates_message_with_custom_default_message
+ @person.errors.expects(:generate_message).with(:title, :empty, {:default => 'custom'})
+ @person.errors.add_on_empty :title, 'custom'
+ end
+
+ def test_errors_add_on_blank_generates_message
+ @person.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
+ @person.errors.add_on_blank :title
+ end
+
+ def test_errors_add_on_blank_generates_message_with_custom_default_message
+ @person.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
+ @person.errors.add_on_blank :title, 'custom'
+ end
+
+ # ActiveRecord::Validations
+ # validates_confirmation_of w/ mocha
+ def test_validates_confirmation_of_generates_message
+ Person.validates_confirmation_of :title
+ @person.title_confirmation = 'foo'
+ @person.errors.expects(:generate_message).with(:title, :confirmation, {:default => nil})
+ @person.valid?
+ end
+
+ def test_validates_confirmation_of_generates_message_with_custom_default_message
+ Person.validates_confirmation_of :title, :message => 'custom'
+ @person.title_confirmation = 'foo'
+ @person.errors.expects(:generate_message).with(:title, :confirmation, {:default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_acceptance_of w/ mocha
+
+ def test_validates_acceptance_of_generates_message
+ Person.validates_acceptance_of :title, :allow_nil => false
+ @person.errors.expects(:generate_message).with(:title, :accepted, {:default => nil})
+ @person.valid?
+ end
+
+ def test_validates_acceptance_of_generates_message_with_custom_default_message
+ Person.validates_acceptance_of :title, :message => 'custom', :allow_nil => false
+ @person.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_presence_of w/ mocha
+
+ def test_validates_presence_of_generates_message
+ Person.validates_presence_of :title
+ @person.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
+ @person.valid?
+ end
+
+ def test_validates_presence_of_generates_message_with_custom_default_message
+ Person.validates_presence_of :title, :message => 'custom'
+ @person.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
+ @person.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_short
+ Person.validates_length_of :title, :within => 3..5
+ @person.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message
+ Person.validates_length_of :title, :within => 3..5, :too_short => 'custom'
+ @person.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'})
+ @person.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_long
+ Person.validates_length_of :title, :within => 3..5
+ @person.title = 'this title is too long'
+ @person.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message
+ Person.validates_length_of :title, :within => 3..5, :too_long => 'custom'
+ @person.title = 'this title is too long'
+ @person.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_length_of :within w/ mocha
+
+ def test_validates_length_of_within_generates_message_with_title_too_short
+ Person.validates_length_of :title, :within => 3..5
+ @person.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message
+ Person.validates_length_of :title, :within => 3..5, :too_short => 'custom'
+ @person.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'})
+ @person.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_long
+ Person.validates_length_of :title, :within => 3..5
+ @person.title = 'this title is too long'
+ @person.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message
+ Person.validates_length_of :title, :within => 3..5, :too_long => 'custom'
+ @person.title = 'this title is too long'
+ @person.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_length_of :is w/ mocha
+
+ def test_validates_length_of_is_generates_message
+ Person.validates_length_of :title, :is => 5
+ @person.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_length_of_is_generates_message_with_custom_default_message
+ Person.validates_length_of :title, :is => 5, :message => 'custom'
+ @person.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_format_of w/ mocha
+
+ def test_validates_format_of_generates_message
+ Person.validates_format_of :title, :with => /^[1-9][0-9]*$/
+ @person.title = '72x'
+ @person.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_format_of_generates_message_with_custom_default_message
+ Person.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom'
+ @person.title = '72x'
+ @person.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_inclusion_of w/ mocha
+
+ def test_validates_inclusion_of_generates_message
+ Person.validates_inclusion_of :title, :in => %w(a b c)
+ @person.title = 'z'
+ @person.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_inclusion_of_generates_message_with_custom_default_message
+ Person.validates_inclusion_of :title, :in => %w(a b c), :message => 'custom'
+ @person.title = 'z'
+ @person.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_exclusion_of w/ mocha
+
+ def test_validates_exclusion_of_generates_message
+ Person.validates_exclusion_of :title, :in => %w(a b c)
+ @person.title = 'a'
+ @person.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_exclusion_of_generates_message_with_custom_default_message
+ Person.validates_exclusion_of :title, :in => %w(a b c), :message => 'custom'
+ @person.title = 'a'
+ @person.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_numericality_of without :only_integer w/ mocha
+
+ def test_validates_numericality_of_generates_message
+ Person.validates_numericality_of :title
+ @person.title = 'a'
+ @person.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_numericality_of_generates_message_with_custom_default_message
+ Person.validates_numericality_of :title, :message => 'custom'
+ @person.title = 'a'
+ @person.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_numericality_of with :only_integer w/ mocha
+
+ def test_validates_numericality_of_only_integer_generates_message
+ Person.validates_numericality_of :title, :only_integer => true
+ @person.title = 'a'
+ @person.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_numericality_of_only_integer_generates_message_with_custom_default_message
+ Person.validates_numericality_of :title, :only_integer => true, :message => 'custom'
+ @person.title = 'a'
+ @person.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_numericality_of :odd w/ mocha
+
+ def test_validates_numericality_of_odd_generates_message
+ Person.validates_numericality_of :title, :only_integer => true, :odd => true
+ @person.title = 0
+ @person.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_numericality_of_odd_generates_message_with_custom_default_message
+ Person.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom'
+ @person.title = 0
+ @person.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_numericality_of :less_than w/ mocha
+
+ def test_validates_numericality_of_less_than_generates_message
+ Person.validates_numericality_of :title, :only_integer => true, :less_than => 0
+ @person.title = 1
+ @person.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_numericality_of_odd_generates_message_with_custom_default_message
+ Person.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom'
+ @person.title = 1
+ @person.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_confirmation_of w/o mocha
+
+ def test_validates_confirmation_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:confirmation => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:confirmation => 'global message'}}}
+
+ Person.validates_confirmation_of :title
+ @person.title_confirmation = 'foo'
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_confirmation_of_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:confirmation => 'global message'}}}
+
+ Person.validates_confirmation_of :title
+ @person.title_confirmation = 'foo'
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_acceptance_of w/o mocha
+
+ def test_validates_acceptance_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:accepted => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:accepted => 'global message'}}}
+
+ Person.validates_acceptance_of :title, :allow_nil => false
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_acceptance_of_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:accepted => 'global message'}}}
+
+ Person.validates_acceptance_of :title, :allow_nil => false
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_presence_of w/o mocha
+
+ def test_validates_presence_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:blank => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:blank => 'global message'}}}
+
+ Person.validates_presence_of :title
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_presence_of_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:blank => 'global message'}}}
+
+ Person.validates_presence_of :title
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_length_of :within w/o mocha
+
+ def test_validates_length_of_within_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:too_short => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:too_short => 'global message'}}}
+
+ Person.validates_length_of :title, :within => 3..5
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_length_of_within_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:too_short => 'global message'}}}
+
+ Person.validates_length_of :title, :within => 3..5
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_length_of :is w/o mocha
+
+ def test_validates_length_of_is_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:wrong_length => 'global message'}}}
+
+ Person.validates_length_of :title, :is => 5
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_length_of_is_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:wrong_length => 'global message'}}}
+
+ Person.validates_length_of :title, :is => 5
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ def test_validates_length_of_is_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:wrong_length => 'global message'}}}
+
+ Person.validates_length_of :title, :is => 5
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_length_of_is_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:wrong_length => 'global message'}}}
+
+ Person.validates_length_of :title, :is => 5
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+
+ # validates_format_of w/o mocha
+
+ def test_validates_format_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:invalid => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:invalid => 'global message'}}}
+
+ Person.validates_format_of :title, :with => /^[1-9][0-9]*$/
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_format_of_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:invalid => 'global message'}}}
+
+ Person.validates_format_of :title, :with => /^[1-9][0-9]*$/
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_inclusion_of w/o mocha
+
+ def test_validates_inclusion_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:inclusion => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:inclusion => 'global message'}}}
+
+ Person.validates_inclusion_of :title, :in => %w(a b c)
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_inclusion_of_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:inclusion => 'global message'}}}
+
+ Person.validates_inclusion_of :title, :in => %w(a b c)
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_exclusion_of w/o mocha
+
+ def test_validates_exclusion_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:exclusion => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:exclusion => 'global message'}}}
+
+ Person.validates_exclusion_of :title, :in => %w(a b c)
+ @person.title = 'a'
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_exclusion_of_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:exclusion => 'global message'}}}
+
+ Person.validates_exclusion_of :title, :in => %w(a b c)
+ @person.title = 'a'
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_numericality_of without :only_integer w/o mocha
+
+ def test_validates_numericality_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}}
+
+ Person.validates_numericality_of :title
+ @person.title = 'a'
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_numericality_of_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}}
+
+ Person.validates_numericality_of :title, :only_integer => true
+ @person.title = 'a'
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_numericality_of with :only_integer w/o mocha
+
+ def test_validates_numericality_of_only_integer_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}}
+
+ Person.validates_numericality_of :title, :only_integer => true
+ @person.title = 'a'
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_numericality_of_only_integer_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}}
+
+ Person.validates_numericality_of :title, :only_integer => true
+ @person.title = 'a'
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_numericality_of :odd w/o mocha
+
+ def test_validates_numericality_of_odd_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:odd => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:odd => 'global message'}}}
+
+ Person.validates_numericality_of :title, :only_integer => true, :odd => true
+ @person.title = 0
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_numericality_of_odd_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:odd => 'global message'}}}
+
+ Person.validates_numericality_of :title, :only_integer => true, :odd => true
+ @person.title = 0
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_numericality_of :less_than w/o mocha
+
+ def test_validates_numericality_of_less_than_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:less_than => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:less_than => 'global message'}}}
+
+ Person.validates_numericality_of :title, :only_integer => true, :less_than => 0
+ @person.title = 1
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_numericality_of_less_than_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:less_than => 'global message'}}}
+
+ Person.validates_numericality_of :title, :only_integer => true, :less_than => 0
+ @person.title = 1
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ def test_validations_with_message_symbol_must_translate
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:custom_error => "I am a custom error"}}}
+ Person.validates_presence_of :title, :message => :custom_error
+ @person.title = nil
+ @person.valid?
+ assert_equal ["I am a custom error"], @person.errors[:title]
+ end
+
+ def test_validates_with_message_symbol_must_translate_per_attribute
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}}
+ Person.validates_presence_of :title, :message => :custom_error
+ @person.title = nil
+ @person.valid?
+ assert_equal ["I am a custom error"], @person.errors[:title]
+ end
+
+ def test_validates_with_message_symbol_must_translate_per_model
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:custom_error => "I am a custom error"}}}}
+ Person.validates_presence_of :title, :message => :custom_error
+ @person.title = nil
+ @person.valid?
+ assert_equal ["I am a custom error"], @person.errors[:title]
+ end
+
+ def test_validates_with_message_string
+ Person.validates_presence_of :title, :message => "I am a custom error"
+ @person.title = nil
+ @person.valid?
+ assert_equal ["I am a custom error"], @person.errors[:title]
+ end
+
+end \ No newline at end of file
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
new file mode 100644
index 0000000000..bc1b0365d2
--- /dev/null
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -0,0 +1,80 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/developer'
+require 'models/person'
+
+class InclusionValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_validates_inclusion_of
+ Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ) )
+
+ assert !Topic.create("title" => "a!", "content" => "abc").valid?
+ assert !Topic.create("title" => "a b", "content" => "abc").valid?
+ assert !Topic.create("title" => nil, "content" => "def").valid?
+
+ t = Topic.create("title" => "a", "content" => "I know you are but what am I?")
+ assert t.valid?
+ t.title = "uhoh"
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is not included in the list"], t.errors[:title]
+
+ assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => nil ) }
+ assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => 0) }
+
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => "hi!" ) }
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => {} ) }
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => [] ) }
+ end
+
+ def test_validates_inclusion_of_with_allow_nil
+ Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :allow_nil=>true )
+
+ assert !Topic.create("title" => "a!", "content" => "abc").valid?
+ assert !Topic.create("title" => "", "content" => "abc").valid?
+ assert Topic.create("title" => nil, "content" => "abc").valid?
+ end
+
+ def test_validates_inclusion_of_with_formatted_message
+ Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :message => "option {{value}} is not in the list" )
+
+ assert Topic.create("title" => "a", "content" => "abc").valid?
+
+ t = Topic.create("title" => "uhoh", "content" => "abc")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["option uhoh is not in the list"], t.errors[:title]
+ end
+
+ def test_validates_inclusion_of_with_custom_error_using_quotes
+ 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[:salary].last
+ end
+ end
+
+ def test_validates_inclusion_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_inclusion_of :karma, :in => %w( abe monkey )
+
+ p = Person.new
+ p.karma = "Lifo"
+ assert p.invalid?
+
+ assert_equal ["is not included in the list"], p.errors[:karma]
+
+ p.karma = "monkey"
+ assert p.valid?
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
new file mode 100644
index 0000000000..4a2f72feab
--- /dev/null
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -0,0 +1,449 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/developer'
+require 'models/person'
+
+class LengthValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_validates_length_of_with_allow_nil
+ Topic.validates_length_of( :title, :is => 5, :allow_nil=>true )
+
+ assert !Topic.create("title" => "ab").valid?
+ assert !Topic.create("title" => "").valid?
+ assert Topic.create("title" => nil).valid?
+ assert Topic.create("title" => "abcde").valid?
+ end
+
+ def test_validates_length_of_with_allow_blank
+ Topic.validates_length_of( :title, :is => 5, :allow_blank=>true )
+
+ assert !Topic.create("title" => "ab").valid?
+ assert Topic.create("title" => "").valid?
+ assert Topic.create("title" => nil).valid?
+ assert Topic.create("title" => "abcde").valid?
+ end
+
+ def test_validates_length_of_using_minimum
+ Topic.validates_length_of :title, :minimum => 5
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "not"
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is too short (minimum is 5 characters)"], t.errors[:title]
+
+ t.title = ""
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is too short (minimum is 5 characters)"], t.errors[:title]
+
+ t.title = nil
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"]
+ end
+
+ def test_optionally_validates_length_of_using_minimum
+ Topic.validates_length_of :title, :minimum => 5, :allow_nil => true
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = nil
+ assert t.valid?
+ end
+
+ def test_validates_length_of_using_maximum
+ Topic.validates_length_of :title, :maximum => 5
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "notvalid"
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is too long (maximum is 5 characters)"], t.errors[:title]
+
+ t.title = ""
+ assert t.valid?
+
+ t.title = nil
+ assert !t.valid?
+ end
+
+ def test_optionally_validates_length_of_using_maximum
+ Topic.validates_length_of :title, :maximum => 5, :allow_nil => true
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = nil
+ assert t.valid?
+ end
+
+ def test_validates_length_of_using_within
+ Topic.validates_length_of(:title, :content, :within => 3..5)
+
+ t = Topic.new("title" => "a!", "content" => "I'm ooooooooh so very long")
+ assert !t.valid?
+ assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title]
+ assert_equal ["is too long (maximum is 5 characters)"], t.errors[:content]
+
+ t.title = nil
+ t.content = nil
+ assert !t.valid?
+ assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title]
+ assert_equal ["is too short (minimum is 3 characters)"], t.errors[:content]
+
+ t.title = "abe"
+ t.content = "mad"
+ assert t.valid?
+ end
+
+ def test_optionally_validates_length_of_using_within
+ Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true
+
+ t = Topic.create('title' => 'abc', 'content' => 'abcd')
+ assert t.valid?
+
+ t.title = nil
+ assert t.valid?
+ end
+
+ def test_optionally_validates_length_of_using_within_on_create
+ Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "my string is too long: {{count}}"
+
+ t = Topic.create("title" => "thisisnotvalid", "content" => "whatever")
+ assert !t.save
+ assert t.errors[:title].any?
+ assert_equal ["my string is too long: 10"], t.errors[:title]
+
+ t.title = "butthisis"
+ assert t.save
+
+ t.title = "few"
+ assert t.save
+
+ t.content = "andthisislong"
+ assert t.save
+
+ t.content = t.title = "iamfine"
+ assert t.save
+ end
+
+ def test_optionally_validates_length_of_using_within_on_update
+ Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "my string is too short: {{count}}"
+
+ t = Topic.create("title" => "vali", "content" => "whatever")
+ assert !t.save
+ assert t.errors[:title].any?
+
+ t.title = "not"
+ assert !t.save
+ assert t.errors[:title].any?
+ assert_equal ["my string is too short: 5"], t.errors[:title]
+
+ t.title = "valid"
+ t.content = "andthisistoolong"
+ assert !t.save
+ assert t.errors[:content].any?
+
+ t.content = "iamfine"
+ assert t.save
+ end
+
+ def test_validates_length_of_using_is
+ Topic.validates_length_of :title, :is => 5
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "notvalid"
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is the wrong length (should be 5 characters)"], t.errors[:title]
+
+ t.title = ""
+ assert !t.valid?
+
+ t.title = nil
+ assert !t.valid?
+ end
+
+ def test_optionally_validates_length_of_using_is
+ Topic.validates_length_of :title, :is => 5, :allow_nil => true
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = nil
+ assert t.valid?
+ end
+
+ def test_validates_length_of_using_bignum
+ bigmin = 2 ** 30
+ bigmax = 2 ** 32
+ bigrange = bigmin...bigmax
+ assert_nothing_raised do
+ Topic.validates_length_of :title, :is => bigmin + 5
+ Topic.validates_length_of :title, :within => bigrange
+ Topic.validates_length_of :title, :in => bigrange
+ Topic.validates_length_of :title, :minimum => bigmin
+ Topic.validates_length_of :title, :maximum => bigmax
+ end
+ end
+
+ def test_validates_length_of_nasty_params
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>6, :maximum=>9) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :maximum=>9) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :minimum=>9) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :is=>9) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>"a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum=>"a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>"a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is=>"a") }
+ end
+
+ def test_validates_length_of_custom_errors_for_minimum_with_message
+ Topic.validates_length_of( :title, :minimum=>5, :message=>"boo {{count}}" )
+ t = Topic.create("title" => "uhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["boo 5"], t.errors[:title]
+ end
+
+ def test_validates_length_of_custom_errors_for_minimum_with_too_short
+ Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo {{count}}" )
+ t = Topic.create("title" => "uhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors[:title]
+ end
+
+ def test_validates_length_of_custom_errors_for_maximum_with_message
+ Topic.validates_length_of( :title, :maximum=>5, :message=>"boo {{count}}" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["boo 5"], t.errors[:title]
+ end
+
+ def test_validates_length_of_custom_errors_for_in
+ Topic.validates_length_of(:title, :in => 10..20, :message => "hoo {{count}}")
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 10"], t.errors["title"]
+
+ t = Topic.create("title" => "uhohuhohuhohuhohuhohuhohuhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 20"], t.errors["title"]
+ end
+
+ def test_validates_length_of_custom_errors_for_maximum_with_too_long
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ def test_validates_length_of_custom_errors_for_is_with_message
+ Topic.validates_length_of( :title, :is=>5, :message=>"boo {{count}}" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["boo 5"], t.errors["title"]
+ end
+
+ def test_validates_length_of_custom_errors_for_is_with_wrong_length
+ Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo {{count}}" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ def test_validates_length_of_using_minimum_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :minimum => 5
+
+ t = Topic.create("title" => "一二三四五", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "一二三四"
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"]
+ end
+ end
+
+ def test_validates_length_of_using_maximum_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :maximum => 5
+
+ t = Topic.create("title" => "一二三四五", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "一二34五六"
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is too long (maximum is 5 characters)"], t.errors["title"]
+ end
+ end
+
+ def test_validates_length_of_using_within_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of(:title, :content, :within => 3..5)
+
+ t = Topic.new("title" => "一二", "content" => "12三四五六七")
+ assert !t.valid?
+ assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title]
+ assert_equal ["is too long (maximum is 5 characters)"], t.errors[:content]
+ t.title = "一二三"
+ t.content = "12三"
+ assert t.valid?
+ end
+ end
+
+ def test_optionally_validates_length_of_using_within_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :within => 3..5, :allow_nil => true
+
+ t = Topic.create(:title => "一二三四五")
+ assert t.valid?, t.errors.inspect
+
+ t = Topic.create(:title => "一二三")
+ assert t.valid?, t.errors.inspect
+
+ t.title = nil
+ assert t.valid?, t.errors.inspect
+ end
+ end
+
+ def test_optionally_validates_length_of_using_within_on_create_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :within => 5..10, :on => :create, :too_long => "長すぎます: {{count}}"
+
+ t = Topic.create("title" => "一二三四五六七八九十A", "content" => "whatever")
+ assert !t.save
+ assert t.errors[:title].any?
+ assert_equal "長すぎます: 10", t.errors[:title].first
+
+ t.title = "一二三四五六七八九"
+ assert t.save
+
+ t.title = "一二3"
+ assert t.save
+
+ t.content = "一二三四五六七八九十"
+ assert t.save
+
+ t.content = t.title = "一二三四五六"
+ assert t.save
+ end
+ end
+
+ def test_optionally_validates_length_of_using_within_on_update_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :within => 5..10, :on => :update, :too_short => "短すぎます: {{count}}"
+
+ t = Topic.create("title" => "一二三4", "content" => "whatever")
+ assert !t.save
+ assert t.errors[:title].any?
+
+ t.title = "1二三4"
+ assert !t.save
+ assert t.errors[:title].any?
+ assert_equal ["短すぎます: 5"], t.errors[:title]
+
+ t.title = "一二三四五六七八九十A"
+ assert !t.save
+ assert t.errors[:title].any?
+
+ t.title = "一二345"
+ assert t.save
+ end
+ end
+
+ def test_validates_length_of_using_is_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :is => 5
+
+ t = Topic.create("title" => "一二345", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "一二345六"
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is the wrong length (should be 5 characters)"], t.errors["title"]
+ end
+ end
+
+ def test_validates_length_of_with_block
+ Topic.validates_length_of :content, :minimum => 5, :too_short=>"Your essay must be at least {{count}} words.",
+ :tokenizer => lambda {|str| str.scan(/\w+/) }
+ t = Topic.create!(:content => "this content should be long enough")
+ assert t.valid?
+
+ t.content = "not long enough"
+ assert !t.valid?
+ assert t.errors[:content].any?
+ assert_equal ["Your essay must be at least 5 words."], t.errors[:content]
+ end
+
+ def test_validates_length_of_with_custom_too_long_using_quotes
+ 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[:name]
+ end
+ end
+
+ def test_validates_length_of_with_custom_too_short_using_quotes
+ 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[:name]
+ end
+ end
+
+ def test_validates_length_of_with_custom_message_using_quotes
+ 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[:name]
+ end
+ end
+
+ def test_validates_length_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_length_of :karma, :minimum => 5
+
+ p = Person.new
+ p.karma = "Pix"
+ assert p.invalid?
+
+ assert_equal ["is too short (minimum is 5 characters)"], p.errors[:karma]
+
+ p.karma = "The Smiths"
+ assert p.valid?
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
new file mode 100644
index 0000000000..0af6eb69ce
--- /dev/null
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -0,0 +1,179 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/developer'
+require 'models/person'
+
+class NumericalityValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ NIL = [nil]
+ BLANK = ["", " ", " \t \r \n"]
+ BIGDECIMAL_STRINGS = %w(12345678901234567890.1234567890) # 30 significent digits
+ FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1 90.1e1 -90.1e5 -90.1e-5 90e-5)
+ INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090)
+ FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS
+ INTEGERS = [0, 10, -10] + INTEGER_STRINGS
+ BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) }
+ 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 test_default_validates_numericality_of
+ Topic.validates_numericality_of :approved
+ invalid!(NIL + BLANK + JUNK)
+ valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
+ end
+
+ def test_validates_numericality_of_with_nil_allowed
+ Topic.validates_numericality_of :approved, :allow_nil => true
+
+ invalid!(JUNK)
+ valid!(NIL + BLANK + FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
+ end
+
+ def test_validates_numericality_of_with_integer_only
+ Topic.validates_numericality_of :approved, :only_integer => true
+
+ invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL + INFINITY)
+ valid!(INTEGERS)
+ end
+
+ def test_validates_numericality_of_with_integer_only_and_nil_allowed
+ Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true
+
+ invalid!(JUNK + FLOATS + BIGDECIMAL + INFINITY)
+ valid!(NIL + BLANK + INTEGERS)
+ end
+
+ def test_validates_numericality_with_greater_than
+ Topic.validates_numericality_of :approved, :greater_than => 10
+
+ invalid!([-10, 10], 'must be greater than 10')
+ valid!([11])
+ end
+
+ def test_validates_numericality_with_greater_than_or_equal
+ Topic.validates_numericality_of :approved, :greater_than_or_equal_to => 10
+
+ invalid!([-9, 9], 'must be greater than or equal to 10')
+ valid!([10])
+ end
+
+ def test_validates_numericality_with_equal_to
+ Topic.validates_numericality_of :approved, :equal_to => 10
+
+ invalid!([-10, 11] + INFINITY, 'must be equal to 10')
+ valid!([10])
+ end
+
+ def test_validates_numericality_with_less_than
+ Topic.validates_numericality_of :approved, :less_than => 10
+
+ invalid!([10], 'must be less than 10')
+ valid!([-9, 9])
+ end
+
+ def test_validates_numericality_with_less_than_or_equal_to
+ Topic.validates_numericality_of :approved, :less_than_or_equal_to => 10
+
+ invalid!([11], 'must be less than or equal to 10')
+ valid!([-10, 10])
+ end
+
+ def test_validates_numericality_with_odd
+ Topic.validates_numericality_of :approved, :odd => true
+
+ invalid!([-2, 2], 'must be odd')
+ valid!([-1, 1])
+ end
+
+ def test_validates_numericality_with_even
+ Topic.validates_numericality_of :approved, :even => true
+
+ invalid!([-1, 1], 'must be even')
+ valid!([-2, 2])
+ end
+
+ def test_validates_numericality_with_greater_than_less_than_and_even
+ Topic.validates_numericality_of :approved, :greater_than => 1, :less_than => 4, :even => true
+
+ invalid!([1, 3, 4])
+ valid!([2])
+ end
+
+ def test_validates_numericality_with_numeric_message
+ Topic.validates_numericality_of :approved, :less_than => 4, :message => "smaller than {{count}}"
+ topic = Topic.new("title" => "numeric test", "approved" => 10)
+
+ assert !topic.valid?
+ assert_equal ["smaller than 4"], topic.errors[:approved]
+
+ Topic.validates_numericality_of :approved, :greater_than => 4, :message => "greater than {{count}}"
+ topic = Topic.new("title" => "numeric test", "approved" => 1)
+
+ assert !topic.valid?
+ assert_equal ["greater than 4"], topic.errors[:approved]
+ end
+
+ def test_numericality_with_getter_method
+ 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_numericality_with_allow_nil_and_getter_method
+ 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_numericality_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_numericality_of :karma, :allow_nil => false
+
+ p = Person.new
+ p.karma = "Pix"
+ assert p.invalid?
+
+ assert_equal ["is not a number"], p.errors[:karma]
+
+ p.karma = "1234"
+ assert p.valid?
+ end
+ end
+
+ private
+
+ def invalid!(values, error = nil)
+ with_each_topic_approved_value(values) do |topic, value|
+ assert !topic.valid?, "#{value.inspect} not rejected as a number"
+ assert topic.errors[:approved].any?, "FAILED for #{value.inspect}"
+ assert_equal error, topic.errors[:approved].first if error
+ end
+ end
+
+ def valid!(values)
+ with_each_topic_approved_value(values) do |topic, value|
+ assert topic.valid?, "#{value.inspect} not accepted as a number"
+ end
+ end
+
+ def with_each_topic_approved_value(values)
+ topic = Topic.new(:title => "numeric test", :content => "whatever")
+ values.each do |value|
+ topic.approved = value
+ yield topic, value
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb
new file mode 100644
index 0000000000..f6bed4903a
--- /dev/null
+++ b/activemodel/test/cases/validations/presence_validation_test.rb
@@ -0,0 +1,57 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/developer'
+require 'models/person'
+
+class PresenceValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_validate_presences
+ Topic.validates_presence_of(:title, :content)
+
+ t = Topic.create
+ assert !t.save
+ assert_equal ["can't be blank"], t.errors[:title]
+ assert_equal ["can't be blank"], t.errors[:content]
+
+ t.title = "something"
+ t.content = " "
+
+ assert !t.save
+ assert_equal ["can't be blank"], t.errors[:content]
+
+ t.content = "like stuff"
+
+ assert t.save
+ end
+
+ def test_validates_presence_of_with_custom_message_using_quotes
+ 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[:non_existent]
+ end
+ end
+
+ def test_validates_presence_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_presence_of :karma
+
+ p = Person.new
+ assert p.invalid?
+
+ assert_equal ["can't be blank"], p.errors[:karma]
+
+ p.karma = "Cold"
+ assert p.valid?
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
new file mode 100644
index 0000000000..8c89494247
--- /dev/null
+++ b/activemodel/test/cases/validations_test.rb
@@ -0,0 +1,178 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/reply'
+require 'models/developer'
+
+class ValidationsTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ # 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
+ r.title = "There's no content!"
+ assert !r.valid?, "A reply without content shouldn't be saveable"
+
+ r.content = "Messa content!"
+ assert r.valid?, "A reply with content should be saveable"
+ end
+
+ def test_single_attr_validation_and_error_msg
+ r = Reply.new
+ r.title = "There's no content!"
+ assert !r.valid?
+ assert r.errors[:content].any?, "A reply without content should mark that attribute as invalid"
+ assert_equal ["Empty"], r.errors["content"], "A reply without content should contain an error"
+ assert_equal 1, r.errors.count
+ end
+
+ def test_double_attr_validation_and_error_msg
+ r = Reply.new
+ assert !r.valid?
+
+ assert r.errors[:title].any?, "A reply without title should mark that attribute as invalid"
+ assert_equal ["Empty"], r.errors["title"], "A reply without title should contain an error"
+
+ assert r.errors[:content].any?, "A reply without content should mark that attribute as invalid"
+ assert_equal ["Empty"], r.errors["content"], "A reply without content should contain an error"
+
+ assert_equal 2, r.errors.count
+ end
+
+ def test_single_error_per_attr_iteration
+ r = Reply.new
+ r.save
+
+ errors = []
+ r.errors.each {|attr, messages| errors << [attr.to_s, messages] }
+
+ assert errors.include?(["title", "Empty"])
+ assert errors.include?(["content", "Empty"])
+ end
+
+ def test_multiple_errors_per_attr_iteration_with_full_error_composition
+ r = Reply.new
+ r.title = "Wrong Create"
+ r.content = "Mismatch"
+ r.save
+
+ errors = r.errors.to_a
+
+ assert_equal "Title is Wrong Create", errors[0]
+ assert_equal "Title is Content Mismatch", errors[1]
+ assert_equal 2, r.errors.count
+ end
+
+ def test_errors_on_base
+ r = Reply.new
+ r.content = "Mismatch"
+ r.save
+ r.errors[:base] << "Reply is not dignifying"
+
+ errors = []
+ r.errors.to_a.each { |error| errors << error }
+
+ assert_equal ["Reply is not dignifying"], r.errors[:base]
+
+ assert errors.include?("Title Empty")
+ assert errors.include?("Reply is not dignifying")
+ assert_equal 2, r.errors.count
+ end
+
+ def test_validates_each
+ hits = 0
+ Topic.validates_each(:title, :content, [:title, :content]) do |record, attr|
+ 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[:title]
+ assert_equal %w(gotcha gotcha), t.errors[:content]
+ end
+
+ def test_validate_block
+ Topic.validate { |topic| topic.errors.add("title", "will never be valid") }
+ t = Topic.create("title" => "Title", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["will never be valid"], t.errors["title"]
+ end
+
+ def test_invalid_validator
+ Topic.validate 3
+ assert_raise(ArgumentError) { t = Topic.create }
+ end
+
+ def test_errors_to_xml
+ r = Reply.new :title => "Wrong Create"
+ assert !r.valid?
+ xml = r.errors.to_xml(:skip_instruct => true)
+ assert_equal "<errors>", xml.first(8)
+ assert xml.include?("<error>Title is Wrong Create</error>")
+ assert xml.include?("<error>Content Empty</error>")
+ end
+
+ 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["title"].first
+ end
+
+ def test_invalid_should_be_the_opposite_of_valid
+ Topic.validates_presence_of :title
+
+ t = Topic.new
+ assert t.invalid?
+ assert t.errors[:title].any?
+
+ t.title = 'Things are going to change'
+ assert !t.invalid?
+ end
+
+ def test_deprecated_error_messages_on
+ Topic.validates_presence_of :title
+
+ t = Topic.new
+ assert t.invalid?
+
+ [:title, "title"].each do |attribute|
+ assert_deprecated { assert_equal "can't be blank", t.errors.on(attribute) }
+ end
+
+ Topic.validates_each(:title) do |record, attribute|
+ record.errors[attribute] << "invalid"
+ end
+
+ assert t.invalid?
+
+ [:title, "title"].each do |attribute|
+ assert_deprecated do
+ assert t.errors.on(attribute).include?("invalid")
+ assert t.errors.on(attribute).include?("can't be blank")
+ end
+ end
+ end
+
+ def test_deprecated_errors_on_base_and_each
+ t = Topic.new
+ assert t.valid?
+
+ assert_deprecated { t.errors.add_to_base "invalid topic" }
+ assert_deprecated { assert_equal "invalid topic", t.errors.on_base }
+ assert_deprecated { assert t.errors.invalid?(:base) }
+
+ all_errors = t.errors.to_a
+ assert_deprecated { assert_equal all_errors, t.errors.each_full{|err| err} }
+ end
+end \ No newline at end of file
diff --git a/activemodel/test/config.rb b/activemodel/test/config.rb
new file mode 100644
index 0000000000..0b577a9936
--- /dev/null
+++ b/activemodel/test/config.rb
@@ -0,0 +1,3 @@
+TEST_ROOT = File.expand_path(File.dirname(__FILE__))
+FIXTURES_ROOT = TEST_ROOT + "/fixtures"
+SCHEMA_FILE = TEST_ROOT + "/schema.rb"
diff --git a/activemodel/test/fixtures/topics.yml b/activemodel/test/fixtures/topics.yml
new file mode 100644
index 0000000000..e4c61ce2d8
--- /dev/null
+++ b/activemodel/test/fixtures/topics.yml
@@ -0,0 +1,41 @@
+first:
+ id: 1
+ title: The First Topic
+ author_name: David
+ author_email_address: david@loudthinking.com
+ written_on: 2003-07-16t15:28:11.2233+01:00
+ last_read: 2004-04-15
+ bonus_time: 2005-01-30t15:28:00.00+01:00
+ content: Have a nice day
+ approved: false
+ replies_count: 1
+
+second:
+ id: 2
+ title: The Second Topic of the day
+ author_name: Mary
+ written_on: 2004-07-15t15:28:00.0099+01:00
+ content: Have a nice day
+ approved: true
+ replies_count: 0
+ parent_id: 1
+ type: Reply
+
+third:
+ id: 3
+ title: The Third Topic of the day
+ author_name: Nick
+ written_on: 2005-07-15t15:28:00.0099+01:00
+ content: I'm a troll
+ approved: true
+ replies_count: 1
+
+fourth:
+ id: 4
+ title: The Fourth Topic of the day
+ author_name: Carl
+ written_on: 2006-07-15t15:28:00.0099+01:00
+ content: Why not?
+ approved: true
+ type: Reply
+ parent_id: 3
diff --git a/activemodel/test/models/developer.rb b/activemodel/test/models/developer.rb
new file mode 100644
index 0000000000..5e6eefeed1
--- /dev/null
+++ b/activemodel/test/models/developer.rb
@@ -0,0 +1,6 @@
+class Developer < ActiveRecord::Base
+ validates_inclusion_of :salary, :in => 50000..200000
+ validates_length_of :name, :within => 3..20
+
+ attr_accessor :name_confirmation
+end
diff --git a/activemodel/test/models/person.rb b/activemodel/test/models/person.rb
new file mode 100644
index 0000000000..d98420f900
--- /dev/null
+++ b/activemodel/test/models/person.rb
@@ -0,0 +1,5 @@
+class Person
+ include ActiveModel::Validations
+
+ attr_accessor :title, :karma
+end
diff --git a/activemodel/test/models/reply.rb b/activemodel/test/models/reply.rb
new file mode 100644
index 0000000000..acfd801674
--- /dev/null
+++ b/activemodel/test/models/reply.rb
@@ -0,0 +1,34 @@
+require 'models/topic'
+
+class Reply < Topic
+ validate :errors_on_empty_content
+ validate_on_create :title_is_wrong_create
+
+ validate :check_empty_title
+ validate_on_create :check_content_mismatch
+ validate_on_update :check_wrong_update
+
+ attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read
+
+ def check_empty_title
+ errors[:title] << "Empty" unless attribute_present?("title")
+ end
+
+ def errors_on_empty_content
+ errors[:content] << "Empty" unless attribute_present?("content")
+ end
+
+ def check_content_mismatch
+ if attribute_present?("title") && attribute_present?("content") && content == "Mismatch"
+ errors[:title] << "is Content Mismatch"
+ end
+ end
+
+ def title_is_wrong_create
+ errors[:title] << "is Wrong Create" if attribute_present?("title") && title == "Wrong Create"
+ end
+
+ def check_wrong_update
+ errors[:title] << "is Wrong Update" if attribute_present?("title") && title == "Wrong Update"
+ end
+end
diff --git a/activemodel/test/models/topic.rb b/activemodel/test/models/topic.rb
new file mode 100644
index 0000000000..1350aa17e7
--- /dev/null
+++ b/activemodel/test/models/topic.rb
@@ -0,0 +1,9 @@
+class Topic < ActiveRecord::Base
+ def condition_is_true
+ true
+ end
+
+ def condition_is_true_but_its_not
+ false
+ end
+end
diff --git a/activemodel/test/schema.rb b/activemodel/test/schema.rb
new file mode 100644
index 0000000000..56b824d445
--- /dev/null
+++ b/activemodel/test/schema.rb
@@ -0,0 +1,14 @@
+ActiveRecord::Schema.define do
+ create_table :topics, :force => true do |t|
+ t.string :title
+ t.string :author_name
+ t.text :content
+ t.boolean :approved, :default => true
+ t.string :type
+ end
+
+ create_table :developers, :force => true do |t|
+ t.string :name
+ t.float :salary
+ end
+end
diff --git a/activemodel/test/test_helper.rb b/activemodel/test/test_helper.rb
deleted file mode 100644
index 5b5678e42d..0000000000
--- a/activemodel/test/test_helper.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'rubygems'
-require 'test/unit'
-
-gem 'mocha', '>= 0.9.3'
-require 'mocha'
-
-require 'active_model'
-require 'active_model/state_machine'
-
-$:.unshift File.dirname(__FILE__) + "/../../activesupport/lib"
-require 'active_support'
-require 'active_support/test_case'
-
-class ActiveModel::TestCase < ActiveSupport::TestCase
-end
-
-begin
- require 'ruby-debug'
- Debugger.start
-rescue LoadError
-end
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 81fe921fd8..36adc7b6f2 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -25,9 +25,17 @@ activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
$:.unshift(activesupport_path) if File.directory?(activesupport_path)
require 'active_support'
-$:.unshift(File.dirname(__FILE__) + '/../../arel/lib')
+arel_path = "#{File.dirname(__FILE__)}/../../arel/lib"
+$:.unshift(arel_path) if File.directory?(arel_path)
require 'arel'
+begin
+ require 'active_model'
+rescue LoadError
+ $:.unshift "#{File.dirname(__FILE__)}/../../activemodel/lib"
+ require 'active_model'
+end
+
module ActiveRecord
# TODO: Review explicit loads to see if they will automatically be handled by the initilizer.
def self.load_all!
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 359e70f5ed..9ecf231a66 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module Aggregations # :nodoc:
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
def clear_aggregation_cache #:nodoc:
self.class.reflect_on_all_aggregations.to_a.each do |assoc|
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index 5df76bb183..af80a579d6 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -1,7 +1,7 @@
module ActiveRecord
# See ActiveRecord::AssociationPreload::ClassMethods for documentation.
module AssociationPreload #:nodoc:
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
# Implements the details of eager loading of ActiveRecord associations.
# Application developers should not use this module directly.
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 2c998faf37..a74e9b76dc 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -79,7 +79,7 @@ module ActiveRecord
# See ActiveRecord::Associations::ClassMethods for documentation.
module Associations # :nodoc:
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
# These classes will be loaded when associations are created.
# So there is no need to eager load them.
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index d5e215af9d..15358979c2 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -2,7 +2,7 @@ require 'active_support/core_ext/enumerable'
module ActiveRecord
module AttributeMethods #:nodoc:
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
DEFAULT_SUFFIXES = %w(= ? _before_type_cast)
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 4ab2818282..a540570f42 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -125,7 +125,7 @@ module ActiveRecord
# post.author.name = ''
# post.save(false) # => true
module AutosaveAssociation
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
ASSOCIATION_TYPES = %w{ has_one belongs_to has_many has_and_belongs_to_many }
@@ -250,7 +250,7 @@ module ActiveRecord
unless association.marked_for_destruction?
association.errors.each do |attribute, message|
attribute = "#{reflection.name}_#{attribute}"
- errors.add(attribute, message) unless errors.on(attribute)
+ errors[attribute] << message if errors[attribute].empty?
end
end
else
diff --git a/activerecord/lib/active_record/batches.rb b/activerecord/lib/active_record/batches.rb
index 4836601297..e41d38fb8f 100644
--- a/activerecord/lib/active_record/batches.rb
+++ b/activerecord/lib/active_record/batches.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module Batches # :nodoc:
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
# When processing large numbers of records, it's often a good idea to do
# so in batches to prevent memory ballooning.
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index 8b8fb37d2d..15ca5e14a2 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module Calculations #:nodoc:
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include, :from]
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index a77fdb1c13..36f5f2ce47 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -211,7 +211,7 @@ module ActiveRecord
# needs to be aware of it because an ordinary +save+ will raise such exception
# instead of quietly returning +false+.
module Callbacks
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
CALLBACKS = %w(
after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb
index ac84f6b209..178767e0c3 100644
--- a/activerecord/lib/active_record/dirty.rb
+++ b/activerecord/lib/active_record/dirty.rb
@@ -34,7 +34,7 @@ module ActiveRecord
# person.name << 'by'
# person.name_change # => ['uncle bob', 'uncle bobby']
module Dirty
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was']
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 721114d9d0..2b0cfc2c3b 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -807,7 +807,7 @@ end
module ActiveRecord
module TestFixtures
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
included do
setup :setup_fixtures
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index e51748b7d2..bc22c2a511 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -42,7 +42,7 @@ module ActiveRecord
# To override the name of the lock_version column, invoke the <tt>set_locking_column</tt> method.
# This method uses the same syntax as <tt>set_table_name</tt>
module Optimistic
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
included do
cattr_accessor :lock_optimistically, :instance_writer => false
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index e7151a3d47..1b22fa5e24 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/hash/except'
module ActiveRecord
module NamedScope
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
# All subclasses of ActiveRecord::Base have one named scope:
# * <tt>scoped</tt> - which allows for the creation of anonymous \scopes, on the fly: <tt>Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)</tt>
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index c532d3dfa3..0beb4321a2 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/object/try'
module ActiveRecord
module NestedAttributes #:nodoc:
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
included do
class_inheritable_accessor :reject_new_nested_attributes_procs, :instance_writer => false
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 1ca76c7b2f..89ec0962bf 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -3,7 +3,7 @@ require 'set'
module ActiveRecord
module Observing # :nodoc:
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
module ClassMethods
# Activates the observers assigned. Examples:
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 3747ba449d..0baa9654b7 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module Reflection # :nodoc:
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
# Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
# This information can, for example, be used in a form builder that took an Active Record object and created input
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index de530a3456..2d90ef35aa 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -99,8 +99,15 @@ HEADER
next if column.name == pk
spec = {}
spec[:name] = column.name.inspect
- spec[:type] = column.type.to_s
- spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && column.type != :decimal
+
+ # AR has an optimisation which handles zero-scale decimals as integers. This
+ # code ensures that the dumper still dumps the column as a decimal.
+ spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
+ 'decimal'
+ else
+ column.type.to_s
+ end
+ spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && spec[:type] != 'decimal'
spec[:precision] = column.precision.inspect if !column.precision.nil?
spec[:scale] = column.scale.inspect if !column.scale.nil?
spec[:null] = 'false' if !column.null
diff --git a/activerecord/lib/active_record/serializers/json_serializer.rb b/activerecord/lib/active_record/serializers/json_serializer.rb
index d376fd5e1b..67e2b2abb3 100644
--- a/activerecord/lib/active_record/serializers/json_serializer.rb
+++ b/activerecord/lib/active_record/serializers/json_serializer.rb
@@ -2,7 +2,7 @@ require 'active_support/json'
module ActiveRecord #:nodoc:
module Serialization
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
included do
cattr_accessor :include_root_in_json, :instance_writer => false
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 3734e170af..da075dabd3 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -8,7 +8,7 @@ module ActiveRecord
# Timestamps are in the local timezone by default but you can use UTC by setting
# <tt>ActiveRecord::Base.default_timezone = :utc</tt>
module Timestamp
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
included do
alias_method_chain :create, :timestamps
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 471a81dfb5..4f8ccdd40e 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -3,7 +3,7 @@ require 'thread'
module ActiveRecord
# See ActiveRecord::Transactions::ClassMethods for documentation.
module Transactions
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
class TransactionError < ActiveRecordError # :nodoc:
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index a18fb3f426..85b65ecf1a 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -16,59 +16,46 @@ module ActiveRecord
end
end
- # 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.
- class Errors
- include Enumerable
-
+ class Errors < ActiveModel::Errors
class << self
def default_error_messages
- ActiveSupport::Deprecation.warn("ActiveRecord::Errors.default_error_messages has been deprecated. Please use I18n.translate('activerecord.errors.messages').")
+ message = "Errors.default_error_messages has been deprecated. Please use I18n.translate('activerecord.errors.messages')."
+ ActiveSupport::Deprecation.warn(message)
+
I18n.translate 'activerecord.errors.messages'
end
end
- def initialize(base) # :nodoc:
- @base, @errors = base, {}
- end
-
- # Adds an error to the base object instead of any particular attribute. This is used
- # to report errors that don't tie to any specific attribute, but rather to the object
- # as a whole. These error messages don't get prepended with any field name when iterating
- # with +each_full+, so they should be complete sentences.
- def add_to_base(msg)
- add(:base, msg)
- end
+ # Returns all the full error messages in an array.
+ #
+ # class Company < ActiveRecord::Base
+ # validates_presence_of :name, :address, :email
+ # validates_length_of :name, :in => 5..30
+ # end
+ #
+ # company = Company.create(:address => '123 First St.')
+ # company.errors.full_messages # =>
+ # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
+ def full_messages(options = {})
+ full_messages = []
- # Adds an error message (+messsage+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
- # for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
- # 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 +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
- def add(attribute, message = nil, options = {})
- message ||= :invalid
- message = generate_message(attribute, message, options) if message.is_a?(Symbol)
- @errors[attribute.to_s] ||= []
- @errors[attribute.to_s] << message
- end
+ each do |attribute, messages|
+ next if messages.empty?
- # Will add an error message to each of the attributes in +attributes+ that is empty.
- def add_on_empty(attributes, custom_message = nil)
- for attr in [attributes].flatten
- value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
- is_empty = value.respond_to?(:empty?) ? value.empty? : false
- add(attr, :empty, :default => custom_message) unless !value.nil? && !is_empty
+ if attribute == :base
+ messages.each {|m| full_messages << m }
+ else
+ attr_name = @base.class.human_attribute_name(attribute.to_s)
+ prefix = attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ')
+ messages.each do |m|
+ full_messages << "#{prefix}#{m}"
+ end
+ end
end
- end
- # Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
- def add_on_blank(attributes, custom_message = nil)
- for attr in [attributes].flatten
- value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
- add(attr, :blank, :default => custom_message) if value.blank?
- end
+ full_messages
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
@@ -88,7 +75,6 @@ module ActiveRecord
# <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|
@@ -111,876 +97,22 @@ module ActiveRecord
I18n.translate(key, options)
end
-
- # Returns true if the specified +attribute+ has errors associated with it.
- #
- # class Company < ActiveRecord::Base
- # validates_presence_of :name, :address, :email
- # validates_length_of :name, :in => 5..30
- # end
- #
- # company = Company.create(:address => '123 First St.')
- # company.errors.invalid?(:name) # => true
- # company.errors.invalid?(:address) # => false
- def invalid?(attribute)
- !@errors[attribute.to_s].nil?
- end
-
- # Returns +nil+, if no errors are associated with the specified +attribute+.
- # Returns the error message, if one error is associated with the specified +attribute+.
- # Returns an array of error messages, if more than one error is associated with the specified +attribute+.
- #
- # class Company < ActiveRecord::Base
- # validates_presence_of :name, :address, :email
- # validates_length_of :name, :in => 5..30
- # end
- #
- # company = Company.create(:address => '123 First St.')
- # company.errors.on(:name) # => ["is too short (minimum is 5 characters)", "can't be blank"]
- # company.errors.on(:email) # => "can't be blank"
- # company.errors.on(:address) # => nil
- def on(attribute)
- errors = @errors[attribute.to_s]
- return nil if errors.nil?
- errors.size == 1 ? errors.first : errors
- end
-
- alias :[] :on
-
- # Returns errors assigned to the base object through +add_to_base+ according to the normal rules of <tt>on(attribute)</tt>.
- def on_base
- on(:base)
- end
-
- # Yields each attribute and associated message per error added.
- #
- # class Company < ActiveRecord::Base
- # validates_presence_of :name, :address, :email
- # validates_length_of :name, :in => 5..30
- # end
- #
- # company = Company.create(:address => '123 First St.')
- # company.errors.each{|attr,msg| puts "#{attr} - #{msg}" }
- # # => name - is too short (minimum is 5 characters)
- # # name - can't be blank
- # # address - can't be blank
- def each
- @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
- end
-
- # Yields each full error message added. So <tt>Person.errors.add("first_name", "can't be empty")</tt> will be returned
- # through iteration as "First name can't be empty".
- #
- # class Company < ActiveRecord::Base
- # validates_presence_of :name, :address, :email
- # validates_length_of :name, :in => 5..30
- # end
- #
- # company = Company.create(:address => '123 First St.')
- # company.errors.each_full{|msg| puts msg }
- # # => Name is too short (minimum is 5 characters)
- # # Name can't be blank
- # # Address can't be blank
- def each_full
- full_messages.each { |msg| yield msg }
- end
-
- # Returns all the full error messages in an array.
- #
- # class Company < ActiveRecord::Base
- # validates_presence_of :name, :address, :email
- # validates_length_of :name, :in => 5..30
- # end
- #
- # company = Company.create(:address => '123 First St.')
- # company.errors.full_messages # =>
- # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
- def full_messages(options = {})
- full_messages = []
-
- @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
- full_messages
- end
-
- # Returns true if no errors have been added.
- def empty?
- @errors.empty?
- end
-
- # Removes all errors that have been added.
- def clear
- @errors = {}
- end
-
- # Returns the total number of errors added. Two errors added to the same attribute will be counted as such.
- def size
- @errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
- end
-
- alias_method :count, :size
- alias_method :length, :size
-
- # Returns an XML representation of this error object.
- #
- # class Company < ActiveRecord::Base
- # validates_presence_of :name, :address, :email
- # validates_length_of :name, :in => 5..30
- # end
- #
- # company = Company.create(:address => '123 First St.')
- # company.errors.to_xml
- # # => <?xml version="1.0" encoding="UTF-8"?>
- # # <errors>
- # # <error>Name is too short (minimum is 5 characters)</error>
- # # <error>Name can't be blank</error>
- # # <error>Address can't be blank</error>
- # # </errors>
- def to_xml(options={})
- require 'builder' unless defined? ::Builder
- options[:root] ||= "errors"
- options[:indent] ||= 2
- options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
-
- options[:builder].instruct! unless options.delete(:skip_instruct)
- options[:builder].errors do |e|
- full_messages.each { |msg| e.error(msg) }
- end
- end
-
end
-
- # Please do have a look at ActiveRecord::Validations::ClassMethods for a higher level of validations.
- #
- # Active Records implement validation by overwriting Base#validate (or the variations, +validate_on_create+ and
- # +validate_on_update+). Each of these methods can inspect the state of the object, which usually means ensuring
- # that a number of attributes have a certain value (such as not empty, within a given range, matching a certain regular expression).
- #
- # Example:
- #
- # class Person < ActiveRecord::Base
- # protected
- # def validate
- # errors.add_on_empty %w( first_name last_name )
- # errors.add("phone_number", "has invalid format") unless phone_number =~ /[0-9]*/
- # end
- #
- # def validate_on_create # is only run the first time a new object is saved
- # unless valid_discount?(membership_discount)
- # errors.add("membership_discount", "has expired")
- # end
- # end
- #
- # def validate_on_update
- # errors.add_to_base("No changes have occurred") if unchanged_attributes?
- # end
- # end
- #
- # person = Person.new("first_name" => "David", "phone_number" => "what?")
- # person.save # => false (and doesn't do the save)
- # person.errors.empty? # => false
- # person.errors.count # => 2
- # person.errors.on "last_name" # => "can't be empty"
- # person.errors.on "phone_number" # => "has invalid format"
- # person.errors.each_full { |msg| puts msg }
- # # => "Last name can't be empty\n" +
- # # "Phone number has invalid format"
- #
- # person.attributes = { "last_name" => "Heinemeier", "phone_number" => "555-555" }
- # person.save # => true (and person is now saved in the database)
- #
- # An Errors object is automatically created for every Active Record.
module Validations
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
- VALIDATIONS = %w( validate validate_on_create validate_on_update )
+ include ActiveSupport::Callbacks
+ include ActiveModel::Validations
included do
alias_method_chain :save, :validation
alias_method_chain :save!, :validation
- include ActiveSupport::Callbacks
- define_callbacks *VALIDATIONS
+ define_callbacks :validate_on_create, :validate_on_update
end
- # Active Record classes can implement validations in several ways. The highest level, easiest to read,
- # and recommended approach is to use the declarative <tt>validates_..._of</tt> class methods (and
- # +validates_associated+) documented below. These are sufficient for most model validations.
- #
- # Slightly lower level is +validates_each+. It provides some of the same options as the purely declarative
- # validation methods, but like all the lower-level approaches it requires manually adding to the errors collection
- # when the record is invalid.
- #
- # At a yet lower level, a model can use the class methods +validate+, +validate_on_create+ and +validate_on_update+
- # to add validation methods or blocks. These are ActiveSupport::Callbacks and follow the same rules of inheritance
- # and chaining.
- #
- # The lowest level style is to define the instance methods +validate+, +validate_on_create+ and +validate_on_update+
- # as documented in ActiveRecord::Validations.
- #
- # == +validate+, +validate_on_create+ and +validate_on_update+ Class Methods
- #
- # Calls to these methods add a validation method or block to the class. Again, this approach is recommended
- # only when the higher-level methods documented below (<tt>validates_..._of</tt> and +validates_associated+) are
- # insufficient to handle the required validation.
- #
- # This can be done with a symbol pointing to a method:
- #
- # class Comment < ActiveRecord::Base
- # validate :must_be_friends
- #
- # def must_be_friends
- # errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
- # end
- # end
- #
- # Or with a block which is passed the current record to be validated:
- #
- # class Comment < ActiveRecord::Base
- # validate do |comment|
- # comment.must_be_friends
- # end
- #
- # def must_be_friends
- # errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
- # end
- # end
- #
- # This usage applies to +validate_on_create+ and +validate_on_update+ as well.
module ClassMethods
- DEFAULT_VALIDATION_OPTIONS = {
- :on => :save,
- :allow_nil => false,
- :allow_blank => false,
- :message => nil
- }.freeze
-
- ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
- ALL_NUMERICALITY_CHECKS = { :greater_than => '>', :greater_than_or_equal_to => '>=',
- :equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
- :odd => 'odd?', :even => 'even?' }.freeze
-
- # Validates each attribute against a block.
- #
- # class Person < ActiveRecord::Base
- # validates_each :first_name, :last_name do |record, attr, value|
- # record.errors.add attr, 'starts with z.' if value[0] == ?z
- # end
- # end
- #
- # Options:
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
- # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
- # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- def validates_each(*attrs)
- options = attrs.extract_options!.symbolize_keys
- attrs = attrs.flatten
-
- # Declare the validation.
- send(validation_method(options[:on] || :save), options) do |record|
- attrs.each do |attr|
- value = record.send(attr)
- next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
- yield record, attr, value
- end
- end
- end
-
- # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
- #
- # Model:
- # class Person < ActiveRecord::Base
- # validates_confirmation_of :user_name, :password
- # validates_confirmation_of :email_address, :message => "should match confirmation"
- # end
- #
- # View:
- # <%= password_field "person", "password" %>
- # <%= password_field "person", "password_confirmation" %>
- #
- # The added +password_confirmation+ attribute is virtual; it exists only as an in-memory attribute for validating the password.
- # To achieve this, the validation adds accessors to the model for the confirmation attribute. NOTE: This check is performed
- # only if +password_confirmation+ is not +nil+, and by default only on save. To require confirmation, make sure to add a presence
- # check for the confirmation attribute:
- #
- # validates_presence_of :password_confirmation, :if => :password_changed?
- #
- # Configuration options:
- # * <tt>:message</tt> - A custom error message (default is: "doesn't match confirmation").
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- def validates_confirmation_of(*attr_names)
- configuration = { :on => :save }
- configuration.update(attr_names.extract_options!)
-
- attr_accessor(*(attr_names.map { |n| "#{n}_confirmation" }))
-
- validates_each(attr_names, configuration) do |record, attr_name, value|
- unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
- record.errors.add(attr_name, :confirmation, :default => configuration[:message])
- end
- end
- end
-
- # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
- #
- # class Person < ActiveRecord::Base
- # validates_acceptance_of :terms_of_service
- # validates_acceptance_of :eula, :message => "must be abided"
- # end
- #
- # If the database column does not exist, the +terms_of_service+ attribute is entirely virtual. This check is
- # performed only if +terms_of_service+ is not +nil+ and by default on save.
- #
- # Configuration options:
- # * <tt>:message</tt> - A custom error message (default is: "must be accepted").
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
- # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is true).
- # * <tt>:accept</tt> - Specifies value that is considered accepted. The default value is a string "1", which
- # makes it easy to relate to an HTML checkbox. This should be set to +true+ if you are validating a database
- # column, since the attribute is typecast from "1" to +true+ before validation.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- def validates_acceptance_of(*attr_names)
- configuration = { :on => :save, :allow_nil => true, :accept => "1" }
- configuration.update(attr_names.extract_options!)
-
- db_cols = begin
- column_names
- rescue Exception # To ignore both statement and connection errors
- []
- end
- names = attr_names.reject { |name| db_cols.include?(name.to_s) }
- attr_accessor(*names)
-
- validates_each(attr_names,configuration) do |record, attr_name, value|
- unless value == configuration[:accept]
- record.errors.add(attr_name, :accepted, :default => configuration[:message])
- end
- end
- end
-
- # Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example:
- #
- # class Person < ActiveRecord::Base
- # validates_presence_of :first_name
- # end
- #
- # The first_name attribute must be in the object and it cannot be blank.
- #
- # If you want to validate the presence of a boolean field (where the real values are true and false),
- # you will want to use <tt>validates_inclusion_of :field_name, :in => [true, false]</tt>.
- #
- # This is due to the way Object#blank? handles boolean values: <tt>false.blank? # => true</tt>.
- #
- # Configuration options:
- # * <tt>message</tt> - A custom error message (default is: "can't be blank").
- # * <tt>on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>,
- # <tt>:update</tt>).
- # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
- # The method, proc or string should return or evaluate to a true or false value.
- # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
- # The method, proc or string should return or evaluate to a true or false value.
- #
- def validates_presence_of(*attr_names)
- configuration = { :on => :save }
- configuration.update(attr_names.extract_options!)
-
- # can't use validates_each here, because it cannot cope with nonexistent attributes,
- # while errors.add_on_empty can
- send(validation_method(configuration[:on]), configuration) do |record|
- record.errors.add_on_blank(attr_names, configuration[:message])
- end
- end
-
- # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
- #
- # class Person < ActiveRecord::Base
- # validates_length_of :first_name, :maximum=>30
- # validates_length_of :last_name, :maximum=>30, :message=>"less than {{count}} if you don't mind"
- # validates_length_of :fax, :in => 7..32, :allow_nil => true
- # validates_length_of :phone, :in => 7..32, :allow_blank => true
- # validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
- # validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least {{count}} character"
- # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with {{count}} characters... don't play me."
- # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least {{count}} words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
- # end
- #
- # Configuration options:
- # * <tt>:minimum</tt> - The minimum size of the attribute.
- # * <tt>:maximum</tt> - The maximum size of the attribute.
- # * <tt>:is</tt> - The exact size of the attribute.
- # * <tt>:within</tt> - A range specifying the minimum and maximum size of the attribute.
- # * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
- # * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
- # * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
- # * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is {{count}} characters)").
- # * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is {{count}} characters)").
- # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be {{count}} characters)").
- # * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
- # count words as in above example.)
- # Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
- def validates_length_of(*attrs)
- # Merge given options with defaults.
- options = {
- :tokenizer => lambda {|value| value.split(//)}
- }.merge(DEFAULT_VALIDATION_OPTIONS)
- options.update(attrs.extract_options!.symbolize_keys)
-
- # Ensure that one and only one range option is specified.
- range_options = ALL_RANGE_OPTIONS & options.keys
- case range_options.size
- when 0
- raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
- when 1
- # Valid number of options; do nothing.
- else
- raise ArgumentError, 'Too many range options specified. Choose only one.'
- end
-
- # Get range option and value.
- option = range_options.first
- option_value = options[range_options.first]
- key = {:is => :wrong_length, :minimum => :too_short, :maximum => :too_long}[option]
- custom_message = options[:message] || options[key]
-
- case option
- when :within, :in
- raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
-
- validates_each(attrs, options) do |record, attr, value|
- value = options[:tokenizer].call(value) if value.kind_of?(String)
- if value.nil? or value.size < option_value.begin
- record.errors.add(attr, :too_short, :default => custom_message || options[:too_short], :count => option_value.begin)
- elsif value.size > option_value.end
- record.errors.add(attr, :too_long, :default => custom_message || options[:too_long], :count => option_value.end)
- end
- end
- when :is, :minimum, :maximum
- raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
-
- # Declare different validations per option.
- validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
-
- validates_each(attrs, options) do |record, attr, value|
- value = options[:tokenizer].call(value) if value.kind_of?(String)
- unless !value.nil? and value.size.method(validity_checks[option])[option_value]
- record.errors.add(attr, key, :default => custom_message, :count => option_value)
- end
- end
- end
- end
-
- alias_method :validates_size_of, :validates_length_of
-
-
- # Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
- # can be named "davidhh".
- #
- # class Person < ActiveRecord::Base
- # validates_uniqueness_of :user_name, :scope => :account_id
- # end
- #
- # It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example,
- # making sure that a teacher can only be on the schedule once per semester for a particular class.
- #
- # class TeacherSchedule < ActiveRecord::Base
- # validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
- # end
- #
- # When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified
- # attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
- #
- # Configuration options:
- # * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
- # * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
- # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
- # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
- # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- #
- # === Concurrency and integrity
- #
- # Using this validation method in conjunction with ActiveRecord::Base#save
- # does not guarantee the absence of duplicate record insertions, because
- # uniqueness checks on the application level are inherently prone to race
- # conditions. For example, suppose that two users try to post a Comment at
- # the same time, and a Comment's title must be unique. At the database-level,
- # the actions performed by these users could be interleaved in the following manner:
- #
- # User 1 | User 2
- # ------------------------------------+--------------------------------------
- # # User 1 checks whether there's |
- # # already a comment with the title |
- # # 'My Post'. This is not the case. |
- # SELECT * FROM comments |
- # WHERE title = 'My Post' |
- # |
- # | # User 2 does the same thing and also
- # | # infers that his title is unique.
- # | SELECT * FROM comments
- # | WHERE title = 'My Post'
- # |
- # # User 1 inserts his comment. |
- # INSERT INTO comments |
- # (title, content) VALUES |
- # ('My Post', 'hi!') |
- # |
- # | # User 2 does the same thing.
- # | INSERT INTO comments
- # | (title, content) VALUES
- # | ('My Post', 'hello!')
- # |
- # | # ^^^^^^
- # | # Boom! We now have a duplicate
- # | # title!
- #
- # This could even happen if you use transactions with the 'serializable'
- # isolation level. There are several ways to get around this problem:
- # - By locking the database table before validating, and unlocking it after
- # saving. However, table locking is very expensive, and thus not
- # recommended.
- # - By locking a lock file before validating, and unlocking it after saving.
- # This does not work if you've scaled your Rails application across
- # multiple web servers (because they cannot share lock files, or cannot
- # do that efficiently), and thus not recommended.
- # - Creating a unique index on the field, by using
- # ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
- # rare case that a race condition occurs, the database will guarantee
- # the field's uniqueness.
- #
- # When the database catches such a duplicate insertion,
- # ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
- # exception. You can either choose to let this error propagate (which
- # will result in the default Rails exception page being shown), or you
- # can catch it and restart the transaction (e.g. by telling the user
- # that the title already exists, and asking him to re-enter the title).
- # This technique is also known as optimistic concurrency control:
- # http://en.wikipedia.org/wiki/Optimistic_concurrency_control
- #
- # Active Record currently provides no way to distinguish unique
- # index constraint errors from other types of database errors, so you
- # will have to parse the (database-specific) exception message to detect
- # such a case.
- def validates_uniqueness_of(*attr_names)
- configuration = { :case_sensitive => true }
- configuration.update(attr_names.extract_options!)
-
- validates_each(attr_names,configuration) do |record, attr_name, value|
- # The check for an existing value should be run from a class that
- # isn't abstract. This means working down from the current class
- # (self), to the first non-abstract class. Since classes don't know
- # their subclasses, we have to build the hierarchy between self and
- # the record's class.
- class_hierarchy = [record.class]
- while class_hierarchy.first != self
- class_hierarchy.insert(0, class_hierarchy.first.superclass)
- end
-
- # Now we can work our way down the tree to the first non-abstract
- # class (which has a database table to query from).
- finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }
-
- column = finder_class.columns_hash[attr_name.to_s]
-
- if value.nil?
- comparison_operator = "IS ?"
- elsif column.text?
- comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
- value = column.limit ? value.to_s[0, column.limit] : value.to_s
- else
- comparison_operator = "= ?"
- end
-
- sql_attribute = "#{record.class.quoted_table_name}.#{connection.quote_column_name(attr_name)}"
-
- if value.nil? || (configuration[:case_sensitive] || !column.text?)
- condition_sql = "#{sql_attribute} #{comparison_operator}"
- condition_params = [value]
- else
- condition_sql = "LOWER(#{sql_attribute}) #{comparison_operator}"
- condition_params = [value.mb_chars.downcase]
- end
-
- if scope = configuration[:scope]
- Array(scope).map do |scope_item|
- scope_value = record.send(scope_item)
- condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{scope_item}", scope_value)
- condition_params << scope_value
- end
- end
-
- unless record.new_record?
- condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?"
- condition_params << record.send(:id)
- end
-
- finder_class.with_exclusive_scope do
- if finder_class.exists?([condition_sql, *condition_params])
- record.errors.add(attr_name, :taken, :default => configuration[:message], :value => value)
- end
- end
- end
- end
-
-
- # Validates whether the value of the specified attribute is of the correct form by matching it against the regular expression
- # provided.
- #
- # class Person < ActiveRecord::Base
- # validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
- # end
- #
- # Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
- #
- # A regular expression must be provided or else an exception will be raised.
- #
- # Configuration options:
- # * <tt>:message</tt> - A custom error message (default is: "is invalid").
- # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
- # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
- # * <tt>:with</tt> - The regular expression used to validate the format with (note: must be supplied!).
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- def validates_format_of(*attr_names)
- configuration = { :on => :save, :with => nil }
- configuration.update(attr_names.extract_options!)
-
- raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
-
- validates_each(attr_names, configuration) do |record, attr_name, value|
- unless value.to_s =~ configuration[:with]
- record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
- end
- end
- end
-
- # Validates whether the value of the specified attribute is available in a particular enumerable object.
- #
- # class Person < ActiveRecord::Base
- # validates_inclusion_of :gender, :in => %w( m f )
- # validates_inclusion_of :age, :in => 0..99
- # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension {{value}} is not included in the list"
- # end
- #
- # Configuration options:
- # * <tt>:in</tt> - An enumerable object of available items.
- # * <tt>:message</tt> - Specifies a custom error message (default is: "is not included in the list").
- # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
- # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- def validates_inclusion_of(*attr_names)
- configuration = { :on => :save }
- configuration.update(attr_names.extract_options!)
-
- enum = configuration[:in] || configuration[:within]
-
- raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?(:include?)
-
- validates_each(attr_names, configuration) do |record, attr_name, value|
- unless enum.include?(value)
- record.errors.add(attr_name, :inclusion, :default => configuration[:message], :value => value)
- end
- end
- end
-
- # Validates that the value of the specified attribute is not in a particular enumerable object.
- #
- # class Person < ActiveRecord::Base
- # validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
- # validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
- # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension {{value}} is not allowed"
- # end
- #
- # Configuration options:
- # * <tt>:in</tt> - An enumerable object of items that the value shouldn't be part of.
- # * <tt>:message</tt> - Specifies a custom error message (default is: "is reserved").
- # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
- # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- def validates_exclusion_of(*attr_names)
- configuration = { :on => :save }
- configuration.update(attr_names.extract_options!)
-
- enum = configuration[:in] || configuration[:within]
-
- raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?(:include?)
-
- validates_each(attr_names, configuration) do |record, attr_name, value|
- if enum.include?(value)
- record.errors.add(attr_name, :exclusion, :default => configuration[:message], :value => value)
- end
- end
- end
-
- # Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
- #
- # class Book < ActiveRecord::Base
- # has_many :pages
- # belongs_to :library
- #
- # validates_associated :pages, :library
- # end
- #
- # Warning: If, after the above definition, you then wrote:
- #
- # class Page < ActiveRecord::Base
- # belongs_to :book
- #
- # validates_associated :book
- # end
- #
- # this would specify a circular dependency and cause infinite recursion.
- #
- # NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association
- # is both present and guaranteed to be valid, you also need to use +validates_presence_of+.
- #
- # Configuration options:
- # * <tt>:message</tt> - A custom error message (default is: "is invalid")
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- def validates_associated(*attr_names)
- configuration = { :on => :save }
- configuration.update(attr_names.extract_options!)
-
- validates_each(attr_names, configuration) do |record, attr_name, value|
- unless (value.is_a?(Array) ? value : [value]).collect { |r| r.nil? || r.valid? }.all?
- record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
- end
- end
- end
-
- # Validates whether the value of the specified attribute is numeric by trying to convert it to
- # a float with Kernel.Float (if <tt>only_integer</tt> is false) or applying it to the regular expression
- # <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>only_integer</tt> is set to true).
- #
- # class Person < ActiveRecord::Base
- # validates_numericality_of :value, :on => :create
- # end
- #
- # Configuration options:
- # * <tt>:message</tt> - A custom error message (default is: "is not a number").
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
- # * <tt>:only_integer</tt> - Specifies whether the value has to be an integer, e.g. an integral value (default is +false+).
- # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is +false+). Notice that for fixnum and float columns empty strings are converted to +nil+.
- # * <tt>:greater_than</tt> - Specifies the value must be greater than the supplied value.
- # * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be greater than or equal the supplied value.
- # * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied value.
- # * <tt>:less_than</tt> - Specifies the value must be less than the supplied value.
- # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less than or equal the supplied value.
- # * <tt>:odd</tt> - Specifies the value must be an odd number.
- # * <tt>:even</tt> - Specifies the value must be an even number.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- def validates_numericality_of(*attr_names)
- configuration = { :on => :save, :only_integer => false, :allow_nil => false }
- configuration.update(attr_names.extract_options!)
-
-
- numericality_options = ALL_NUMERICALITY_CHECKS.keys & configuration.keys
-
- (numericality_options - [ :odd, :even ]).each do |option|
- raise ArgumentError, ":#{option} must be a number" unless configuration[option].is_a?(Numeric)
- end
-
- validates_each(attr_names,configuration) do |record, attr_name, value|
- raw_value = record.send("#{attr_name}_before_type_cast") || value
-
- next if configuration[:allow_nil] and raw_value.nil?
-
- if configuration[:only_integer]
- unless raw_value.to_s =~ /\A[+-]?\d+\Z/
- record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
- next
- end
- raw_value = raw_value.to_i
- else
- begin
- raw_value = Kernel.Float(raw_value)
- rescue ArgumentError, TypeError
- record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
- next
- end
- end
-
- numericality_options.each do |option|
- case option
- when :odd, :even
- unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
- record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message])
- end
- else
- record.errors.add(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option]) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
- end
- end
- end
- end
-
# Creates an object just like Base.create but calls save! instead of save
# so an exception is raised if the record is invalid.
def create!(attributes = nil, &block)
@@ -994,75 +126,82 @@ module ActiveRecord
end
end
- private
- def validation_method(on)
- case on
- when :save then :validate
- when :create then :validate_on_create
- when :update then :validate_on_update
- end
+ def validation_method(on)
+ case on
+ when :create
+ :validate_on_create
+ when :update
+ :validate_on_update
+ else
+ :validate
end
+ end
end
- # The validation process on save can be skipped by passing false. The regular Base#save method is
- # replaced with this when the validations module is mixed in, which it is by default.
- def save_with_validation(perform_validation = true)
- if perform_validation && valid? || !perform_validation
- save_without_validation
- else
- false
+ module InstanceMethods
+ # The validation process on save can be skipped by passing false. The regular Base#save method is
+ # replaced with this when the validations module is mixed in, which it is by default.
+ def save_with_validation(perform_validation = true)
+ if perform_validation && valid? || !perform_validation
+ save_without_validation
+ else
+ false
+ end
end
- end
- # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
- # if the record is not valid.
- def save_with_validation!
- if valid?
- save_without_validation!
- else
- raise RecordInvalid.new(self)
+ # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
+ # if the record is not valid.
+ def save_with_validation!
+ if valid?
+ save_without_validation!
+ else
+ raise RecordInvalid.new(self)
+ end
end
- end
- # Runs +validate+ and +validate_on_create+ or +validate_on_update+ and returns true if no errors were added otherwise false.
- def valid?
- errors.clear
+ # Runs all the specified validations and returns true if no errors were added otherwise false.
+ def valid?
+ errors.clear
- run_callbacks(:validate)
- validate
+ run_callbacks(:validate)
- if new_record?
- run_callbacks(:validate_on_create)
- validate_on_create
- else
- run_callbacks(:validate_on_update)
- validate_on_update
- end
+ if respond_to?(:validate)
+ ActiveSupport::Deprecation.warn("Base#validate has been deprecated, please use Base.validate :method instead")
+ validate
+ end
- errors.empty?
- end
+ if new_record?
+ run_callbacks(:validate_on_create)
- # Performs the opposite of <tt>valid?</tt>. Returns true if errors were added, false otherwise.
- def invalid?
- !valid?
- end
+ if respond_to?(:validate_on_create)
+ ActiveSupport::Deprecation.warn("Base#validate_on_create has been deprecated, please use Base.validate_on_create :method instead")
+ validate_on_create
+ end
+ else
+ run_callbacks(:validate_on_update)
- # Returns the Errors object that holds all information about attribute error messages.
- def errors
- @errors ||= Errors.new(self)
- end
+ if respond_to?(:validate_on_update)
+ ActiveSupport::Deprecation.warn("Base#validate_on_update has been deprecated, please use Base.validate_on_update :method instead")
+ validate_on_update
+ end
+ end
- protected
- # Overwrite this method for validation checks on all saves and use <tt>Errors.add(field, msg)</tt> for invalid attributes.
- def validate
+ errors.empty?
end
- # Overwrite this method for validation checks used only on creation.
- def validate_on_create
+ # Returns the Errors object that holds all information about attribute error messages.
+ def errors
+ @errors ||= Errors.new(self)
end
- # Overwrite this method for validation checks used only on updates.
- def validate_on_update
+ def get_attribute_value(attribute)
+ respond_to?(attribute.to_sym) ? send(attribute.to_sym) : self[attribute.to_sym]
end
+ end
end
end
+
+Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
+ filename = File.basename(path)
+ require "active_record/validations/#{filename}"
+end
diff --git a/activemodel/lib/active_model/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index b2d78af580..92f47d770f 100644
--- a/activemodel/lib/active_model/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -1,4 +1,4 @@
-module ActiveModel
+module ActiveRecord
module Validations
module ClassMethods
# Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
@@ -18,14 +18,14 @@ module ActiveModel
# validates_associated :book
# end
#
- # ...this would specify a circular dependency and cause infinite recursion.
+ # this would specify a circular dependency and cause infinite recursion.
#
# NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association
# is both present and guaranteed to be valid, you also need to use +validates_presence_of+.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is invalid")
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
+ # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
@@ -33,12 +33,12 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_associated(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save }
- configuration.update(attr_names.extract_options!)
+ configuration = attr_names.extract_options!
validates_each(attr_names, configuration) do |record, attr_name, value|
- record.errors.add(attr_name, configuration[:message]) unless
- (value.is_a?(Array) ? value : [value]).inject(true) { |v, r| (r.nil? || r.valid?) && v }
+ unless (value.is_a?(Array) ? value : [value]).collect { |r| r.nil? || r.valid? }.all?
+ record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
new file mode 100644
index 0000000000..edec4e9e43
--- /dev/null
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -0,0 +1,159 @@
+module ActiveRecord
+ module Validations
+ module ClassMethods
+ # Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
+ # can be named "davidhh".
+ #
+ # class Person < ActiveRecord::Base
+ # validates_uniqueness_of :user_name, :scope => :account_id
+ # end
+ #
+ # It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example,
+ # making sure that a teacher can only be on the schedule once per semester for a particular class.
+ #
+ # class TeacherSchedule < ActiveRecord::Base
+ # validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
+ # end
+ #
+ # When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified
+ # attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
+ #
+ # Configuration options:
+ # * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
+ # * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
+ # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
+ # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
+ # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
+ # method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # method, proc or string should return or evaluate to a true or false value.
+ #
+ # === Concurrency and integrity
+ #
+ # Using this validation method in conjunction with ActiveRecord::Base#save
+ # does not guarantee the absence of duplicate record insertions, because
+ # uniqueness checks on the application level are inherently prone to race
+ # conditions. For example, suppose that two users try to post a Comment at
+ # the same time, and a Comment's title must be unique. At the database-level,
+ # the actions performed by these users could be interleaved in the following manner:
+ #
+ # User 1 | User 2
+ # ------------------------------------+--------------------------------------
+ # # User 1 checks whether there's |
+ # # already a comment with the title |
+ # # 'My Post'. This is not the case. |
+ # SELECT * FROM comments |
+ # WHERE title = 'My Post' |
+ # |
+ # | # User 2 does the same thing and also
+ # | # infers that his title is unique.
+ # | SELECT * FROM comments
+ # | WHERE title = 'My Post'
+ # |
+ # # User 1 inserts his comment. |
+ # INSERT INTO comments |
+ # (title, content) VALUES |
+ # ('My Post', 'hi!') |
+ # |
+ # | # User 2 does the same thing.
+ # | INSERT INTO comments
+ # | (title, content) VALUES
+ # | ('My Post', 'hello!')
+ # |
+ # | # ^^^^^^
+ # | # Boom! We now have a duplicate
+ # | # title!
+ #
+ # This could even happen if you use transactions with the 'serializable'
+ # isolation level. There are several ways to get around this problem:
+ # - By locking the database table before validating, and unlocking it after
+ # saving. However, table locking is very expensive, and thus not
+ # recommended.
+ # - By locking a lock file before validating, and unlocking it after saving.
+ # This does not work if you've scaled your Rails application across
+ # multiple web servers (because they cannot share lock files, or cannot
+ # do that efficiently), and thus not recommended.
+ # - Creating a unique index on the field, by using
+ # ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
+ # rare case that a race condition occurs, the database will guarantee
+ # the field's uniqueness.
+ #
+ # When the database catches such a duplicate insertion,
+ # ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
+ # exception. You can either choose to let this error propagate (which
+ # will result in the default Rails exception page being shown), or you
+ # can catch it and restart the transaction (e.g. by telling the user
+ # that the title already exists, and asking him to re-enter the title).
+ # This technique is also known as optimistic concurrency control:
+ # http://en.wikipedia.org/wiki/Optimistic_concurrency_control
+ #
+ # Active Record currently provides no way to distinguish unique
+ # index constraint errors from other types of database errors, so you
+ # will have to parse the (database-specific) exception message to detect
+ # such a case.
+ def validates_uniqueness_of(*attr_names)
+ configuration = { :case_sensitive => true }
+ configuration.update(attr_names.extract_options!)
+
+ validates_each(attr_names,configuration) do |record, attr_name, value|
+ # The check for an existing value should be run from a class that
+ # isn't abstract. This means working down from the current class
+ # (self), to the first non-abstract class. Since classes don't know
+ # their subclasses, we have to build the hierarchy between self and
+ # the record's class.
+ class_hierarchy = [record.class]
+ while class_hierarchy.first != self
+ class_hierarchy.insert(0, class_hierarchy.first.superclass)
+ end
+
+ # Now we can work our way down the tree to the first non-abstract
+ # class (which has a database table to query from).
+ finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }
+
+ column = finder_class.columns_hash[attr_name.to_s]
+
+ if value.nil?
+ comparison_operator = "IS ?"
+ elsif column.text?
+ comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
+ value = column.limit ? value.to_s[0, column.limit] : value.to_s
+ else
+ comparison_operator = "= ?"
+ end
+
+ sql_attribute = "#{record.class.quoted_table_name}.#{connection.quote_column_name(attr_name)}"
+
+ if value.nil? || (configuration[:case_sensitive] || !column.text?)
+ condition_sql = "#{sql_attribute} #{comparison_operator}"
+ condition_params = [value]
+ else
+ condition_sql = "LOWER(#{sql_attribute}) #{comparison_operator}"
+ condition_params = [value.mb_chars.downcase]
+ end
+
+ if scope = configuration[:scope]
+ Array(scope).map do |scope_item|
+ scope_value = record.send(scope_item)
+ condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{scope_item}", scope_value)
+ condition_params << scope_value
+ end
+ end
+
+ unless record.new_record?
+ condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?"
+ condition_params << record.send(:id)
+ end
+
+ finder_class.with_exclusive_scope do
+ if finder_class.exists?([condition_sql, *condition_params])
+ record.errors.add(attr_name, :taken, :default => configuration[:message], :value => value)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
index cb7fe9698b..f313a75233 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -7,7 +7,7 @@ require 'models/categorization'
require 'active_support/core_ext/array/random_access'
module Remembered
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
included do
after_create :remember
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 1ddb3f49bf..7140de77ea 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -183,7 +183,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
account = firm.build_account
assert !account.save
- assert_equal "can't be empty", account.errors.on("credit_limit")
+ assert_equal ["can't be empty"], account.errors["credit_limit"]
end
def test_build_association_twice_without_saving_affects_nothing
@@ -219,7 +219,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal account, firm.account
assert !account.save
assert_equal account, firm.account
- assert_equal "can't be empty", account.errors.on("credit_limit")
+ assert_equal ["can't be empty"], account.errors["credit_limit"]
end
def test_create
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index f95b2c0079..ddca5e962d 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -61,7 +61,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
assert !firm.account.valid?
assert !firm.valid?
assert !firm.save
- assert_equal "is invalid", firm.errors.on("account")
+ assert_equal ["is invalid"], firm.errors["account"]
end
def test_save_succeeds_for_invalid_has_one_with_validate_false
@@ -160,7 +160,7 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
assert !log.developer.valid?
assert !log.valid?
assert !log.save
- assert_equal "is invalid", log.errors.on("developer")
+ assert_equal ["is invalid"], log.errors["developer"]
end
def test_save_succeeds_for_invalid_belongs_to_with_validate_false
@@ -650,16 +650,16 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_automatically_validate_the_associated_model
@pirate.ship.name = ''
- assert !@pirate.valid?
- assert !@pirate.errors.on(:ship_name).blank?
+ assert @pirate.invalid?
+ assert @pirate.errors[:ship_name].any?
end
def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
@pirate.ship.name = nil
@pirate.catchphrase = nil
- assert !@pirate.valid?
- assert !@pirate.errors.on(:ship_name).blank?
- assert !@pirate.errors.on(:catchphrase).blank?
+ assert @pirate.invalid?
+ assert @pirate.errors[:ship_name].any?
+ assert @pirate.errors[:catchphrase].any?
end
def test_should_still_allow_to_bypass_validations_on_the_associated_model
@@ -740,16 +740,16 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_automatically_validate_the_associated_model
@ship.pirate.catchphrase = ''
- assert !@ship.valid?
- assert !@ship.errors.on(:pirate_catchphrase).blank?
+ assert @ship.invalid?
+ assert @ship.errors[:pirate_catchphrase].any?
end
def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_is_not_valid
@ship.name = nil
@ship.pirate.catchphrase = nil
- assert !@ship.valid?
- assert !@ship.errors.on(:name).blank?
- assert !@ship.errors.on(:pirate_catchphrase).blank?
+ assert @ship.invalid?
+ assert @ship.errors[:name].any?
+ assert @ship.errors[:pirate_catchphrase].any?
end
def test_should_still_allow_to_bypass_validations_on_the_associated_model
@@ -811,16 +811,16 @@ module AutosaveAssociationOnACollectionAssociationTests
@pirate.send(@association_name).each { |child| child.name = '' }
assert !@pirate.valid?
- assert_equal "can't be blank", @pirate.errors.on("#{@association_name}_name")
- assert @pirate.errors.on(@association_name).blank?
+ assert_equal ["can't be blank"], @pirate.errors["#{@association_name}_name"]
+ assert @pirate.errors[@association_name].empty?
end
def test_should_not_use_default_invalid_error_on_associated_models
@pirate.send(@association_name).build(:name => '')
assert !@pirate.valid?
- assert_equal "can't be blank", @pirate.errors.on("#{@association_name}_name")
- assert @pirate.errors.on(@association_name).blank?
+ assert_equal ["can't be blank"], @pirate.errors["#{@association_name}_name"]
+ assert @pirate.errors[@association_name].empty?
end
def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
@@ -828,8 +828,8 @@ module AutosaveAssociationOnACollectionAssociationTests
@pirate.catchphrase = nil
assert !@pirate.valid?
- assert_equal "can't be blank", @pirate.errors.on("#{@association_name}_name")
- assert !@pirate.errors.on(:catchphrase).blank?
+ assert_equal ["can't be blank"], @pirate.errors["#{@association_name}_name"]
+ assert @pirate.errors[:catchphrase].any?
end
def test_should_allow_to_bypass_validations_on_the_associated_models_on_update
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index f82784836e..c30186286d 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -63,7 +63,7 @@ end
class ActiveSupport::TestCase
include ActiveRecord::TestFixtures
- include ActiveRecord::Testing::RepairHelper
+ include ActiveModel::ValidationsRepairHelper
self.fixture_path = FIXTURES_ROOT
self.use_instantiated_fixtures = false
diff --git a/activerecord/test/cases/repair_helper.rb b/activerecord/test/cases/repair_helper.rb
index 686bfee46d..80d04010d6 100644
--- a/activerecord/test/cases/repair_helper.rb
+++ b/activerecord/test/cases/repair_helper.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module Testing
module RepairHelper
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
module Toolbox
def self.record_validations(*model_classes)
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index f9ad7f3ba3..4f02be3c06 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -190,4 +190,9 @@ class SchemaDumperTest < ActiveRecord::TestCase
output = stream.string
assert_match %r{:precision => 3,[[:space:]]+:scale => 2,[[:space:]]+:default => 2.78}, output
end
+
+ def test_schema_dump_keeps_large_precision_integer_columns_as_decimal
+ output = standard_dump
+ assert_match %r{t.decimal\s+"atoms_in_universe",\s+:precision => 55,\s+:scale => 0}, output
+ end
end
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
new file mode 100644
index 0000000000..b1203c12ed
--- /dev/null
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -0,0 +1,101 @@
+# encoding: utf-8
+require "cases/helper"
+require 'models/topic'
+require 'models/reply'
+require 'models/owner'
+
+class AssociationValidationTest < ActiveRecord::TestCase
+ fixtures :topics, :owners
+
+ repair_validations(Topic)
+
+ def test_validates_size_of_association
+ 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[:pets].any?
+ pet = o.pets.build('name' => 'apet')
+ assert o.valid?
+ end
+ end
+
+ def test_validates_size_of_association_using_within
+ 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[:pets].any?
+
+ pet = o.pets.build('name' => 'apet')
+ assert o.valid?
+
+ 2.times { o.pets.build('name' => 'apet') }
+ assert !o.save
+ assert o.errors[:pets].any?
+ end
+ end
+
+ def test_validates_associated_many
+ Topic.validates_associated( :replies )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ t.replies << [r = Reply.new("title" => "A reply"), r2 = Reply.new("title" => "Another reply", "content" => "non-empty"), r3 = Reply.new("title" => "Yet another reply"), r4 = Reply.new("title" => "The last reply", "content" => "non-empty")]
+ assert !t.valid?
+ assert t.errors[:replies].any?
+ assert_equal 1, r.errors.count # make sure all associated objects have been validated
+ assert_equal 0, r2.errors.count
+ assert_equal 1, r3.errors.count
+ assert_equal 0, r4.errors.count
+ r.content = r3.content = "non-empty"
+ assert t.valid?
+ end
+
+ def test_validates_associated_one
+ 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[:topic].any?
+ r.topic.content = "non-empty"
+ assert r.valid?
+ end
+ end
+
+ def test_validates_associated_with_custom_message_using_quotes
+ 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[:topic]
+ end
+ end
+
+ def test_validates_associated_missing
+ repair_validations(Reply) do
+ Reply.validates_presence_of(:topic)
+ r = Reply.create("title" => "A reply", "content" => "with content!")
+ assert !r.valid?
+ assert r.errors[:topic].any?
+
+ r.topic = Topic.find :first
+ assert r.valid?
+ end
+ end
+
+ def test_validates_size_of_association_utf8
+ 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[:pets].any?
+ o.pets.build('name' => 'あいうえおかきくけこ')
+ assert o.valid?
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
new file mode 100644
index 0000000000..29c10de4fe
--- /dev/null
+++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -0,0 +1,166 @@
+require "cases/helper"
+require 'models/topic'
+require 'models/reply'
+
+class I18nGenerateMessageValidationTest < Test::Unit::TestCase
+ def setup
+ reset_callbacks Topic
+ @topic = Topic.new
+ I18n.backend.store_translations :'en', {
+ :activerecord => {
+ :errors => {
+ :messages => {
+ :taken => "has already been taken",
+ }
+ }
+ }
+ }
+ end
+
+ def reset_callbacks(*models)
+ models.each do |model|
+ model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ model.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ end
+ end
+
+ # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
+ def test_generate_message_inclusion_with_default_message
+ assert_equal 'is not included in the list', @topic.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_inclusion_with_custom_message
+ assert_equal 'custom message title', @topic.errors.generate_message(:title, :inclusion, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value)
+ def test_generate_message_exclusion_with_default_message
+ assert_equal 'is reserved', @topic.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_exclusion_with_custom_message
+ assert_equal 'custom message title', @topic.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
+ # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
+ def test_generate_message_invalid_with_default_message
+ assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_invalid_with_custom_message
+ assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message])
+ def test_generate_message_confirmation_with_default_message
+ assert_equal "doesn't match confirmation", @topic.errors.generate_message(:title, :confirmation, :default => nil)
+ end
+
+ def test_generate_message_confirmation_with_custom_message
+ assert_equal 'custom message', @topic.errors.generate_message(:title, :confirmation, :default => 'custom message')
+ end
+
+ # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message])
+ def test_generate_message_accepted_with_default_message
+ assert_equal "must be accepted", @topic.errors.generate_message(:title, :accepted, :default => nil)
+ end
+
+ def test_generate_message_accepted_with_custom_message
+ assert_equal 'custom message', @topic.errors.generate_message(:title, :accepted, :default => 'custom message')
+ end
+
+ # add_on_empty: generate_message(attr, :empty, :default => custom_message)
+ def test_generate_message_empty_with_default_message
+ assert_equal "can't be empty", @topic.errors.generate_message(:title, :empty, :default => nil)
+ end
+
+ def test_generate_message_empty_with_custom_message
+ assert_equal 'custom message', @topic.errors.generate_message(:title, :empty, :default => 'custom message')
+ end
+
+ # add_on_blank: generate_message(attr, :blank, :default => custom_message)
+ def test_generate_message_blank_with_default_message
+ assert_equal "can't be blank", @topic.errors.generate_message(:title, :blank, :default => nil)
+ end
+
+ def test_generate_message_blank_with_custom_message
+ assert_equal 'custom message', @topic.errors.generate_message(:title, :blank, :default => 'custom message')
+ end
+
+ # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end)
+ def test_generate_message_too_long_with_default_message
+ assert_equal "is too long (maximum is 10 characters)", @topic.errors.generate_message(:title, :too_long, :default => nil, :count => 10)
+ end
+
+ def test_generate_message_too_long_with_custom_message
+ assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_long, :default => 'custom message {{count}}', :count => 10)
+ end
+
+ # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
+ def test_generate_message_too_short_with_default_message
+ assert_equal "is too short (minimum is 10 characters)", @topic.errors.generate_message(:title, :too_short, :default => nil, :count => 10)
+ end
+
+ def test_generate_message_too_short_with_custom_message
+ assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_short, :default => 'custom message {{count}}', :count => 10)
+ end
+
+ # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value)
+ def test_generate_message_wrong_length_with_default_message
+ assert_equal "is the wrong length (should be 10 characters)", @topic.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10)
+ end
+
+ def test_generate_message_wrong_length_with_custom_message
+ assert_equal 'custom message 10', @topic.errors.generate_message(:title, :wrong_length, :default => 'custom message {{count}}', :count => 10)
+ end
+
+ # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
+ def test_generate_message_not_a_number_with_default_message
+ assert_equal "is not a number", @topic.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_not_a_number_with_custom_message
+ assert_equal 'custom message title', @topic.errors.generate_message(:title, :not_a_number, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message])
+ def test_generate_message_greater_than_with_default_message
+ assert_equal "must be greater than 10", @topic.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_greater_than_or_equal_to_with_default_message
+ assert_equal "must be greater than or equal to 10", @topic.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_equal_to_with_default_message
+ assert_equal "must be equal to 10", @topic.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_less_than_with_default_message
+ assert_equal "must be less than 10", @topic.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_less_than_or_equal_to_with_default_message
+ assert_equal "must be less than or equal to 10", @topic.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_odd_with_default_message
+ assert_equal "must be odd", @topic.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_even_with_default_message
+ assert_equal "must be even", @topic.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10)
+ end
+
+ # validates_uniqueness_of: generate_message(attr_name, :taken, :default => configuration[:message])
+ def test_generate_message_taken_with_default_message
+ assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_taken_with_custom_message
+ assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+end
diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index 20d1369a64..0303147e50 100644
--- a/activerecord/test/cases/validations_i18n_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -2,7 +2,7 @@ require "cases/helper"
require 'models/topic'
require 'models/reply'
-class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
+class I18nValidationTest < ActiveRecord::TestCase
def setup
reset_callbacks Topic
@topic = Topic.new
@@ -54,7 +54,6 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
# ActiveRecord::Errors
def test_errors_generate_message_translates_custom_model_attribute_key
-
I18n.expects(:translate).with(
:topic,
{ :count => 1,
@@ -122,6 +121,55 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
end
+ # validates_uniqueness_of w/ mocha
+
+ def test_validates_uniqueness_of_generates_message
+ Topic.validates_uniqueness_of :title
+ @topic.title = unique_topic.title
+ @topic.errors.expects(:generate_message).with(:title, :taken, {:default => nil, :value => 'unique!'})
+ @topic.valid?
+ end
+
+ def test_validates_uniqueness_of_generates_message_with_custom_default_message
+ Topic.validates_uniqueness_of :title, :message => 'custom'
+ @topic.title = unique_topic.title
+ @topic.errors.expects(:generate_message).with(:title, :taken, {:default => 'custom', :value => 'unique!'})
+ @topic.valid?
+ end
+
+ # validates_associated w/ mocha
+
+ def test_validates_associated_generates_message
+ Topic.validates_associated :replies
+ replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil})
+ replied_topic.valid?
+ end
+
+ def test_validates_associated_generates_message_with_custom_default_message
+ Topic.validates_associated :replies
+ replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil})
+ replied_topic.valid?
+ end
+
+ # validates_associated w/o mocha
+
+ def test_validates_associated_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
+
+ Topic.validates_associated :replies
+ replied_topic.valid?
+ assert_equal ['custom message'], replied_topic.errors[:replies]
+ end
+
+ def test_validates_associated_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
+
+ Topic.validates_associated :replies
+ replied_topic.valid?
+ assert_equal ['global message'], replied_topic.errors[:replies]
+ end
+
def test_errors_add_on_empty_generates_message
@topic.errors.expects(:generate_message).with(:title, :empty, {:default => nil})
@topic.errors.add_on_empty :title
@@ -143,7 +191,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
end
def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
- @topic.errors.instance_variable_set :@errors, { 'title' => ['empty'] }
+ @topic.errors.add('title', 'empty')
I18n.expects(:translate).with(:"topic.title", :default => ['Title'], :scope => [:activerecord, :attributes], :count => 1).returns('Title')
@topic.errors.full_messages :locale => 'en'
end
@@ -260,22 +308,6 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
@topic.valid?
end
- # validates_uniqueness_of w/ mocha
-
- def test_validates_uniqueness_of_generates_message
- Topic.validates_uniqueness_of :title
- @topic.title = unique_topic.title
- @topic.errors.expects(:generate_message).with(:title, :taken, {:default => nil, :value => 'unique!'})
- @topic.valid?
- end
-
- def test_validates_uniqueness_of_generates_message_with_custom_default_message
- Topic.validates_uniqueness_of :title, :message => 'custom'
- @topic.title = unique_topic.title
- @topic.errors.expects(:generate_message).with(:title, :taken, {:default => 'custom', :value => 'unique!'})
- @topic.valid?
- end
-
# validates_format_of w/ mocha
def test_validates_format_of_generates_message
@@ -388,20 +420,6 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
@topic.valid?
end
- # validates_associated w/ mocha
-
- def test_validates_associated_generates_message
- Topic.validates_associated :replies
- replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil})
- replied_topic.valid?
- end
-
- def test_validates_associated_generates_message_with_custom_default_message
- Topic.validates_associated :replies
- replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil})
- replied_topic.valid?
- end
-
# validates_confirmation_of w/o mocha
def test_validates_confirmation_of_finds_custom_model_key_translation
@@ -411,7 +429,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_confirmation_of :title
@topic.title_confirmation = 'foo'
@topic.valid?
- assert_equal 'custom message', @topic.errors.on(:title)
+ assert_equal ['custom message'], @topic.errors[:title]
end
def test_validates_confirmation_of_finds_global_default_translation
@@ -420,7 +438,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_confirmation_of :title
@topic.title_confirmation = 'foo'
@topic.valid?
- assert_equal 'global message', @topic.errors.on(:title)
+ assert_equal ['global message'], @topic.errors[:title]
end
# validates_acceptance_of w/o mocha
@@ -431,7 +449,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_acceptance_of :title, :allow_nil => false
@topic.valid?
- assert_equal 'custom message', @topic.errors.on(:title)
+ assert_equal ['custom message'], @topic.errors[:title]
end
def test_validates_acceptance_of_finds_global_default_translation
@@ -439,7 +457,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_acceptance_of :title, :allow_nil => false
@topic.valid?
- assert_equal 'global message', @topic.errors.on(:title)
+ assert_equal ['global message'], @topic.errors[:title]
end
# validates_presence_of w/o mocha
@@ -450,7 +468,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_presence_of :title
@topic.valid?
- assert_equal 'custom message', @topic.errors.on(:title)
+ assert_equal ['custom message'], @topic.errors[:title]
end
def test_validates_presence_of_finds_global_default_translation
@@ -458,7 +476,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_presence_of :title
@topic.valid?
- assert_equal 'global message', @topic.errors.on(:title)
+ assert_equal ['global message'], @topic.errors[:title]
end
# validates_length_of :within w/o mocha
@@ -469,7 +487,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_length_of :title, :within => 3..5
@topic.valid?
- assert_equal 'custom message', @topic.errors.on(:title)
+ assert_equal ['custom message'], @topic.errors[:title]
end
def test_validates_length_of_within_finds_global_default_translation
@@ -477,7 +495,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_length_of :title, :within => 3..5
@topic.valid?
- assert_equal 'global message', @topic.errors.on(:title)
+ assert_equal ['global message'], @topic.errors[:title]
end
# validates_length_of :is w/o mocha
@@ -488,7 +506,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_length_of :title, :is => 5
@topic.valid?
- assert_equal 'custom message', @topic.errors.on(:title)
+ assert_equal ['custom message'], @topic.errors[:title]
end
def test_validates_length_of_is_finds_global_default_translation
@@ -496,18 +514,16 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_length_of :title, :is => 5
@topic.valid?
- assert_equal 'global message', @topic.errors.on(:title)
+ assert_equal ['global message'], @topic.errors[:title]
end
- # validates_uniqueness_of w/o mocha
-
def test_validates_length_of_is_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
- assert_equal 'custom message', @topic.errors.on(:title)
+ assert_equal ['custom message'], @topic.errors[:title]
end
def test_validates_length_of_is_finds_global_default_translation
@@ -515,7 +531,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_length_of :title, :is => 5
@topic.valid?
- assert_equal 'global message', @topic.errors.on(:title)
+ assert_equal ['global message'], @topic.errors[:title]
end
@@ -527,7 +543,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
@topic.valid?
- assert_equal 'custom message', @topic.errors.on(:title)
+ assert_equal ['custom message'], @topic.errors[:title]
end
def test_validates_format_of_finds_global_default_translation
@@ -535,7 +551,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
@topic.valid?
- assert_equal 'global message', @topic.errors.on(:title)
+ assert_equal ['global message'], @topic.errors[:title]
end
# validates_inclusion_of w/o mocha
@@ -546,7 +562,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_inclusion_of :title, :in => %w(a b c)
@topic.valid?
- assert_equal 'custom message', @topic.errors.on(:title)
+ assert_equal ['custom message'], @topic.errors[:title]
end
def test_validates_inclusion_of_finds_global_default_translation
@@ -554,7 +570,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_inclusion_of :title, :in => %w(a b c)
@topic.valid?
- assert_equal 'global message', @topic.errors.on(:title)
+ assert_equal ['global message'], @topic.errors[:title]
end
# validates_exclusion_of w/o mocha
@@ -566,7 +582,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_exclusion_of :title, :in => %w(a b c)
@topic.title = 'a'
@topic.valid?
- assert_equal 'custom message', @topic.errors.on(:title)
+ assert_equal ['custom message'], @topic.errors[:title]
end
def test_validates_exclusion_of_finds_global_default_translation
@@ -575,7 +591,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_exclusion_of :title, :in => %w(a b c)
@topic.title = 'a'
@topic.valid?
- assert_equal 'global message', @topic.errors.on(:title)
+ assert_equal ['global message'], @topic.errors[:title]
end
# validates_numericality_of without :only_integer w/o mocha
@@ -587,7 +603,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_numericality_of :title
@topic.title = 'a'
@topic.valid?
- assert_equal 'custom message', @topic.errors.on(:title)
+ assert_equal ['custom message'], @topic.errors[:title]
end
def test_validates_numericality_of_finds_global_default_translation
@@ -596,7 +612,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@topic.valid?
- assert_equal 'global message', @topic.errors.on(:title)
+ assert_equal ['global message'], @topic.errors[:title]
end
# validates_numericality_of with :only_integer w/o mocha
@@ -608,7 +624,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@topic.valid?
- assert_equal 'custom message', @topic.errors.on(:title)
+ assert_equal ['custom message'], @topic.errors[:title]
end
def test_validates_numericality_of_only_integer_finds_global_default_translation
@@ -617,7 +633,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@topic.valid?
- assert_equal 'global message', @topic.errors.on(:title)
+ assert_equal ['global message'], @topic.errors[:title]
end
# validates_numericality_of :odd w/o mocha
@@ -629,7 +645,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_numericality_of :title, :only_integer => true, :odd => true
@topic.title = 0
@topic.valid?
- assert_equal 'custom message', @topic.errors.on(:title)
+ assert_equal ['custom message'], @topic.errors[:title]
end
def test_validates_numericality_of_odd_finds_global_default_translation
@@ -638,7 +654,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_numericality_of :title, :only_integer => true, :odd => true
@topic.title = 0
@topic.valid?
- assert_equal 'global message', @topic.errors.on(:title)
+ assert_equal ['global message'], @topic.errors[:title]
end
# validates_numericality_of :less_than w/o mocha
@@ -650,7 +666,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
@topic.title = 1
@topic.valid?
- assert_equal 'custom message', @topic.errors.on(:title)
+ assert_equal ['custom message'], @topic.errors[:title]
end
def test_validates_numericality_of_less_than_finds_global_default_translation
@@ -659,27 +675,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
@topic.title = 1
@topic.valid?
- assert_equal 'global message', @topic.errors.on(:title)
- end
-
-
- # validates_associated w/o mocha
-
- def test_validates_associated_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
-
- Topic.validates_associated :replies
- replied_topic.valid?
- assert_equal 'custom message', replied_topic.errors.on(:replies)
- end
-
- def test_validates_associated_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
-
- Topic.validates_associated :replies
- replied_topic.valid?
- assert_equal 'global message', replied_topic.errors.on(:replies)
+ assert_equal ['global message'], @topic.errors[:title]
end
def test_validations_with_message_symbol_must_translate
@@ -687,7 +683,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_presence_of :title, :message => :custom_error
@topic.title = nil
@topic.valid?
- assert_equal "I am a custom error", @topic.errors.on(:title)
+ assert_equal ["I am a custom error"], @topic.errors[:title]
end
def test_validates_with_message_symbol_must_translate_per_attribute
@@ -695,7 +691,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_presence_of :title, :message => :custom_error
@topic.title = nil
@topic.valid?
- assert_equal "I am a custom error", @topic.errors.on(:title)
+ assert_equal ["I am a custom error"], @topic.errors[:title]
end
def test_validates_with_message_symbol_must_translate_per_model
@@ -703,195 +699,13 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
Topic.validates_presence_of :title, :message => :custom_error
@topic.title = nil
@topic.valid?
- assert_equal "I am a custom error", @topic.errors.on(:title)
+ assert_equal ["I am a custom error"], @topic.errors[:title]
end
def test_validates_with_message_string
Topic.validates_presence_of :title, :message => "I am a custom error"
@topic.title = nil
@topic.valid?
- assert_equal "I am a custom error", @topic.errors.on(:title)
- end
-
-end
-
-class ActiveRecordValidationsGenerateMessageI18nTests < Test::Unit::TestCase
- def setup
- reset_callbacks Topic
- @topic = Topic.new
- I18n.backend.store_translations :'en', {
- :activerecord => {
- :errors => {
- :messages => {
- :inclusion => "is not included in the list",
- :exclusion => "is reserved",
- :invalid => "is invalid",
- :confirmation => "doesn't match confirmation",
- :accepted => "must be accepted",
- :empty => "can't be empty",
- :blank => "can't be blank",
- :too_long => "is too long (maximum is {{count}} characters)",
- :too_short => "is too short (minimum is {{count}} characters)",
- :wrong_length => "is the wrong length (should be {{count}} characters)",
- :taken => "has already been taken",
- :not_a_number => "is not a number",
- :greater_than => "must be greater than {{count}}",
- :greater_than_or_equal_to => "must be greater than or equal to {{count}}",
- :equal_to => "must be equal to {{count}}",
- :less_than => "must be less than {{count}}",
- :less_than_or_equal_to => "must be less than or equal to {{count}}",
- :odd => "must be odd",
- :even => "must be even"
- }
- }
- }
- }
- end
-
- def reset_callbacks(*models)
- models.each do |model|
- model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
- model.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
- model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
- end
- end
-
- # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
- def test_generate_message_inclusion_with_default_message
- assert_equal 'is not included in the list', @topic.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title')
- end
-
- def test_generate_message_inclusion_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :inclusion, :default => 'custom message {{value}}', :value => 'title')
- end
-
- # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value)
- def test_generate_message_exclusion_with_default_message
- assert_equal 'is reserved', @topic.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title')
- end
-
- def test_generate_message_exclusion_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title')
- end
-
- # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
- # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
- def test_generate_message_invalid_with_default_message
- assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :default => nil, :value => 'title')
- end
-
- def test_generate_message_invalid_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :default => 'custom message {{value}}', :value => 'title')
- end
-
- # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message])
- def test_generate_message_confirmation_with_default_message
- assert_equal "doesn't match confirmation", @topic.errors.generate_message(:title, :confirmation, :default => nil)
- end
-
- def test_generate_message_confirmation_with_custom_message
- assert_equal 'custom message', @topic.errors.generate_message(:title, :confirmation, :default => 'custom message')
- end
-
- # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message])
- def test_generate_message_accepted_with_default_message
- assert_equal "must be accepted", @topic.errors.generate_message(:title, :accepted, :default => nil)
- end
-
- def test_generate_message_accepted_with_custom_message
- assert_equal 'custom message', @topic.errors.generate_message(:title, :accepted, :default => 'custom message')
- end
-
- # add_on_empty: generate_message(attr, :empty, :default => custom_message)
- def test_generate_message_empty_with_default_message
- assert_equal "can't be empty", @topic.errors.generate_message(:title, :empty, :default => nil)
- end
-
- def test_generate_message_empty_with_custom_message
- assert_equal 'custom message', @topic.errors.generate_message(:title, :empty, :default => 'custom message')
- end
-
- # add_on_blank: generate_message(attr, :blank, :default => custom_message)
- def test_generate_message_blank_with_default_message
- assert_equal "can't be blank", @topic.errors.generate_message(:title, :blank, :default => nil)
- end
-
- def test_generate_message_blank_with_custom_message
- assert_equal 'custom message', @topic.errors.generate_message(:title, :blank, :default => 'custom message')
- end
-
- # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end)
- def test_generate_message_too_long_with_default_message
- assert_equal "is too long (maximum is 10 characters)", @topic.errors.generate_message(:title, :too_long, :default => nil, :count => 10)
- end
-
- def test_generate_message_too_long_with_custom_message
- assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_long, :default => 'custom message {{count}}', :count => 10)
- end
-
- # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
- def test_generate_message_too_short_with_default_message
- assert_equal "is too short (minimum is 10 characters)", @topic.errors.generate_message(:title, :too_short, :default => nil, :count => 10)
- end
-
- def test_generate_message_too_short_with_custom_message
- assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_short, :default => 'custom message {{count}}', :count => 10)
- end
-
- # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value)
- def test_generate_message_wrong_length_with_default_message
- assert_equal "is the wrong length (should be 10 characters)", @topic.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10)
- end
-
- def test_generate_message_wrong_length_with_custom_message
- assert_equal 'custom message 10', @topic.errors.generate_message(:title, :wrong_length, :default => 'custom message {{count}}', :count => 10)
- end
-
- # validates_uniqueness_of: generate_message(attr_name, :taken, :default => configuration[:message])
- def test_generate_message_taken_with_default_message
- assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :default => nil, :value => 'title')
- end
-
- def test_generate_message_taken_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :default => 'custom message {{value}}', :value => 'title')
- end
-
- # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
- def test_generate_message_not_a_number_with_default_message
- assert_equal "is not a number", @topic.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title')
- end
-
- def test_generate_message_not_a_number_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :not_a_number, :default => 'custom message {{value}}', :value => 'title')
- end
-
- # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message])
- def test_generate_message_greater_than_with_default_message
- assert_equal "must be greater than 10", @topic.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_greater_than_or_equal_to_with_default_message
- assert_equal "must be greater than or equal to 10", @topic.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
+ assert_equal ["I am a custom error"], @topic.errors[:title]
end
-
- def test_generate_message_equal_to_with_default_message
- assert_equal "must be equal to 10", @topic.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_less_than_with_default_message
- assert_equal "must be less than 10", @topic.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_less_than_or_equal_to_with_default_message
- assert_equal "must be less than or equal to 10", @topic.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_odd_with_default_message
- assert_equal "must be odd", @topic.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10)
- end
-
- def test_generate_message_even_with_default_message
- assert_equal "must be even", @topic.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10)
- end
-
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
new file mode 100644
index 0000000000..961db51d1d
--- /dev/null
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -0,0 +1,278 @@
+# encoding: utf-8
+require "cases/helper"
+require 'models/topic'
+require 'models/reply'
+require 'models/warehouse_thing'
+require 'models/guid'
+require 'models/event'
+
+# The following methods in Topic are used in test_conditional_validation_*
+class Topic
+ has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
+ has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
+end
+
+class UniqueReply < Reply
+ validates_uniqueness_of :content, :scope => 'parent_id'
+end
+
+class SillyUniqueReply < UniqueReply
+end
+
+class Wizard < ActiveRecord::Base
+ self.abstract_class = true
+
+ validates_uniqueness_of :name
+end
+
+class IneptWizard < Wizard
+ validates_uniqueness_of :city
+end
+
+class Conjurer < IneptWizard
+end
+
+class Thaumaturgist < IneptWizard
+end
+
+class UniquenessValidationTest < ActiveRecord::TestCase
+ fixtures :topics, 'warehouse-things'
+
+ repair_validations(Topic)
+
+ def test_validate_uniqueness
+ Topic.validates_uniqueness_of(:title)
+
+ t = Topic.new("title" => "I'm unique!")
+ assert t.save, "Should save t as unique"
+
+ t.content = "Remaining unique"
+ assert t.save, "Should still save t as unique"
+
+ t2 = Topic.new("title" => "I'm unique!")
+ assert !t2.valid?, "Shouldn't be valid"
+ assert !t2.save, "Shouldn't save t2 as unique"
+ assert_equal ["has already been taken"], t2.errors[:title]
+
+ t2.title = "Now Im really also unique"
+ assert t2.save, "Should now save t2 as unique"
+ end
+
+ def test_validates_uniquness_with_newline_chars
+ Topic.validates_uniqueness_of(:title, :case_sensitive => false)
+
+ t = Topic.new("title" => "new\nline")
+ assert t.save, "Should save t as unique"
+ end
+
+ def test_validate_uniqueness_with_scope
+ repair_validations(Reply) do
+ Reply.validates_uniqueness_of(:content, :scope => "parent_id")
+
+ t = Topic.create("title" => "I'm unique!")
+
+ 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.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"
+ end
+ end
+
+ def test_validate_uniqueness_scoped_to_defining_class
+ t = Topic.create("title" => "What, me worry?")
+
+ r1 = t.unique_replies.create "title" => "r1", "content" => "a barrel of fun"
+ assert r1.valid?, "Saving r1"
+
+ r2 = t.silly_unique_replies.create "title" => "r2", "content" => "a barrel of fun"
+ assert !r2.valid?, "Saving r2"
+
+ # Should succeed as validates_uniqueness_of only applies to
+ # UniqueReply and its subclasses
+ r3 = t.replies.create "title" => "r2", "content" => "a barrel of fun"
+ assert r3.valid?, "Saving r3"
+ end
+
+ def test_validate_uniqueness_with_scope_array
+ 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!")
+
+ 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.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.author_name = "jj"
+ assert r3.save, "Saving r3 the second time."
+
+ r3.author_name = "jeremy"
+ assert !r3.save, "Saving r3 the third time."
+ end
+ end
+
+ def test_validate_case_insensitive_uniqueness
+ Topic.validates_uniqueness_of(:title, :parent_id, :case_sensitive => false, :allow_nil => true)
+
+ t = Topic.new("title" => "I'm unique!", :parent_id => 2)
+ assert t.save, "Should save t as unique"
+
+ t.content = "Remaining unique"
+ assert t.save, "Should still save t as unique"
+
+ t2 = Topic.new("title" => "I'm UNIQUE!", :parent_id => 1)
+ assert !t2.valid?, "Shouldn't be valid"
+ assert !t2.save, "Shouldn't save t2 as unique"
+ assert t2.errors[:title].any?
+ assert t2.errors[:parent_id].any?
+ assert_equal ["has already been taken"], t2.errors[:title]
+
+ t2.title = "I'm truly UNIQUE!"
+ assert !t2.valid?, "Shouldn't be valid"
+ assert !t2.save, "Shouldn't save t2 as unique"
+ assert t2.errors[:title].empty?
+ assert t2.errors[:parent_id].any?
+
+ t2.parent_id = 4
+ assert t2.save, "Should now save t2 as unique"
+
+ t2.parent_id = nil
+ t2.title = nil
+ assert t2.valid?, "should validate with nil"
+ assert t2.save, "should save with nil"
+
+ with_kcode('UTF8') do
+ t_utf8 = Topic.new("title" => "Я тоже уникальный!")
+ assert t_utf8.save, "Should save t_utf8 as unique"
+
+ # If database hasn't UTF-8 character set, this test fails
+ if Topic.find(t_utf8, :select => 'LOWER(title) AS title').title == "я тоже уникальный!"
+ t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!")
+ assert !t2_utf8.valid?, "Shouldn't be valid"
+ assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique"
+ end
+ end
+ end
+
+ def test_validate_case_sensitive_uniqueness
+ Topic.validates_uniqueness_of(:title, :case_sensitive => true, :allow_nil => true)
+
+ t = Topic.new("title" => "I'm unique!")
+ assert t.save, "Should save t as unique"
+
+ t.content = "Remaining unique"
+ assert t.save, "Should still save t as unique"
+
+ t2 = Topic.new("title" => "I'M UNIQUE!")
+ assert t2.valid?, "Should be valid"
+ assert t2.save, "Should save t2 as unique"
+ assert t2.errors[:title].empty?
+ assert t2.errors[:parent_id].empty?
+ assert_not_equal ["has already been taken"], t2.errors[:title]
+
+ t3 = Topic.new("title" => "I'M uNiQUe!")
+ assert t3.valid?, "Should be valid"
+ assert t3.save, "Should save t2 as unique"
+ assert t3.errors[:title].empty?
+ assert t3.errors[:parent_id].empty?
+ assert_not_equal ["has already been taken"], t3.errors[:title]
+ end
+
+ def test_validate_case_sensitive_uniqueness_with_attribute_passed_as_integer
+ Topic.validates_uniqueness_of(:title, :case_sensitve => true)
+ t = Topic.create!('title' => 101)
+
+ t2 = Topic.new('title' => 101)
+ assert !t2.valid?
+ assert t2.errors[:title]
+ end
+
+ def test_validate_uniqueness_with_non_standard_table_names
+ i1 = WarehouseThing.create(:value => 1000)
+ assert !i1.valid?, "i1 should not be valid"
+ assert i1.errors[:value].any?, "Should not be empty"
+ end
+
+ def test_validates_uniqueness_inside_with_scope
+ Topic.validates_uniqueness_of(:title)
+
+ Topic.with_scope(:find => { :conditions => { :author_name => "David" } }) do
+ t1 = Topic.new("title" => "I'm unique!", "author_name" => "Mary")
+ assert t1.save
+ t2 = Topic.new("title" => "I'm unique!", "author_name" => "David")
+ assert !t2.valid?
+ end
+ end
+
+ def test_validate_uniqueness_with_columns_which_are_sql_keywords
+ 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_uniqueness_with_limit
+ # Event.title is limited to 5 characters
+ e1 = Event.create(:title => "abcde")
+ assert e1.valid?, "Could not create an event with a unique, 5 character title"
+ e2 = Event.create(:title => "abcdefgh")
+ assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique"
+ end
+
+ def test_validate_straight_inheritance_uniqueness
+ w1 = IneptWizard.create(:name => "Rincewind", :city => "Ankh-Morpork")
+ assert w1.valid?, "Saving w1"
+
+ # Should use validation from base class (which is abstract)
+ w2 = IneptWizard.new(:name => "Rincewind", :city => "Quirm")
+ assert !w2.valid?, "w2 shouldn't be valid"
+ assert w2.errors[:name].any?, "Should have errors for name"
+ assert_equal ["has already been taken"], w2.errors[:name], "Should have uniqueness message for name"
+
+ w3 = Conjurer.new(:name => "Rincewind", :city => "Quirm")
+ assert !w3.valid?, "w3 shouldn't be valid"
+ assert w3.errors[:name].any?, "Should have errors for name"
+ assert_equal ["has already been taken"], w3.errors[:name], "Should have uniqueness message for name"
+
+ w4 = Conjurer.create(:name => "The Amazing Bonko", :city => "Quirm")
+ assert w4.valid?, "Saving w4"
+
+ w5 = Thaumaturgist.new(:name => "The Amazing Bonko", :city => "Lancre")
+ assert !w5.valid?, "w5 shouldn't be valid"
+ assert w5.errors[:name].any?, "Should have errors for name"
+ assert_equal ["has already been taken"], w5.errors[:name], "Should have uniqueness message for name"
+
+ w6 = Thaumaturgist.new(:name => "Mustrum Ridcully", :city => "Quirm")
+ assert !w6.valid?, "w6 shouldn't be valid"
+ assert w6.errors[:city].any?, "Should have errors for city"
+ assert_equal ["has already been taken"], w6.errors[:city], "Should have uniqueness message for city"
+ end
+
+ def test_validates_uniqueness_of_with_custom_message_using_quotes
+ 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[:name]
+ end
+ end
+end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index c20f5ae63e..a4e874e5e6 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -10,94 +10,43 @@ require 'models/owner'
require 'models/pet'
require 'models/event'
-# The following methods in Topic are used in test_conditional_validation_*
-class Topic
- has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
- has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
-
- def condition_is_true
- true
- end
-
- def condition_is_true_but_its_not
- false
- end
-end
-
class ProtectedPerson < ActiveRecord::Base
set_table_name 'people'
attr_accessor :addon
attr_protected :first_name
end
-class UniqueReply < Reply
- validates_uniqueness_of :content, :scope => 'parent_id'
-end
-
-class SillyUniqueReply < UniqueReply
-end
-
-class Wizard < ActiveRecord::Base
- self.abstract_class = true
+class DeprecatedPerson < ActiveRecord::Base
+ set_table_name 'people'
- validates_uniqueness_of :name
-end
+ protected
-class IneptWizard < Wizard
- validates_uniqueness_of :city
-end
+ def validate
+ errors[:name] << "always invalid"
+ end
-class Conjurer < IneptWizard
-end
+ def validate_on_create
+ errors[:name] << "invalid on create"
+ end
-class Thaumaturgist < IneptWizard
+ def validate_on_update
+ errors[:name] << "invalid on update"
+ end
end
-
class ValidationsTest < ActiveRecord::TestCase
- fixtures :topics, :developers, 'warehouse-things'
+ fixtures :topics, :developers
# 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
- r.title = "There's no content!"
- assert !r.valid?, "A reply without content shouldn't be saveable"
-
- r.content = "Messa content!"
- assert r.valid?, "A reply with content should be saveable"
- end
-
- def test_single_attr_validation_and_error_msg
- r = Reply.new
- r.title = "There's no content!"
- assert !r.valid?
- assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
- assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
- assert_equal 1, r.errors.count
- end
-
- def test_double_attr_validation_and_error_msg
- r = Reply.new
- assert !r.valid?
-
- assert r.errors.invalid?("title"), "A reply without title should mark that attribute as invalid"
- assert_equal "Empty", r.errors.on("title"), "A reply without title should contain an error"
-
- assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
- assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
-
- assert_equal 2, r.errors.count
- end
-
def test_error_on_create
r = Reply.new
r.title = "Wrong Create"
assert !r.valid?
- assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
- assert_equal "is Wrong Create", r.errors.on("title"), "A reply with a bad content should contain an error"
+ assert r.errors[:title].any?, "A reply with a bad title should mark that attribute as invalid"
+ assert_equal ["is Wrong Create"], r.errors[:title], "A reply with a bad content should contain an error"
end
def test_error_on_update
@@ -109,8 +58,8 @@ class ValidationsTest < ActiveRecord::TestCase
r.title = "Wrong Update"
assert !r.save, "Second save should fail"
- assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
- assert_equal "is Wrong Update", r.errors.on("title"), "A reply with a bad content should contain an error"
+ assert r.errors[:title].any?, "A reply with a bad title should mark that attribute as invalid"
+ assert_equal ["is Wrong Update"], r.errors[:title], "A reply with a bad content should contain an error"
end
def test_invalid_record_exception
@@ -172,47 +121,6 @@ class ValidationsTest < ActiveRecord::TestCase
end
end
- def test_single_error_per_attr_iteration
- r = Reply.new
- r.save
-
- errors = []
- r.errors.each { |attr, msg| errors << [attr, msg] }
-
- assert errors.include?(["title", "Empty"])
- assert errors.include?(["content", "Empty"])
- end
-
- def test_multiple_errors_per_attr_iteration_with_full_error_composition
- r = Reply.new
- r.title = "Wrong Create"
- r.content = "Mismatch"
- r.save
-
- errors = []
- r.errors.each_full { |error| errors << error }
-
- assert_equal "Title is Wrong Create", errors[0]
- assert_equal "Title is Content Mismatch", errors[1]
- assert_equal 2, r.errors.count
- end
-
- def test_errors_on_base
- r = Reply.new
- r.content = "Mismatch"
- r.save
- r.errors.add_to_base "Reply is not dignifying"
-
- errors = []
- r.errors.each_full { |error| errors << error }
-
- assert_equal "Reply is not dignifying", r.errors.on_base
-
- assert errors.include?("Title Empty")
- assert errors.include?("Reply is not dignifying")
- assert_equal 2, r.errors.count
- end
-
def test_create_without_validation
reply = Reply.new
assert !reply.save
@@ -225,96 +133,6 @@ class ValidationsTest < ActiveRecord::TestCase
assert count+1, Reply.count
end
- def test_validates_each
- hits = 0
- Topic.validates_each(:title, :content, [:title, :content]) do |record, attr|
- 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)
- end
-
- def test_no_title_confirmation
- Topic.validates_confirmation_of(:title)
-
- t = Topic.new(:author_name => "Plutarch")
- assert t.valid?
-
- t.title_confirmation = "Parallel Lives"
- assert !t.valid?
-
- t.title_confirmation = nil
- t.title = "Parallel Lives"
- assert t.valid?
-
- t.title_confirmation = "Parallel Lives"
- assert t.valid?
- end
-
- def test_title_confirmation
- Topic.validates_confirmation_of(:title)
-
- t = Topic.create("title" => "We should be confirmed","title_confirmation" => "")
- assert !t.save
-
- t.title_confirmation = "We should be confirmed"
- assert t.save
- end
-
- def test_terms_of_service_agreement_no_acceptance
- Topic.validates_acceptance_of(:terms_of_service, :on => :create)
-
- t = Topic.create("title" => "We should not be confirmed")
- assert t.save
- end
-
- def test_terms_of_service_agreement
- Topic.validates_acceptance_of(:terms_of_service, :on => :create)
-
- t = Topic.create("title" => "We should be confirmed","terms_of_service" => "")
- assert !t.save
- assert_equal "must be accepted", t.errors.on(:terms_of_service)
-
- t.terms_of_service = "1"
- assert t.save
- end
-
-
- def test_eula
- Topic.validates_acceptance_of(:eula, :message => "must be abided", :on => :create)
-
- t = Topic.create("title" => "We should be confirmed","eula" => "")
- assert !t.save
- assert_equal "must be abided", t.errors.on(:eula)
-
- t.eula = "1"
- assert t.save
- end
-
- def test_terms_of_service_agreement_with_accept_value
- Topic.validates_acceptance_of(:terms_of_service, :on => :create, :accept => "I agree.")
-
- t = Topic.create("title" => "We should be confirmed", "terms_of_service" => "")
- assert !t.save
- assert_equal "must be accepted", t.errors.on(:terms_of_service)
-
- t.terms_of_service = "I agree."
- assert t.save
- end
-
- def test_validates_acceptance_of_as_database_column
- 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
Object.const_set :IncorporealModel, Class.new(ActiveRecord::Base)
@@ -323,868 +141,6 @@ class ValidationsTest < ActiveRecord::TestCase
end
end
- def test_validate_presences
- Topic.validates_presence_of(:title, :content)
-
- t = Topic.create
- assert !t.save
- assert_equal "can't be blank", t.errors.on(:title)
- assert_equal "can't be blank", t.errors.on(:content)
-
- t.title = "something"
- t.content = " "
-
- assert !t.save
- assert_equal "can't be blank", t.errors.on(:content)
-
- t.content = "like stuff"
-
- assert t.save
- end
-
- def test_validate_uniqueness
- Topic.validates_uniqueness_of(:title)
-
- t = Topic.new("title" => "I'm unique!")
- assert t.save, "Should save t as unique"
-
- t.content = "Remaining unique"
- assert t.save, "Should still save t as unique"
-
- t2 = Topic.new("title" => "I'm unique!")
- assert !t2.valid?, "Shouldn't be valid"
- assert !t2.save, "Shouldn't save t2 as unique"
- assert_equal "has already been taken", t2.errors.on(:title)
-
- t2.title = "Now Im really also unique"
- assert t2.save, "Should now save t2 as unique"
- end
-
- def test_validates_uniquness_with_newline_chars
- Topic.validates_uniqueness_of(:title, :case_sensitive => false)
-
- t = Topic.new("title" => "new\nline")
- assert t.save, "Should save t as unique"
- end
-
- def test_validate_uniqueness_with_scope
- repair_validations(Reply) do
- Reply.validates_uniqueness_of(:content, :scope => "parent_id")
-
- t = Topic.create("title" => "I'm unique!")
-
- 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.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"
- end
- end
-
- def test_validate_uniqueness_scoped_to_defining_class
- t = Topic.create("title" => "What, me worry?")
-
- r1 = t.unique_replies.create "title" => "r1", "content" => "a barrel of fun"
- assert r1.valid?, "Saving r1"
-
- r2 = t.silly_unique_replies.create "title" => "r2", "content" => "a barrel of fun"
- assert !r2.valid?, "Saving r2"
-
- # Should succeed as validates_uniqueness_of only applies to
- # UniqueReply and its subclasses
- r3 = t.replies.create "title" => "r2", "content" => "a barrel of fun"
- assert r3.valid?, "Saving r3"
- end
-
- def test_validate_uniqueness_with_scope_array
- 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!")
-
- 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.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.author_name = "jj"
- assert r3.save, "Saving r3 the second time."
-
- r3.author_name = "jeremy"
- assert !r3.save, "Saving r3 the third time."
- end
- end
-
- def test_validate_case_insensitive_uniqueness
- Topic.validates_uniqueness_of(:title, :parent_id, :case_sensitive => false, :allow_nil => true)
-
- t = Topic.new("title" => "I'm unique!", :parent_id => 2)
- assert t.save, "Should save t as unique"
-
- t.content = "Remaining unique"
- assert t.save, "Should still save t as unique"
-
- t2 = Topic.new("title" => "I'm UNIQUE!", :parent_id => 1)
- assert !t2.valid?, "Shouldn't be valid"
- assert !t2.save, "Shouldn't save t2 as unique"
- assert t2.errors.on(:title)
- assert t2.errors.on(:parent_id)
- assert_equal "has already been taken", t2.errors.on(:title)
-
- t2.title = "I'm truly UNIQUE!"
- assert !t2.valid?, "Shouldn't be valid"
- assert !t2.save, "Shouldn't save t2 as unique"
- assert_nil t2.errors.on(:title)
- assert t2.errors.on(:parent_id)
-
- t2.parent_id = 4
- assert t2.save, "Should now save t2 as unique"
-
- t2.parent_id = nil
- t2.title = nil
- assert t2.valid?, "should validate with nil"
- assert t2.save, "should save with nil"
-
- with_kcode('UTF8') do
- t_utf8 = Topic.new("title" => "Я тоже уникальный!")
- assert t_utf8.save, "Should save t_utf8 as unique"
-
- # If database hasn't UTF-8 character set, this test fails
- if Topic.find(t_utf8, :select => 'LOWER(title) AS title').title == "я тоже уникальный!"
- t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!")
- assert !t2_utf8.valid?, "Shouldn't be valid"
- assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique"
- end
- end
- end
-
- def test_validate_case_sensitive_uniqueness
- Topic.validates_uniqueness_of(:title, :case_sensitive => true, :allow_nil => true)
-
- t = Topic.new("title" => "I'm unique!")
- assert t.save, "Should save t as unique"
-
- t.content = "Remaining unique"
- assert t.save, "Should still save t as unique"
-
- t2 = Topic.new("title" => "I'M UNIQUE!")
- assert t2.valid?, "Should be valid"
- assert t2.save, "Should save t2 as unique"
- assert !t2.errors.on(:title)
- assert !t2.errors.on(:parent_id)
- assert_not_equal "has already been taken", t2.errors.on(:title)
-
- t3 = Topic.new("title" => "I'M uNiQUe!")
- assert t3.valid?, "Should be valid"
- assert t3.save, "Should save t2 as unique"
- assert !t3.errors.on(:title)
- assert !t3.errors.on(:parent_id)
- assert_not_equal "has already been taken", t3.errors.on(:title)
- end
-
- def test_validate_case_sensitive_uniqueness_with_attribute_passed_as_integer
- Topic.validates_uniqueness_of(:title, :case_sensitve => true)
- t = Topic.create!('title' => 101)
-
- t2 = Topic.new('title' => 101)
- assert !t2.valid?
- assert t2.errors.on(:title)
- end
-
- def test_validate_uniqueness_with_non_standard_table_names
- i1 = WarehouseThing.create(:value => 1000)
- assert !i1.valid?, "i1 should not be valid"
- assert i1.errors.on(:value), "Should not be empty"
- end
-
- def test_validates_uniqueness_inside_with_scope
- Topic.validates_uniqueness_of(:title)
-
- Topic.with_scope(:find => { :conditions => { :author_name => "David" } }) do
- t1 = Topic.new("title" => "I'm unique!", "author_name" => "Mary")
- assert t1.save
- t2 = Topic.new("title" => "I'm unique!", "author_name" => "David")
- assert !t2.valid?
- end
- end
-
- def test_validate_uniqueness_with_columns_which_are_sql_keywords
- 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_uniqueness_with_limit
- # Event.title is limited to 5 characters
- e1 = Event.create(:title => "abcde")
- assert e1.valid?, "Could not create an event with a unique, 5 character title"
- e2 = Event.create(:title => "abcdefgh")
- assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique"
- end
-
- def test_validate_straight_inheritance_uniqueness
- w1 = IneptWizard.create(:name => "Rincewind", :city => "Ankh-Morpork")
- assert w1.valid?, "Saving w1"
-
- # Should use validation from base class (which is abstract)
- w2 = IneptWizard.new(:name => "Rincewind", :city => "Quirm")
- assert !w2.valid?, "w2 shouldn't be valid"
- assert w2.errors.on(:name), "Should have errors for name"
- assert_equal "has already been taken", w2.errors.on(:name), "Should have uniqueness message for name"
-
- w3 = Conjurer.new(:name => "Rincewind", :city => "Quirm")
- assert !w3.valid?, "w3 shouldn't be valid"
- assert w3.errors.on(:name), "Should have errors for name"
- assert_equal "has already been taken", w3.errors.on(:name), "Should have uniqueness message for name"
-
- w4 = Conjurer.create(:name => "The Amazing Bonko", :city => "Quirm")
- assert w4.valid?, "Saving w4"
-
- w5 = Thaumaturgist.new(:name => "The Amazing Bonko", :city => "Lancre")
- assert !w5.valid?, "w5 shouldn't be valid"
- assert w5.errors.on(:name), "Should have errors for name"
- assert_equal "has already been taken", w5.errors.on(:name), "Should have uniqueness message for name"
-
- w6 = Thaumaturgist.new(:name => "Mustrum Ridcully", :city => "Quirm")
- assert !w6.valid?, "w6 shouldn't be valid"
- assert w6.errors.on(:city), "Should have errors for city"
- assert_equal "has already been taken", w6.errors.on(:city), "Should have uniqueness message for city"
- end
-
- def test_validate_format
- Topic.validates_format_of(:title, :content, :with => /^Validation\smacros \w+!$/, :message => "is bad data")
-
- t = Topic.create("title" => "i'm incorrect", "content" => "Validation macros rule!")
- assert !t.valid?, "Shouldn't be valid"
- assert !t.save, "Shouldn't save because it's invalid"
- assert_equal "is bad data", t.errors.on(:title)
- assert_nil t.errors.on(:content)
-
- t.title = "Validation macros rule!"
-
- assert t.save
- assert_nil t.errors.on(:title)
-
- assert_raise(ArgumentError) { Topic.validates_format_of(:title, :content) }
- end
-
- def test_validate_format_with_allow_blank
- Topic.validates_format_of(:title, :with => /^Validation\smacros \w+!$/, :allow_blank=>true)
- assert !Topic.create("title" => "Shouldn't be valid").valid?
- assert Topic.create("title" => "").valid?
- assert Topic.create("title" => nil).valid?
- assert Topic.create("title" => "Validation macros rule!").valid?
- end
-
- # testing ticket #3142
- def test_validate_format_numeric
- Topic.validates_format_of(:title, :content, :with => /^[1-9][0-9]*$/, :message => "is bad data")
-
- t = Topic.create("title" => "72x", "content" => "6789")
- assert !t.valid?, "Shouldn't be valid"
- assert !t.save, "Shouldn't save because it's invalid"
- assert_equal "is bad data", t.errors.on(:title)
- assert_nil t.errors.on(:content)
-
- t.title = "-11"
- assert !t.valid?, "Shouldn't be valid"
-
- t.title = "03"
- assert !t.valid?, "Shouldn't be valid"
-
- t.title = "z44"
- assert !t.valid?, "Shouldn't be valid"
-
- t.title = "5v7"
- assert !t.valid?, "Shouldn't be valid"
-
- t.title = "1"
-
- assert t.save
- assert_nil t.errors.on(:title)
- end
-
- def test_validate_format_with_formatted_message
- Topic.validates_format_of(:title, :with => /^Valid Title$/, :message => "can't be {{value}}")
- t = Topic.create(:title => 'Invalid title')
- assert_equal "can't be Invalid title", t.errors.on(:title)
- end
-
- def test_validates_inclusion_of
- Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ) )
-
- assert !Topic.create("title" => "a!", "content" => "abc").valid?
- assert !Topic.create("title" => "a b", "content" => "abc").valid?
- assert !Topic.create("title" => nil, "content" => "def").valid?
-
- t = Topic.create("title" => "a", "content" => "I know you are but what am I?")
- assert t.valid?
- t.title = "uhoh"
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "is not included in the list", t.errors["title"]
-
- assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => nil ) }
- assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => 0) }
-
- assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => "hi!" ) }
- assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => {} ) }
- assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => [] ) }
- end
-
- def test_validates_inclusion_of_with_allow_nil
- Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :allow_nil=>true )
-
- assert !Topic.create("title" => "a!", "content" => "abc").valid?
- assert !Topic.create("title" => "", "content" => "abc").valid?
- assert Topic.create("title" => nil, "content" => "abc").valid?
- end
-
- def test_numericality_with_getter_method
- 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
- Topic.validates_length_of( :title, :is => 5, :allow_nil=>true )
-
- assert !Topic.create("title" => "ab").valid?
- assert !Topic.create("title" => "").valid?
- assert Topic.create("title" => nil).valid?
- assert Topic.create("title" => "abcde").valid?
- end
-
- def test_validates_length_of_with_allow_blank
- Topic.validates_length_of( :title, :is => 5, :allow_blank=>true )
-
- assert !Topic.create("title" => "ab").valid?
- assert Topic.create("title" => "").valid?
- assert Topic.create("title" => nil).valid?
- assert Topic.create("title" => "abcde").valid?
- end
-
- def test_validates_inclusion_of_with_formatted_message
- Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :message => "option {{value}} is not in the list" )
-
- assert Topic.create("title" => "a", "content" => "abc").valid?
-
- t = Topic.create("title" => "uhoh", "content" => "abc")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "option uhoh is not in the list", t.errors["title"]
- end
-
- def test_numericality_with_allow_nil_and_getter_method
- 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
- Topic.validates_exclusion_of( :title, :in => %w( abe monkey ) )
-
- assert Topic.create("title" => "something", "content" => "abc").valid?
- assert !Topic.create("title" => "monkey", "content" => "abc").valid?
- end
-
- def test_validates_exclusion_of_with_formatted_message
- Topic.validates_exclusion_of( :title, :in => %w( abe monkey ), :message => "option {{value}} is restricted" )
-
- assert Topic.create("title" => "something", "content" => "abc")
-
- t = Topic.create("title" => "monkey")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "option monkey is restricted", t.errors["title"]
- end
-
- def test_validates_length_of_using_minimum
- Topic.validates_length_of :title, :minimum => 5
-
- t = Topic.create("title" => "valid", "content" => "whatever")
- assert t.valid?
-
- t.title = "not"
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
-
- t.title = ""
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
-
- t.title = nil
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
- end
-
- def test_optionally_validates_length_of_using_minimum
- Topic.validates_length_of :title, :minimum => 5, :allow_nil => true
-
- t = Topic.create("title" => "valid", "content" => "whatever")
- assert t.valid?
-
- t.title = nil
- assert t.valid?
- end
-
- def test_validates_length_of_using_maximum
- Topic.validates_length_of :title, :maximum => 5
-
- t = Topic.create("title" => "valid", "content" => "whatever")
- assert t.valid?
-
- t.title = "notvalid"
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "is too long (maximum is 5 characters)", t.errors["title"]
-
- t.title = ""
- assert t.valid?
-
- t.title = nil
- assert !t.valid?
- end
-
- def test_optionally_validates_length_of_using_maximum
- Topic.validates_length_of :title, :maximum => 5, :allow_nil => true
-
- t = Topic.create("title" => "valid", "content" => "whatever")
- assert t.valid?
-
- t.title = nil
- assert t.valid?
- end
-
- def test_validates_length_of_using_within
- Topic.validates_length_of(:title, :content, :within => 3..5)
-
- t = Topic.new("title" => "a!", "content" => "I'm ooooooooh so very long")
- assert !t.valid?
- assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
- assert_equal "is too long (maximum is 5 characters)", t.errors.on(:content)
-
- t.title = nil
- t.content = nil
- assert !t.valid?
- assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
- assert_equal "is too short (minimum is 3 characters)", t.errors.on(:content)
-
- t.title = "abe"
- t.content = "mad"
- assert t.valid?
- end
-
- def test_optionally_validates_length_of_using_within
- Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true
-
- t = Topic.create('title' => 'abc', 'content' => 'abcd')
- assert t.valid?
-
- t.title = nil
- assert t.valid?
- end
-
- def test_optionally_validates_length_of_using_within_on_create
- Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "my string is too long: {{count}}"
-
- t = Topic.create("title" => "thisisnotvalid", "content" => "whatever")
- assert !t.save
- assert t.errors.on(:title)
- assert_equal "my string is too long: 10", t.errors[:title]
-
- t.title = "butthisis"
- assert t.save
-
- t.title = "few"
- assert t.save
-
- t.content = "andthisislong"
- assert t.save
-
- t.content = t.title = "iamfine"
- assert t.save
- end
-
- def test_optionally_validates_length_of_using_within_on_update
- Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "my string is too short: {{count}}"
-
- t = Topic.create("title" => "vali", "content" => "whatever")
- assert !t.save
- assert t.errors.on(:title)
-
- t.title = "not"
- assert !t.save
- assert t.errors.on(:title)
- assert_equal "my string is too short: 5", t.errors[:title]
-
- t.title = "valid"
- t.content = "andthisistoolong"
- assert !t.save
- assert t.errors.on(:content)
-
- t.content = "iamfine"
- assert t.save
- end
-
- def test_validates_length_of_using_is
- Topic.validates_length_of :title, :is => 5
-
- t = Topic.create("title" => "valid", "content" => "whatever")
- assert t.valid?
-
- t.title = "notvalid"
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "is the wrong length (should be 5 characters)", t.errors["title"]
-
- t.title = ""
- assert !t.valid?
-
- t.title = nil
- assert !t.valid?
- end
-
- def test_optionally_validates_length_of_using_is
- Topic.validates_length_of :title, :is => 5, :allow_nil => true
-
- t = Topic.create("title" => "valid", "content" => "whatever")
- assert t.valid?
-
- t.title = nil
- assert t.valid?
- end
-
- def test_validates_length_of_using_bignum
- bigmin = 2 ** 30
- bigmax = 2 ** 32
- bigrange = bigmin...bigmax
- assert_nothing_raised do
- Topic.validates_length_of :title, :is => bigmin + 5
- Topic.validates_length_of :title, :within => bigrange
- Topic.validates_length_of :title, :in => bigrange
- Topic.validates_length_of :title, :minimum => bigmin
- Topic.validates_length_of :title, :maximum => bigmax
- end
- end
-
- def test_validates_length_with_globally_modified_error_message
- ActiveSupport::Deprecation.silence do
- ActiveRecord::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre {{count}}'
- end
- Topic.validates_length_of :title, :minimum => 10
- t = Topic.create(:title => 'too short')
- assert !t.valid?
-
- assert_equal 'tu est trops petit hombre 10', t.errors['title']
- end
-
- def test_validates_size_of_association
- 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
- 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
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>6, :maximum=>9) }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :maximum=>9) }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :minimum=>9) }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :is=>9) }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>"a") }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum=>"a") }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>"a") }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is=>"a") }
- end
-
- def test_validates_length_of_custom_errors_for_minimum_with_message
- Topic.validates_length_of( :title, :minimum=>5, :message=>"boo {{count}}" )
- t = Topic.create("title" => "uhoh", "content" => "whatever")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "boo 5", t.errors["title"]
- end
-
- def test_validates_length_of_custom_errors_for_minimum_with_too_short
- Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo {{count}}" )
- t = Topic.create("title" => "uhoh", "content" => "whatever")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "hoo 5", t.errors["title"]
- end
-
- def test_validates_length_of_custom_errors_for_maximum_with_message
- Topic.validates_length_of( :title, :maximum=>5, :message=>"boo {{count}}" )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "boo 5", t.errors["title"]
- end
-
- def test_validates_length_of_custom_errors_for_in
- Topic.validates_length_of(:title, :in => 10..20, :message => "hoo {{count}}")
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "hoo 10", t.errors["title"]
-
- t = Topic.create("title" => "uhohuhohuhohuhohuhohuhohuhohuhoh", "content" => "whatever")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "hoo 20", t.errors["title"]
- end
-
- def test_validates_length_of_custom_errors_for_maximum_with_too_long
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}" )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "hoo 5", t.errors["title"]
- end
-
- def test_validates_length_of_custom_errors_for_is_with_message
- Topic.validates_length_of( :title, :is=>5, :message=>"boo {{count}}" )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "boo 5", t.errors["title"]
- end
-
- def test_validates_length_of_custom_errors_for_is_with_wrong_length
- Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo {{count}}" )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "hoo 5", t.errors["title"]
- end
-
- def test_validates_length_of_using_minimum_utf8
- with_kcode('UTF8') do
- Topic.validates_length_of :title, :minimum => 5
-
- t = Topic.create("title" => "一二三四五", "content" => "whatever")
- assert t.valid?
-
- t.title = "一二三四"
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
- end
- end
-
- def test_validates_length_of_using_maximum_utf8
- with_kcode('UTF8') do
- Topic.validates_length_of :title, :maximum => 5
-
- t = Topic.create("title" => "一二三四五", "content" => "whatever")
- assert t.valid?
-
- t.title = "一二34五六"
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "is too long (maximum is 5 characters)", t.errors["title"]
- end
- end
-
- def test_validates_length_of_using_within_utf8
- with_kcode('UTF8') do
- Topic.validates_length_of(:title, :content, :within => 3..5)
-
- t = Topic.new("title" => "一二", "content" => "12三四五六七")
- assert !t.valid?
- assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
- assert_equal "is too long (maximum is 5 characters)", t.errors.on(:content)
- t.title = "一二三"
- t.content = "12三"
- assert t.valid?
- end
- end
-
- def test_optionally_validates_length_of_using_within_utf8
- with_kcode('UTF8') do
- Topic.validates_length_of :title, :within => 3..5, :allow_nil => true
-
- t = Topic.create(:title => "一二三四五")
- assert t.valid?, t.errors.inspect
-
- t = Topic.create(:title => "一二三")
- assert t.valid?, t.errors.inspect
-
- t.title = nil
- assert t.valid?, t.errors.inspect
- end
- end
-
- def test_optionally_validates_length_of_using_within_on_create_utf8
- with_kcode('UTF8') do
- Topic.validates_length_of :title, :within => 5..10, :on => :create, :too_long => "長すぎます: {{count}}"
-
- t = Topic.create("title" => "一二三四五六七八九十A", "content" => "whatever")
- assert !t.save
- assert t.errors.on(:title)
- assert_equal "長すぎます: 10", t.errors[:title]
-
- t.title = "一二三四五六七八九"
- assert t.save
-
- t.title = "一二3"
- assert t.save
-
- t.content = "一二三四五六七八九十"
- assert t.save
-
- t.content = t.title = "一二三四五六"
- assert t.save
- end
- end
-
- def test_optionally_validates_length_of_using_within_on_update_utf8
- with_kcode('UTF8') do
- Topic.validates_length_of :title, :within => 5..10, :on => :update, :too_short => "短すぎます: {{count}}"
-
- t = Topic.create("title" => "一二三4", "content" => "whatever")
- assert !t.save
- assert t.errors.on(:title)
-
- t.title = "1二三4"
- assert !t.save
- assert t.errors.on(:title)
- assert_equal "短すぎます: 5", t.errors[:title]
-
- t.title = "一二三四五六七八九十A"
- assert !t.save
- assert t.errors.on(:title)
-
- t.title = "一二345"
- assert t.save
- end
- end
-
- def test_validates_length_of_using_is_utf8
- with_kcode('UTF8') do
- Topic.validates_length_of :title, :is => 5
-
- t = Topic.create("title" => "一二345", "content" => "whatever")
- assert t.valid?
-
- t.title = "一二345六"
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "is the wrong length (should be 5 characters)", t.errors["title"]
- end
- end
-
- def test_validates_length_of_with_block
- Topic.validates_length_of :content, :minimum => 5, :too_short=>"Your essay must be at least {{count}} words.",
- :tokenizer => lambda {|str| str.scan(/\w+/) }
- t = Topic.create!(:content => "this content should be long enough")
- assert t.valid?
-
- t.content = "not long enough"
- assert !t.valid?
- assert t.errors.on(:content)
- assert_equal "Your essay must be at least 5 words.", t.errors[:content]
- end
-
- def test_validates_size_of_association_utf8
- 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
-
- def test_validates_associated_many
- Topic.validates_associated( :replies )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- t.replies << [r = Reply.new("title" => "A reply"), r2 = Reply.new("title" => "Another reply", "content" => "non-empty"), r3 = Reply.new("title" => "Yet another reply"), r4 = Reply.new("title" => "The last reply", "content" => "non-empty")]
- assert !t.valid?
- assert t.errors.on(:replies)
- assert_equal 1, r.errors.count # make sure all associated objects have been validated
- assert_equal 0, r2.errors.count
- assert_equal 1, r3.errors.count
- assert_equal 0, r4.errors.count
- r.content = r3.content = "non-empty"
- assert t.valid?
- end
-
- def test_validates_associated_one
- 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
- Topic.validate { |topic| topic.errors.add("title", "will never be valid") }
- t = Topic.create("title" => "Title", "content" => "whatever")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "will never be valid", t.errors["title"]
- end
-
- def test_invalid_validator
- Topic.validate 3
- assert_raise(ArgumentError) { t = Topic.create }
- end
-
def test_throw_away_typing
d = Developer.new("name" => "David", "salary" => "100,000")
assert !d.valid?
@@ -1192,411 +148,40 @@ class ValidationsTest < ActiveRecord::TestCase
assert_equal "100,000", d.salary_before_type_cast
end
- def test_validates_acceptance_of_with_custom_error_using_quotes
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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)
+ def test_validates_length_with_globally_modified_error_message
+ ActiveSupport::Deprecation.silence do
+ ActiveRecord::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre {{count}}'
end
- end
- def test_if_validation_using_method_true
- # When the method returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => :condition_is_true )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "hoo 5", t.errors["title"]
- end
-
- def test_unless_validation_using_method_true
- # When the method returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => :condition_is_true )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
- assert !t.errors.on(:title)
- end
-
- def test_if_validation_using_method_false
- # When the method returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => :condition_is_true_but_its_not )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
- assert !t.errors.on(:title)
- end
-
- def test_unless_validation_using_method_false
- # When the method returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => :condition_is_true_but_its_not )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "hoo 5", t.errors["title"]
- end
-
- def test_if_validation_using_string_true
- # When the evaluated string returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => "a = 1; a == 1" )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "hoo 5", t.errors["title"]
- end
-
- def test_unless_validation_using_string_true
- # When the evaluated string returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => "a = 1; a == 1" )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
- assert !t.errors.on(:title)
- end
-
- def test_if_validation_using_string_false
- # When the evaluated string returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => "false")
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
- assert !t.errors.on(:title)
- end
-
- def test_unless_validation_using_string_false
- # When the evaluated string returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => "false")
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "hoo 5", t.errors["title"]
- end
-
- def test_if_validation_using_block_true
- # When the block returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
- :if => Proc.new { |r| r.content.size > 4 } )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ Topic.validates_length_of :title, :minimum => 10
+ t = Topic.create(:title => 'too short')
assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "hoo 5", t.errors["title"]
- end
-
- def test_unless_validation_using_block_true
- # When the block returns true
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
- :unless => Proc.new { |r| r.content.size > 4 } )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
- assert !t.errors.on(:title)
- end
- def test_if_validation_using_block_false
- # When the block returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
- :if => Proc.new { |r| r.title != "uhohuhoh"} )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
- assert !t.errors.on(:title)
+ assert_equal ['tu est trops petit hombre 10'], t.errors[:title]
end
- def test_unless_validation_using_block_false
- # When the block returns false
- Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
- :unless => Proc.new { |r| r.title != "uhohuhoh"} )
- t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
- assert !t.valid?
- assert t.errors.on(:title)
- assert_equal "hoo 5", t.errors["title"]
- end
-
- def test_validates_associated_missing
+ def test_validates_acceptance_of_as_database_column
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)
+ Reply.validates_acceptance_of(:author_name)
- r.topic = Topic.find :first
- assert r.valid?
+ reply = Reply.create("author_name" => "Dan Brown")
+ assert_equal "Dan Brown", reply["author_name"]
end
end
- def test_errors_to_xml
- r = Reply.new :title => "Wrong Create"
- assert !r.valid?
- xml = r.errors.to_xml(:skip_instruct => true)
- assert_equal "<errors>", xml.first(8)
- assert xml.include?("<error>Title is Wrong Create</error>")
- assert xml.include?("<error>Content Empty</error>")
- end
-
- 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
-
- def test_invalid_should_be_the_opposite_of_valid
- Topic.validates_presence_of :title
-
- t = Topic.new
- assert t.invalid?
- assert t.errors.invalid?(:title)
-
- t.title = 'Things are going to change'
- assert !t.invalid?
- end
-
- # previous implementation of validates_presence_of eval'd the
- # string with the wrong binding, this regression test is to
- # ensure that it works correctly
- def test_validation_with_if_as_string
- Topic.validates_presence_of(:title)
- Topic.validates_presence_of(:author_name, :if => "title.to_s.match('important')")
-
- t = Topic.new
- assert !t.valid?, "A topic without a title should not be valid"
- assert !t.errors.invalid?("author_name"), "A topic without an 'important' title should not require an author"
-
- t.title = "Just a title"
- assert t.valid?, "A topic with a basic title should be valid"
-
- t.title = "A very important title"
- assert !t.valid?, "A topic with an important title, but without an author, should not be valid"
- assert t.errors.invalid?("author_name"), "A topic with an 'important' title should require an author"
-
- t.author_name = "Hubert J. Farnsworth"
- assert t.valid?, "A topic with an important title and author should be valid"
- end
-end
-
-
-class ValidatesNumericalityTest < ActiveRecord::TestCase
- NIL = [nil]
- BLANK = ["", " ", " \t \r \n"]
- BIGDECIMAL_STRINGS = %w(12345678901234567890.1234567890) # 30 significent digits
- FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1 90.1e1 -90.1e5 -90.1e-5 90e-5)
- INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090)
- FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS
- INTEGERS = [0, 10, -10] + INTEGER_STRINGS
- BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) }
- 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 test_deprecated_validation_instance_methods
+ tom = DeprecatedPerson.new
- repair_validations(Topic)
-
- def test_default_validates_numericality_of
- Topic.validates_numericality_of :approved
-
- invalid!(NIL + BLANK + JUNK)
- valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
- end
-
- def test_validates_numericality_of_with_nil_allowed
- Topic.validates_numericality_of :approved, :allow_nil => true
-
- invalid!(JUNK)
- valid!(NIL + BLANK + FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
- end
-
- def test_validates_numericality_of_with_integer_only
- Topic.validates_numericality_of :approved, :only_integer => true
-
- invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL + INFINITY)
- valid!(INTEGERS)
- end
-
- def test_validates_numericality_of_with_integer_only_and_nil_allowed
- Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true
-
- invalid!(JUNK + FLOATS + BIGDECIMAL + INFINITY)
- valid!(NIL + BLANK + INTEGERS)
- end
-
- def test_validates_numericality_with_greater_than
- Topic.validates_numericality_of :approved, :greater_than => 10
-
- invalid!([-10, 10], 'must be greater than 10')
- valid!([11])
- end
-
- def test_validates_numericality_with_greater_than_or_equal
- Topic.validates_numericality_of :approved, :greater_than_or_equal_to => 10
-
- invalid!([-9, 9], 'must be greater than or equal to 10')
- valid!([10])
- end
-
- def test_validates_numericality_with_equal_to
- Topic.validates_numericality_of :approved, :equal_to => 10
-
- invalid!([-10, 11] + INFINITY, 'must be equal to 10')
- valid!([10])
- end
-
- def test_validates_numericality_with_less_than
- Topic.validates_numericality_of :approved, :less_than => 10
-
- invalid!([10], 'must be less than 10')
- valid!([-9, 9])
- end
-
- def test_validates_numericality_with_less_than_or_equal_to
- Topic.validates_numericality_of :approved, :less_than_or_equal_to => 10
-
- invalid!([11], 'must be less than or equal to 10')
- valid!([-10, 10])
- end
-
- def test_validates_numericality_with_odd
- Topic.validates_numericality_of :approved, :odd => true
-
- invalid!([-2, 2], 'must be odd')
- valid!([-1, 1])
- end
-
- def test_validates_numericality_with_even
- Topic.validates_numericality_of :approved, :even => true
-
- invalid!([-1, 1], 'must be even')
- valid!([-2, 2])
- end
-
- def test_validates_numericality_with_greater_than_less_than_and_even
- Topic.validates_numericality_of :approved, :greater_than => 1, :less_than => 4, :even => true
-
- invalid!([1, 3, 4])
- valid!([2])
- end
-
- def test_validates_numericality_with_numeric_message
- Topic.validates_numericality_of :approved, :less_than => 4, :message => "smaller than {{count}}"
- topic = Topic.new("title" => "numeric test", "approved" => 10)
-
- assert !topic.valid?
- assert_equal "smaller than 4", topic.errors.on(:approved)
-
- Topic.validates_numericality_of :approved, :greater_than => 4, :message => "greater than {{count}}"
- topic = Topic.new("title" => "numeric test", "approved" => 1)
-
- assert !topic.valid?
- assert_equal "greater than 4", topic.errors.on(:approved)
- end
-
- private
- def invalid!(values, error=nil)
- with_each_topic_approved_value(values) do |topic, value|
- assert !topic.valid?, "#{value.inspect} not rejected as a number"
- assert topic.errors.on(:approved)
- assert_equal error, topic.errors.on(:approved) if error
- end
+ assert_deprecated do
+ assert tom.invalid?
+ assert_equal ["always invalid", "invalid on create"], tom.errors[:name]
end
- def valid!(values)
- with_each_topic_approved_value(values) do |topic, value|
- assert topic.valid?, "#{value.inspect} not accepted as a number"
- end
- end
+ tom.save(false)
- def with_each_topic_approved_value(values)
- topic = Topic.new("title" => "numeric test", "content" => "whatever")
- values.each do |value|
- topic.approved = value
- yield topic, value
- end
+ assert_deprecated do
+ assert tom.invalid?
+ assert_equal ["always invalid", "invalid on update"], tom.errors[:name]
end
+ end
end
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index eb68153bbe..4c2514b0b3 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -145,10 +145,13 @@ class Account < ActiveRecord::Base
true
end
+ validate :check_empty_credit_limit
+
protected
- def validate
- errors.add_on_empty "credit_limit"
- end
+
+ def check_empty_credit_limit
+ errors.add_on_empty "credit_limit"
+ end
private
diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb
index 3c34efbe16..8b84c2fb5e 100644
--- a/activerecord/test/models/company_in_module.rb
+++ b/activerecord/test/models/company_in_module.rb
@@ -54,10 +54,13 @@ module MyApplication
i.belongs_to :nested_unqualified_billing_firm, :class_name => 'Nested::Firm'
end
+ validate :check_empty_credit_limit
+
protected
- def validate
- errors.add_on_empty "credit_limit"
- end
+
+ def check_empty_credit_limit
+ errors.add_on_empty "credit_limit"
+ end
end
end
end
diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb
index 1c990acab6..616c07687c 100644
--- a/activerecord/test/models/reply.rb
+++ b/activerecord/test/models/reply.rb
@@ -11,26 +11,30 @@ class Reply < Topic
attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read
- def validate
- errors.add("title", "Empty") unless attribute_present? "title"
+ validate :check_empty_title
+ validate_on_create :check_content_mismatch
+ validate_on_update :check_wrong_update
+
+ def check_empty_title
+ errors[:title] << "Empty" unless attribute_present?("title")
end
def errors_on_empty_content
- errors.add("content", "Empty") unless attribute_present? "content"
+ errors[:content] << "Empty" unless attribute_present?("content")
end
- def validate_on_create
+ def check_content_mismatch
if attribute_present?("title") && attribute_present?("content") && content == "Mismatch"
- errors.add("title", "is Content Mismatch")
+ errors[:title] << "is Content Mismatch"
end
end
def title_is_wrong_create
- errors.add("title", "is Wrong Create") if attribute_present?("title") && title == "Wrong Create"
+ errors[:title] << "is Wrong Create" if attribute_present?("title") && title == "Wrong Create"
end
- def validate_on_update
- errors.add("title", "is Wrong Update") if attribute_present?("title") && title == "Wrong Update"
+ def check_wrong_update
+ errors[:title] << "is Wrong Update" if attribute_present?("title") && title == "Wrong Update"
end
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 6e8813d8ab..b2aaccb352 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -273,6 +273,7 @@ ActiveRecord::Schema.define do
t.decimal :my_house_population, :precision => 2, :scale => 0
t.decimal :decimal_number_with_default, :precision => 3, :scale => 2, :default => 2.78
t.float :temperature
+ t.decimal :atoms_in_universe, :precision => 55, :scale => 0
end
create_table :orders, :force => true do |t|
diff --git a/activeresource/lib/active_resource.rb b/activeresource/lib/active_resource.rb
index 720abee72e..b46801affc 100644
--- a/activeresource/lib/active_resource.rb
+++ b/activeresource/lib/active_resource.rb
@@ -25,6 +25,18 @@ activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
$:.unshift(activesupport_path) if File.directory?(activesupport_path)
require 'active_support'
+begin
+ require 'active_model'
+rescue LoadError
+ $:.unshift "#{File.dirname(__FILE__)}/../../activemodel/lib"
+ require 'active_model'
+end
+
+require 'active_resource/formats'
+require 'active_resource/base'
+require 'active_resource/validations'
+require 'active_resource/custom_methods'
+
module ActiveResource
autoload :Base, 'active_resource/base'
end
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index dc24e713ff..11a7bbba3e 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -1,12 +1,14 @@
require 'active_support'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/inheritable_attributes'
+require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/module/attr_accessor_with_default'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/misc'
require 'set'
+require 'uri'
module ActiveResource
autoload :Formats, 'active_resource/formats'
@@ -346,9 +348,9 @@ module ActiveResource
# Do not include any modules in the default element name. This makes it easier to seclude ARes objects
# in a separate namespace without having to set element_name repeatedly.
- attr_accessor_with_default(:element_name) { to_s.split("::").last.underscore } #:nodoc:
+ attr_accessor_with_default(:element_name) { ActiveSupport::Inflector.underscore(to_s.split("::").last) } #:nodoc:
- attr_accessor_with_default(:collection_name) { element_name.pluralize } #:nodoc:
+ attr_accessor_with_default(:collection_name) { ActiveSupport::Inflector.pluralize(element_name) } #:nodoc:
attr_accessor_with_default(:primary_key, 'id') #:nodoc:
# Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>)
@@ -384,7 +386,7 @@ module ActiveResource
end_code
silence_warnings { instance_eval code, __FILE__, __LINE__ }
rescue
- logger.error "Couldn't set prefix: #{$!}\n #{code}"
+ logger.error "Couldn't set prefix: #{$!}\n #{code}" if logger
raise
end
diff --git a/activeresource/lib/active_resource/validations.rb b/activeresource/lib/active_resource/validations.rb
index da6084fe9f..95c5467647 100644
--- a/activeresource/lib/active_resource/validations.rb
+++ b/activeresource/lib/active_resource/validations.rb
@@ -6,203 +6,8 @@ module ActiveResource
end
# Active Resource 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.
- class Errors
- include Enumerable
- attr_reader :errors
-
- delegate :empty?, :to => :errors
-
- def initialize(base) # :nodoc:
- @base, @errors = base, {}
- end
-
- # Adds an error to the base object instead of any particular attribute. This is used
- # to report errors that don't tie to any specific attribute, but rather to the object
- # as a whole. These error messages don't get prepended with any field name when iterating
- # with +each_full+, so they should be complete sentences.
- #
- # ==== Examples
- # my_folder = Folder.find(1)
- # my_folder.errors.add_to_base("You can't edit an existing folder")
- # my_folder.errors.on_base
- # # => "You can't edit an existing folder"
- #
- # my_folder.errors.add_to_base("This folder has been tagged as frozen")
- # my_folder.valid?
- # # => false
- # my_folder.errors.on_base
- # # => ["You can't edit an existing folder", "This folder has been tagged as frozen"]
- #
- def add_to_base(msg)
- add(:base, msg)
- end
-
- # Adds an error to an Active Resource object's attribute (named for the +attribute+ parameter)
- # with the error message in +msg+.
- #
- # ==== Examples
- # my_resource = Node.find(1)
- # my_resource.errors.add('name', 'can not be "base"') if my_resource.name == 'base'
- # my_resource.errors.on('name')
- # # => 'can not be "base"!'
- #
- # my_resource.errors.add('desc', 'can not be blank') if my_resource.desc == ''
- # my_resource.valid?
- # # => false
- # my_resource.errors.on('desc')
- # # => 'can not be blank!'
- #
- def add(attribute, msg)
- @errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
- @errors[attribute.to_s] << msg
- end
-
- # Returns true if the specified +attribute+ has errors associated with it.
- #
- # ==== Examples
- # my_resource = Disk.find(1)
- # my_resource.errors.add('location', 'must be Main') unless my_resource.location == 'Main'
- # my_resource.errors.on('location')
- # # => 'must be Main!'
- #
- # my_resource.errors.invalid?('location')
- # # => true
- # my_resource.errors.invalid?('name')
- # # => false
- def invalid?(attribute)
- !@errors[attribute.to_s].nil?
- end
-
- # Returns +nil+ if no errors are associated with the specified +attribute+.
- # Returns the error message if one error is associated with the specified +attribute+.
- # Returns an array of error messages if more than one error is associated with the specified +attribute+.
- #
- # ==== Examples
- # my_person = Person.new(params[:person])
- # my_person.errors.on('login')
- # # => nil
- #
- # my_person.errors.add('login', 'can not be empty') if my_person.login == ''
- # my_person.errors.on('login')
- # # => 'can not be empty'
- #
- # my_person.errors.add('login', 'can not be longer than 10 characters') if my_person.login.length > 10
- # my_person.errors.on('login')
- # # => ['can not be empty', 'can not be longer than 10 characters']
- def on(attribute)
- errors = @errors[attribute.to_s]
- return nil if errors.nil?
- errors.size == 1 ? errors.first : errors
- end
-
- alias :[] :on
-
- # Returns errors assigned to the base object through +add_to_base+ according to the normal rules of <tt>on(attribute)</tt>.
- #
- # ==== Examples
- # my_account = Account.find(1)
- # my_account.errors.on_base
- # # => nil
- #
- # my_account.errors.add_to_base("This account is frozen")
- # my_account.errors.on_base
- # # => "This account is frozen"
- #
- # my_account.errors.add_to_base("This account has been closed")
- # my_account.errors.on_base
- # # => ["This account is frozen", "This account has been closed"]
- #
- def on_base
- on(:base)
- end
-
- # Yields each attribute and associated message per error added.
- #
- # ==== Examples
- # my_person = Person.new(params[:person])
- #
- # my_person.errors.add('login', 'can not be empty') if my_person.login == ''
- # my_person.errors.add('password', 'can not be empty') if my_person.password == ''
- # messages = ''
- # my_person.errors.each {|attr, msg| messages += attr.humanize + " " + msg + "<br />"}
- # messages
- # # => "Login can not be empty<br />Password can not be empty<br />"
- #
- def each
- @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
- end
-
- # Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
- # through iteration as "First name can't be empty".
- #
- # ==== Examples
- # my_person = Person.new(params[:person])
- #
- # my_person.errors.add('login', 'can not be empty') if my_person.login == ''
- # my_person.errors.add('password', 'can not be empty') if my_person.password == ''
- # messages = ''
- # my_person.errors.each_full {|msg| messages += msg + "<br/>"}
- # messages
- # # => "Login can not be empty<br />Password can not be empty<br />"
- #
- def each_full
- full_messages.each { |msg| yield msg }
- end
-
- # Returns all the full error messages in an array.
- #
- # ==== Examples
- # my_person = Person.new(params[:person])
- #
- # my_person.errors.add('login', 'can not be empty') if my_person.login == ''
- # my_person.errors.add('password', 'can not be empty') if my_person.password == ''
- # messages = ''
- # my_person.errors.full_messages.each {|msg| messages += msg + "<br/>"}
- # messages
- # # => "Login can not be empty<br />Password can not be empty<br />"
- #
- def full_messages
- full_messages = []
-
- @errors.each_key do |attr|
- @errors[attr].each do |msg|
- next if msg.nil?
-
- if attr == "base"
- full_messages << msg
- else
- full_messages << [attr.humanize, msg].join(' ')
- end
- end
- end
- full_messages
- end
-
- def clear
- @errors = {}
- end
-
- # Returns the total number of errors added. Two errors added to the same attribute will be counted as such
- # with this as well.
- #
- # ==== Examples
- # my_person = Person.new(params[:person])
- # my_person.errors.size
- # # => 0
- #
- # my_person.errors.add('login', 'can not be empty') if my_person.login == ''
- # my_person.errors.add('password', 'can not be empty') if my_person.password == ''
- # my_person.error.size
- # # => 2
- #
- def size
- @errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
- end
-
- alias_method :count, :size
- alias_method :length, :size
-
+ # to determine whether the object in a valid state to be saved. See usage example in Validations.
+ class Errors < ActiveModel::Errors
# Grabs errors from the XML response.
def from_xml(xml)
clear
@@ -215,7 +20,7 @@ module ActiveResource
end
end
- add_to_base message if attr_message.nil?
+ self[:base] << message if attr_message.nil?
end
end
end
@@ -236,7 +41,7 @@ module ActiveResource
# person.errors.empty? # => false
# person.errors.count # => 1
# person.errors.full_messages # => ["Last name can't be empty"]
- # person.errors.on(:last_name) # => "can't be empty"
+ # person.errors[:last_name] # => ["can't be empty"]
# person.last_name = "Halpert"
# person.save # => true (and person is now saved to the remote service)
#
diff --git a/activeresource/test/base_errors_test.rb b/activeresource/test/base_errors_test.rb
index 7ae92c7d98..28813821df 100644
--- a/activeresource/test/base_errors_test.rb
+++ b/activeresource/test/base_errors_test.rb
@@ -20,21 +20,21 @@ class BaseErrorsTest < Test::Unit::TestCase
end
def test_should_parse_errors_to_individual_attributes
- assert @person.errors.invalid?(:name)
- assert_equal "can't be blank", @person.errors.on(:age)
+ assert @person.errors[:name].any?
+ assert_equal ["can't be blank"], @person.errors[:age]
assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name]
- assert_equal "Person quota full for today.", @person.errors.on_base
+ assert_equal ["Person quota full for today."], @person.errors[:base]
end
def test_should_iterate_over_errors
errors = []
- @person.errors.each { |attribute, message| errors << [attribute, message] }
+ @person.errors.each { |attribute, message| errors << [attribute.to_s, message] }
assert errors.include?(["name", "can't be blank"])
end
def test_should_iterate_over_full_errors
errors = []
- @person.errors.each_full { |message| errors << message }
+ @person.errors.to_a.each { |message| errors << message }
assert errors.include?("Name can't be blank")
end
diff --git a/activesupport/lib/active_support/autoload.rb b/activesupport/lib/active_support/autoload.rb
index ed229d1c5f..75706855d6 100644
--- a/activesupport/lib/active_support/autoload.rb
+++ b/activesupport/lib/active_support/autoload.rb
@@ -5,7 +5,7 @@ module ActiveSupport
autoload :BufferedLogger, 'active_support/buffered_logger'
autoload :Cache, 'active_support/cache'
autoload :Callbacks, 'active_support/callbacks'
- autoload :NewCallbacks, 'active_support/new_callbacks'
+ autoload :Concern, 'active_support/concern'
autoload :ConcurrentHash, 'active_support/concurrent_hash'
autoload :DependencyModule, 'active_support/dependency_module'
autoload :Deprecation, 'active_support/deprecation'
@@ -15,6 +15,7 @@ module ActiveSupport
autoload :MessageEncryptor, 'active_support/message_encryptor'
autoload :MessageVerifier, 'active_support/message_verifier'
autoload :Multibyte, 'active_support/multibyte'
+ autoload :NewCallbacks, 'active_support/new_callbacks'
autoload :OptionMerger, 'active_support/option_merger'
autoload :OrderedHash, 'active_support/ordered_hash'
autoload :OrderedOptions, 'active_support/ordered_options'
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index 4bbcd8e4c4..3b5fccc737 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -45,7 +45,7 @@ module ActiveSupport
elsif value.nil?
value = super
local_cache.write(key, value || NULL) if local_cache
- value
+ value.duplicable? ? value.dup : value
else
# forcing the value to be immutable
value.duplicable? ? value.dup : value
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
new file mode 100644
index 0000000000..dcf1e8152f
--- /dev/null
+++ b/activesupport/lib/active_support/concern.rb
@@ -0,0 +1,25 @@
+require 'active_support/dependency_module'
+
+module ActiveSupport
+ module Concern
+ include DependencyModule
+
+ def append_features(base)
+ if super
+ base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
+ base.send :include, const_get("InstanceMethods") if const_defined?("InstanceMethods")
+ base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
+ end
+ end
+
+ def included(base = nil, &block)
+ if base.nil?
+ @_included_block = block
+ else
+ super
+ end
+ end
+
+ alias_method :include, :depends_on
+ end
+end
diff --git a/activesupport/lib/active_support/dependency_module.rb b/activesupport/lib/active_support/dependency_module.rb
index 9872b9654b..6847c0f86a 100644
--- a/activesupport/lib/active_support/dependency_module.rb
+++ b/activesupport/lib/active_support/dependency_module.rb
@@ -1,19 +1,9 @@
module ActiveSupport
module DependencyModule
def append_features(base)
- return if base < self
+ return false if base < self
(@_dependencies ||= []).each { |dep| base.send(:include, dep) }
super
- base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
- base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
- end
-
- def included(base = nil, &block)
- if base.nil?
- @_included_block = block
- else
- super
- end
end
def depends_on(*mods)
diff --git a/activesupport/lib/active_support/new_callbacks.rb b/activesupport/lib/active_support/new_callbacks.rb
index b6cbdbb6b0..58d4c47ccb 100644
--- a/activesupport/lib/active_support/new_callbacks.rb
+++ b/activesupport/lib/active_support/new_callbacks.rb
@@ -286,7 +286,14 @@ module ActiveSupport
filter
when Proc
@klass.send(:define_method, method_name, &filter)
- method_name << (filter.arity == 1 ? "(self)" : "")
+ method_name << case filter.arity
+ when 1
+ "(self)"
+ when 2
+ " self, Proc.new "
+ else
+ ""
+ end
when Method
@klass.send(:define_method, "#{method_name}_method") { filter }
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
@@ -311,6 +318,11 @@ module ActiveSupport
def #{method_name}(&blk)
if :#{kind} == :around && #{method_name}_object.respond_to?(:filter)
#{method_name}_object.send("filter", self, &blk)
+ # TODO: Deprecate this
+ elsif #{method_name}_object.respond_to?(:before) && #{method_name}_object.respond_to?(:after)
+ should_continue = #{method_name}_object.before(self)
+ yield if should_continue
+ #{method_name}_object.after(self)
else
#{method_name}_object.send("#{kind}_#{name}", self, &blk)
end
@@ -319,7 +331,13 @@ module ActiveSupport
else
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{method_name}(&blk)
- #{method_name}_object.send("#{kind}_#{name}", self, &blk)
+ if #{method_name}_object.respond_to?(:#{kind})
+ #{method_name}_object.#{kind}(self, &blk)
+ elsif #{method_name}_object.respond_to?(:filter)
+ #{method_name}_object.send("filter", self, &blk)
+ else
+ #{method_name}_object.send("#{kind}_#{name}", self, &blk)
+ end
end
RUBY_EVAL
end
@@ -367,7 +385,7 @@ module ActiveSupport
# The _run_save_callbacks method can optionally take a key, which
# will be used to compile an optimized callback method for each
# key. See #define_callbacks for more information.
- def _define_runner(symbol, str, options)
+ def _define_runner(symbol, str, options)
str = <<-RUBY_EVAL
def _run_#{symbol}_callbacks(key = nil)
if key
@@ -464,7 +482,9 @@ module ActiveSupport
self._#{symbol}_callbacks.delete_if {|c| c.matches?(type, :#{symbol}, filter)}
Callback.new(filter, type, options.dup, self, :#{symbol})
end
- self._#{symbol}_callbacks.push(*filters)
+ options[:prepend] ?
+ self._#{symbol}_callbacks.unshift(*filters) :
+ self._#{symbol}_callbacks.push(*filters)
_define_runner(:#{symbol},
self._#{symbol}_callbacks.compile(nil, :terminator => _#{symbol}_terminator),
options)
@@ -479,7 +499,7 @@ module ActiveSupport
filter = self._#{symbol}_callbacks.find {|c| c.matches?(type, :#{symbol}, filter) }
per_key = options[:per_key] || {}
- if filter
+ if filter && options.any?
filter.recompile!(options, per_key)
else
self._#{symbol}_callbacks.delete(filter)
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index bd237a5c8e..51d04d9388 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -180,6 +180,15 @@ uses_memcached 'memcached backed store' do
end
end
+ def test_stored_objects_should_not_be_frozen
+ @cache.with_local_cache do
+ @cache.write('foo', 'bar')
+ end
+ @cache.with_local_cache do
+ assert !@cache.read('foo').frozen?
+ end
+ end
+
def test_write_should_return_true_on_success
@cache.with_local_cache do
result = @cache.write('foo', 'bar')
diff --git a/activesupport/test/dependency_module_test.rb b/activesupport/test/concern_test.rb
index be7db0fa7b..4cbe56a2d2 100644
--- a/activesupport/test/dependency_module_test.rb
+++ b/activesupport/test/concern_test.rb
@@ -1,9 +1,9 @@
require 'abstract_unit'
-require 'active_support/dependency_module'
+require 'active_support/concern'
-class DependencyModuleTest < Test::Unit::TestCase
+class ConcernTest < Test::Unit::TestCase
module Baz
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
module ClassMethods
def baz
@@ -19,6 +19,9 @@ class DependencyModuleTest < Test::Unit::TestCase
end
end
+ module InstanceMethods
+ end
+
included do
self.included_ran = true
end
@@ -29,9 +32,9 @@ class DependencyModuleTest < Test::Unit::TestCase
end
module Bar
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
- depends_on Baz
+ include Baz
def bar
"bar"
@@ -43,9 +46,9 @@ class DependencyModuleTest < Test::Unit::TestCase
end
module Foo
- extend ActiveSupport::DependencyModule
+ extend ActiveSupport::Concern
- depends_on Bar, Baz
+ include Bar, Baz
end
def setup
@@ -55,17 +58,23 @@ class DependencyModuleTest < Test::Unit::TestCase
def test_module_is_included_normally
@klass.send(:include, Baz)
assert_equal "baz", @klass.new.baz
- assert_equal DependencyModuleTest::Baz, @klass.included_modules[0]
+ assert @klass.included_modules.include?(ConcernTest::Baz)
@klass.send(:include, Baz)
assert_equal "baz", @klass.new.baz
- assert_equal DependencyModuleTest::Baz, @klass.included_modules[0]
+ assert @klass.included_modules.include?(ConcernTest::Baz)
end
def test_class_methods_are_extended
@klass.send(:include, Baz)
assert_equal "baz", @klass.baz
- assert_equal DependencyModuleTest::Baz::ClassMethods, (class << @klass; self.included_modules; end)[0]
+ assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; self.included_modules; end)[0]
+ end
+
+ def test_instance_methods_are_included
+ @klass.send(:include, Baz)
+ assert_equal "baz", @klass.new.baz
+ assert @klass.included_modules.include?(ConcernTest::Baz::InstanceMethods)
end
def test_included_block_is_ran
@@ -78,11 +87,11 @@ class DependencyModuleTest < Test::Unit::TestCase
assert_equal "bar", @klass.new.bar
assert_equal "bar+baz", @klass.new.baz
assert_equal "baz", @klass.baz
- assert_equal [DependencyModuleTest::Bar, DependencyModuleTest::Baz], @klass.included_modules[0..1]
+ assert @klass.included_modules.include?(ConcernTest::Bar)
end
- def test_depends_on_with_multiple_modules
+ def test_dependencies_with_multiple_modules
@klass.send(:include, Foo)
- assert_equal [DependencyModuleTest::Foo, DependencyModuleTest::Bar, DependencyModuleTest::Baz], @klass.included_modules[0..2]
+ assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz::InstanceMethods, ConcernTest::Baz], @klass.included_modules[0..3]
end
end
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 8e7dfb38cc..782afd5aa4 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*
+* Ruby 1.9: use UTF-8 for default internal and external encodings. [Jeremy Kemper]
+
* Added db/seeds.rb as a default file for storing seed data for the database. Can be loaded with rake db:seed (or created alongside the db with db:setup). (This is also known as the "Stop Putting Gawd Damn Seed Data In Your Migrations" feature) [DHH]
diff --git a/railties/lib/commands/server.rb b/railties/lib/commands/server.rb
index 91ac7752ef..01dd33fa8c 100644
--- a/railties/lib/commands/server.rb
+++ b/railties/lib/commands/server.rb
@@ -22,17 +22,17 @@ options = {
ARGV.clone.options do |opts|
opts.on("-p", "--port=port", Integer,
- "Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v }
+ "Runs Rails on the specified port.", "Default: #{options[:Port]}") { |v| options[:Port] = v }
opts.on("-b", "--binding=ip", String,
- "Binds Rails to the specified ip.", "Default: 0.0.0.0") { |v| options[:Host] = v }
+ "Binds Rails to the specified ip.", "Default: #{options[:Host]}") { |v| options[:Host] = v }
opts.on("-c", "--config=file", String,
"Use custom rackup configuration file") { |v| options[:config] = v }
opts.on("-d", "--daemon", "Make server run as a Daemon.") { options[:detach] = true }
opts.on("-u", "--debugger", "Enable ruby-debugging for the server.") { options[:debugger] = true }
opts.on("-e", "--environment=name", String,
"Specifies the environment to run this server under (test/development/production).",
- "Default: development") { |v| options[:environment] = v }
- opts.on("-P", "--path=/path", String, "Runs Rails app mounted at a specific path.", "Default: /") { |v| options[:path] = v }
+ "Default: #{options[:environment]}") { |v| options[:environment] = v }
+ opts.on("-P", "--path=/path", String, "Runs Rails app mounted at a specific path.", "Default: #{options[:path]}") { |v| options[:path] = v }
opts.separator ""
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index 3c0d5940ea..7273cea0c5 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -426,10 +426,14 @@ Run `rake gems:install` to install the missing gems.
# should override this behaviour and set the relevant +default_charset+
# on ActionController::Base.
#
- # For Ruby 1.9, this does nothing. Specify the default encoding in the Ruby
- # shebang line if you don't want UTF-8.
+ # For Ruby 1.9, UTF-8 is the default internal and external encoding.
def initialize_encoding
- $KCODE='u' if RUBY_VERSION < '1.9'
+ if RUBY_VERSION < '1.9'
+ $KCODE='u'
+ else
+ Encoding.default_internal = Encoding::UTF_8
+ Encoding.default_external = Encoding::UTF_8
+ end
end
# This initialization routine does nothing unless <tt>:active_record</tt>
@@ -445,7 +449,8 @@ Run `rake gems:install` to install the missing gems.
def initialize_database_middleware
if configuration.frameworks.include?(:active_record)
- if ActionController::Base.session_store == ActiveRecord::SessionStore
+ if configuration.frameworks.include?(:action_controller) &&
+ ActionController::Base.session_store.name == 'ActiveRecord::SessionStore'
configuration.middleware.insert_before :"ActiveRecord::SessionStore", ActiveRecord::ConnectionAdapters::ConnectionManagement
configuration.middleware.insert_before :"ActiveRecord::SessionStore", ActiveRecord::QueryCache
else
@@ -886,7 +891,7 @@ Run `rake gems:install` to install the missing gems.
# Enable threaded mode. Allows concurrent requests to controller actions and
# multiple database connections. Also disables automatic dependency loading
- # after boot, and disables reloading code on every request, as these are
+ # after boot, and disables reloading code on every request, as these are
# fundamentally incompatible with thread safety.
def threadsafe!
self.preload_frameworks = true
@@ -1129,3 +1134,4 @@ class Rails::OrderedOptions < Array #:nodoc:
return false
end
end
+
diff --git a/railties/lib/rails_generator/generators/components/model_subclass/USAGE b/railties/lib/rails_generator/generators/components/model_subclass/USAGE
new file mode 100644
index 0000000000..a4b558a401
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/model_subclass/USAGE
@@ -0,0 +1,13 @@
+Description:
+ Create a model subclass of parent, used for Single Table Inheritance.
+
+ Both subclass and parent name can be either CamelCased or under_scored.
+
+ This generates a model class in app/models and a unit test in test/unit.
+
+Examples:
+ `./script/generate model_subclass admin user`
+
+ creates an Admin model, which will inheritate from User model, test:
+ Model: app/models/admin.rb
+ Test: test/unit/admin_test.rb
diff --git a/railties/lib/rails_generator/generators/components/model_subclass/model_subclass_generator.rb b/railties/lib/rails_generator/generators/components/model_subclass/model_subclass_generator.rb
new file mode 100644
index 0000000000..e8ac3da2cd
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/model_subclass/model_subclass_generator.rb
@@ -0,0 +1,32 @@
+class ModelSubclassGenerator < Rails::Generator::NamedBase
+ default_options :skip_unit_test => false
+
+ def manifest
+ record do |m|
+ # Check for class naming collisions.
+ m.class_collisions class_name, "#{class_name}Test"
+
+ # Model and test directories.
+ m.directory File.join('app/models', class_path)
+ m.directory File.join('test/unit', class_path)
+
+ # Model class and unit test
+ m.template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb"), :assigns => assigns
+ m.template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_test.rb"), :assigns => assigns
+
+ end
+ end
+
+ protected
+ def banner
+ "Usage: #{$0} #{spec.name} Subclass Parent"
+ end
+
+ def assigns
+ {:parent_class_name => parent_class_name}
+ end
+
+ def parent_class_name
+ @args.first.try(:camelize) || usage
+ end
+end
diff --git a/railties/lib/rails_generator/generators/components/model_subclass/templates/model.rb b/railties/lib/rails_generator/generators/components/model_subclass/templates/model.rb
new file mode 100644
index 0000000000..d0037b322b
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/model_subclass/templates/model.rb
@@ -0,0 +1,3 @@
+class <%= class_name %> < <%= parent_class_name %>
+
+end \ No newline at end of file
diff --git a/railties/lib/rails_generator/generators/components/model_subclass/templates/unit_test.rb b/railties/lib/rails_generator/generators/components/model_subclass/templates/unit_test.rb
new file mode 100644
index 0000000000..3e0bc29d3a
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/model_subclass/templates/unit_test.rb
@@ -0,0 +1,8 @@
+require 'test_helper'
+
+class <%= class_name %>Test < ActiveSupport::TestCase
+ # Replace this with your real tests.
+ test "the truth" do
+ assert true
+ end
+end
diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake
index cdab5d8bb0..0e256737f9 100644
--- a/railties/lib/tasks/databases.rake
+++ b/railties/lib/tasks/databases.rake
@@ -56,12 +56,28 @@ namespace :db do
when 'mysql'
@charset = ENV['CHARSET'] || 'utf8'
@collation = ENV['COLLATION'] || 'utf8_general_ci'
+ creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)}
begin
ActiveRecord::Base.establish_connection(config.merge('database' => nil))
- ActiveRecord::Base.connection.create_database(config['database'], :charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation))
+ ActiveRecord::Base.connection.create_database(config['database'], creation_options)
ActiveRecord::Base.establish_connection(config)
- rescue
- $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['charset'] || @charset}, collation: #{config['collation'] || @collation} (if you set the charset manually, make sure you have a matching collation)"
+ rescue Mysql::Error => sqlerr
+ if sqlerr.errno == Mysql::Error::ER_ACCESS_DENIED_ERROR
+ print "#{sqlerr.error}. \nPlease provide the root password for your mysql installation\n>"
+ root_password = $stdin.gets.strip
+ grant_statement = "GRANT ALL PRIVILEGES ON #{config['database']}.* " \
+ "TO '#{config['username']}'@'localhost' " \
+ "IDENTIFIED BY '#{config['password']}' WITH GRANT OPTION;"
+ ActiveRecord::Base.establish_connection(config.merge(
+ 'database' => nil, 'username' => 'root', 'password' => root_password))
+ ActiveRecord::Base.connection.create_database(config['database'], creation_options)
+ ActiveRecord::Base.connection.execute grant_statement
+ ActiveRecord::Base.establish_connection(config)
+ else
+ $stderr.puts sqlerr.error
+ $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['charset'] || @charset}, collation: #{config['collation'] || @collation}"
+ $stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['charset']
+ end
end
when 'postgresql'
@encoding = config[:encoding] || ENV['CHARSET'] || 'utf8'
@@ -282,7 +298,9 @@ namespace :db do
ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"]
ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"]
search_path = abcs[RAILS_ENV]["schema_search_path"]
- search_path = "--schema=#{search_path}" if search_path
+ unless search_path.blank?
+ search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ")
+ end
`pg_dump -i -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{search_path} #{abcs[RAILS_ENV]["database"]}`
raise "Error dumping database" if $?.exitstatus == 1
when "sqlite", "sqlite3"
diff --git a/railties/test/generators/rails_model_subclass_generator_test.rb b/railties/test/generators/rails_model_subclass_generator_test.rb
new file mode 100644
index 0000000000..30066b5a3c
--- /dev/null
+++ b/railties/test/generators/rails_model_subclass_generator_test.rb
@@ -0,0 +1,15 @@
+require 'generators/generator_test_helper'
+
+class RailsModelSubclassGeneratorTest < GeneratorTestCase
+
+ def test_model_subclass_generates_resources
+ run_generator('model_subclass', %w(Car Product))
+
+ assert_generated_model_for :car, "Product"
+ assert_generated_unit_test_for :car
+ end
+
+ def test_model_subclass_must_have_a_parent_class_name
+ assert_raise(Rails::Generator::UsageError) { run_generator('model_subclass', %w(Car)) }
+ end
+end \ No newline at end of file
diff --git a/railties/test/initializer_test.rb b/railties/test/initializer_test.rb
index d77a045e56..987a5ada86 100644
--- a/railties/test/initializer_test.rb
+++ b/railties/test/initializer_test.rb
@@ -309,9 +309,10 @@ class InitializerSetupI18nTests < Test::Unit::TestCase
config.i18n.load_path << "my/other/locale.yml"
Rails::Initializer.run(:initialize_i18n, config)
- assert_equal [
+ assert_equal [
File.expand_path(File.dirname(__FILE__) + "/../../activesupport/lib/active_support/locale/en.yml"),
File.expand_path(File.dirname(__FILE__) + "/../../actionpack/lib/action_view/locale/en.yml"),
+ File.expand_path(File.dirname(__FILE__) + "/../../activemodel/lib/active_model/locale/en.yml"),
File.expand_path(File.dirname(__FILE__) + "/../../activerecord/lib/active_record/locale/en.yml"),
"my/test/locale.yml",
"my/other/locale.yml" ], I18n.load_path.collect { |path| path =~ /^\./ ? File.expand_path(path) : path }
@@ -363,17 +364,31 @@ class InitializerDatabaseMiddlewareTest < Test::Unit::TestCase
ensure
ActionController::Base.session_store = store
end
+
+ def test_ensure_database_middleware_doesnt_use_action_controller_on_initializing
+ @config.frameworks -= [:action_controller]
+ store = ActionController::Base.session_store
+ ActionController::Base.session_store = ActiveRecord::SessionStore
+
+ @config.middleware.expects(:use).with(ActiveRecord::ConnectionAdapters::ConnectionManagement)
+ @config.middleware.expects(:use).with(ActiveRecord::QueryCache)
+
+ Rails::Initializer.run(:initialize_database_middleware, @config)
+ ensure
+ ActionController::Base.session_store = store
+ @config.frameworks += [:action_controller]
+ end
end
class InitializerViewPathsTest < Test::Unit::TestCase
def setup
@config = Rails::Configuration.new
@config.frameworks = [:action_view, :action_controller, :action_mailer]
-
+
ActionController::Base.stubs(:view_paths).returns(stub)
ActionMailer::Base.stubs(:view_paths).returns(stub)
end
-
+
def test_load_view_paths_doesnt_perform_anything_when_action_view_not_in_frameworks
@config.frameworks -= [:action_view]
ActionController::Base.view_paths.expects(:load!).never
@@ -390,4 +405,5 @@ class RailsRootTest < Test::Unit::TestCase
def test_rails_dot_root_should_be_a_pathname
assert_equal File.join(RAILS_ROOT, 'app', 'controllers'), Rails.root.join('app', 'controllers').to_s
end
-end \ No newline at end of file
+end
+
diff --git a/tools/profile_requires b/tools/profile
index 927467bc4e..927467bc4e 100755
--- a/tools/profile_requires
+++ b/tools/profile