aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXavier Noria <fxn@hashref.com>2010-05-09 11:46:45 +0200
committerXavier Noria <fxn@hashref.com>2010-05-09 11:46:45 +0200
commite1a0d86fe0355ddff8c86db0f42f3824ffe14c02 (patch)
tree5833022ca41905f243c15c8a5ffdf864de69bfa6
parent1ff3d951e620ddeeb97e87e2024391470e886467 (diff)
parentdf508bd97062b871fe25eda8d1bb61cd43d79bc4 (diff)
downloadrails-e1a0d86fe0355ddff8c86db0f42f3824ffe14c02.tar.gz
rails-e1a0d86fe0355ddff8c86db0f42f3824ffe14c02.tar.bz2
rails-e1a0d86fe0355ddff8c86db0f42f3824ffe14c02.zip
Merge remote branch 'rails/master'
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb2
-rw-r--r--actionpack/test/dispatch/request/xml_params_parsing_test.rb13
-rw-r--r--activemodel/CHANGELOG5
-rw-r--r--activemodel/lib/active_model/serializers/json.rb6
-rw-r--r--activemodel/lib/active_model/validations.rb17
-rw-r--r--activemodel/test/cases/helper.rb1
-rw-r--r--activemodel/test/cases/serializeration/json_serialization_test.rb16
-rw-r--r--activemodel/test/cases/tests_database.rb35
-rw-r--r--activemodel/test/cases/validations/acceptance_validation_test.rb33
-rw-r--r--activemodel/test/cases/validations/conditional_validation_test.rb44
-rw-r--r--activemodel/test/cases/validations/confirmation_validation_test.rb11
-rw-r--r--activemodel/test/cases/validations/exclusion_validation_test.rb12
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb37
-rw-r--r--activemodel/test/cases/validations/i18n_generate_message_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb4
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb25
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb196
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb13
-rw-r--r--activemodel/test/cases/validations/presence_validation_test.rb15
-rw-r--r--activemodel/test/cases/validations/validations_context_test.rb39
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb35
-rw-r--r--activemodel/test/cases/validations_test.rb89
-rw-r--r--activemodel/test/fixtures/topics.yml41
-rw-r--r--activemodel/test/models/developer.rb6
-rw-r--r--activemodel/test/models/reply.rb16
-rw-r--r--activemodel/test/models/topic.rb12
-rw-r--r--activemodel/test/schema.rb14
-rw-r--r--activerecord/CHANGELOG2
-rw-r--r--activerecord/lib/active_record.rb2
-rw-r--r--activerecord/lib/active_record/aggregations.rb1
-rwxr-xr-xactiverecord/lib/active_record/associations.rb16
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb11
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb100
-rw-r--r--activerecord/lib/active_record/autosave_association.rb6
-rwxr-xr-xactiverecord/lib/active_record/base.rb331
-rw-r--r--activerecord/lib/active_record/callbacks.rb48
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb27
-rw-r--r--activerecord/lib/active_record/counter_cache.rb107
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb27
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb230
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb16
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb22
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb8
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb7
-rw-r--r--activerecord/lib/active_record/timestamp.rb48
-rw-r--r--activerecord/lib/active_record/transactions.rb22
-rw-r--r--activerecord/lib/active_record/validations.rb69
-rwxr-xr-xactiverecord/test/cases/base_test.rb10
-rw-r--r--activerecord/test/cases/finder_test.rb18
-rw-r--r--activerecord/test/cases/locking_test.rb9
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb25
-rw-r--r--activerecord/test/cases/validations_test.rb2
-rw-r--r--activesupport/CHANGELOG6
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb8
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb56
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb5
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb7
-rw-r--r--railties/lib/rails/commands/dbconsole.rb4
-rw-r--r--railties/lib/rails/generators.rb46
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb5
-rw-r--r--railties/test/generators/app_generator_test.rb8
66 files changed, 1065 insertions, 1013 deletions
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index a25089176c..d4208ca96e 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -38,7 +38,7 @@ module ActionDispatch
when Proc
strategy.call(request.raw_post)
when :xml_simple, :xml_node
- data = Hash.from_xml(request.body) || {}
+ data = Hash.from_xml(request.body.read) || {}
request.body.rewind if request.body.respond_to?(:rewind)
data.with_indifferent_access
when :yaml
diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
index 488799ac2a..f2ce2c5b93 100644
--- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
@@ -16,6 +16,19 @@ class XmlParamsParsingTest < ActionController::IntegrationTest
TestController.last_request_parameters = nil
end
+ test "parses a strict rack.input" do
+ class Linted
+ def call(env)
+ bar = env['action_dispatch.request.request_parameters']['foo']
+ result = "<ok>#{bar}</ok>"
+ [200, {"Content-Type" => "application/xml", "Content-Length" => result.length.to_s}, result]
+ end
+ end
+ req = Rack::MockRequest.new(ActionDispatch::ParamsParser.new(Linted.new))
+ resp = req.post('/', "CONTENT_TYPE" => "application/xml", :input => "<foo>bar</foo>", :lint => true)
+ assert_equal "<ok>bar</ok>", resp.body
+ end
+
test "parses hash params" do
with_test_routing do
xml = "<person><name>David</name></person>"
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
index 74aec3bfd3..43cf67d2b7 100644
--- a/activemodel/CHANGELOG
+++ b/activemodel/CHANGELOG
@@ -1,3 +1,8 @@
+*Rails 3.0.0 [beta 4/release candidate] (unreleased)*
+
+* JSON supports a custom root option: to_json(:root => 'custom') #4515 [Jatinder Singh]
+
+
*Rails 3.0.0 [beta 3] (April 13th, 2010)*
* No changes
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index 794de7dc55..ffdfbfcaaf 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -79,7 +79,11 @@ module ActiveModel
# "title": "So I was thinking"}]}
def encode_json(encoder)
hash = serializable_hash(encoder.options)
- hash = { self.class.model_name.element => hash } if include_root_in_json
+ if include_root_in_json
+ custom_root = encoder.options && encoder.options[:root]
+ hash = { custom_root || self.class.model_name.element => hash }
+ end
+
ActiveSupport::JSON.encode(hash)
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index c69cabc888..7c705b8899 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -29,7 +29,7 @@ module ActiveModel
# person.invalid?
# #=> false
# person.first_name = 'zoolander'
- # person.valid?
+ # person.valid?
# #=> false
# person.invalid?
# #=> true
@@ -48,6 +48,8 @@ module ActiveModel
extend ActiveModel::Translation
define_callbacks :validate, :scope => :name
+ attr_accessor :validation_context
+
class_attribute :_validators
self._validators = Hash.new { |h,k| h[k] = [] }
end
@@ -117,7 +119,7 @@ module ActiveModel
options = args.last
if options.is_a?(Hash) && options.key?(:on)
options[:if] = Array.wrap(options[:if])
- options[:if] << "@_on_validate == :#{options[:on]}"
+ options[:if] << "validation_context == :#{options[:on]}"
end
set_callback(:validate, *args, &block)
end
@@ -150,15 +152,20 @@ module ActiveModel
end
# Runs all the specified validations and returns true if no errors were added otherwise false.
- def valid?
+ # Context can optionally be supplied to define which callbacks to test against (the context is
+ # defined on the validations using :on).
+ def valid?(context = nil)
+ current_context, self.validation_context = validation_context, context
errors.clear
_run_validate_callbacks
errors.empty?
+ ensure
+ self.validation_context = current_context
end
# Performs the opposite of <tt>valid?</tt>. Returns true if errors were added, false otherwise.
- def invalid?
- !valid?
+ def invalid?(context = nil)
+ !valid?(context)
end
# Hook method defining how an attribute value should be retieved. By default this is assumed
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index 8578ab7dbd..a81584bbad 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -5,6 +5,7 @@ $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
require 'config'
require 'active_model'
+require 'active_support/core_ext/string/access'
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
diff --git a/activemodel/test/cases/serializeration/json_serialization_test.rb b/activemodel/test/cases/serializeration/json_serialization_test.rb
index 81df52fcb9..7e89815c96 100644
--- a/activemodel/test/cases/serializeration/json_serialization_test.rb
+++ b/activemodel/test/cases/serializeration/json_serialization_test.rb
@@ -37,6 +37,22 @@ class JsonSerializationTest < ActiveModel::TestCase
end
end
+ test "should include custom root in json" do
+ begin
+ Contact.include_root_in_json = true
+ json = @contact.to_json(:root => 'json_contact')
+
+ assert_match %r{^\{"json_contact":\{}, json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_match %r{"age":16}, json
+ assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_match %r{"awesome":true}, json
+ assert_match %r{"preferences":\{"shows":"anime"\}}, json
+ ensure
+ Contact.include_root_in_json = false
+ end
+ end
+
test "should encode all encodable attributes" do
json = @contact.to_json
diff --git a/activemodel/test/cases/tests_database.rb b/activemodel/test/cases/tests_database.rb
deleted file mode 100644
index 8ca54d2678..0000000000
--- a/activemodel/test/cases/tests_database.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'logger'
-
-$:.unshift(File.dirname(__FILE__) + '/../../../activerecord/lib')
-require 'active_record'
-
-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:' }
-
- adapter = defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'
- options = defaults.merge :adapter => adapter, :timeout => 500
- ActiveRecord::Base.establish_connection(options)
- end
- end
-end
diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb
index 11c9c1edfd..de04e11258 100644
--- a/activemodel/test/cases/validations/acceptance_validation_test.rb
+++ b/activemodel/test/cases/validations/acceptance_validation_test.rb
@@ -1,57 +1,54 @@
# 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
def teardown
Topic.reset_callbacks(:validate)
end
def test_terms_of_service_agreement_no_acceptance
- Topic.validates_acceptance_of(:terms_of_service, :on => :create)
+ Topic.validates_acceptance_of(:terms_of_service)
- t = Topic.create("title" => "We should not be confirmed")
- assert t.save
+ t = Topic.new("title" => "We should not be confirmed")
+ assert t.valid?
end
def test_terms_of_service_agreement
- Topic.validates_acceptance_of(:terms_of_service, :on => :create)
+ Topic.validates_acceptance_of(:terms_of_service)
- t = Topic.create("title" => "We should be confirmed","terms_of_service" => "")
- assert !t.save
+ t = Topic.new("title" => "We should be confirmed","terms_of_service" => "")
+ assert t.invalid?
assert_equal ["must be accepted"], t.errors[:terms_of_service]
t.terms_of_service = "1"
- assert t.save
+ assert t.valid?
end
def test_eula
- Topic.validates_acceptance_of(:eula, :message => "must be abided", :on => :create)
+ Topic.validates_acceptance_of(:eula, :message => "must be abided")
- t = Topic.create("title" => "We should be confirmed","eula" => "")
- assert !t.save
+ t = Topic.new("title" => "We should be confirmed","eula" => "")
+ assert t.invalid?
assert_equal ["must be abided"], t.errors[:eula]
t.eula = "1"
- assert t.save
+ assert t.valid?
end
def test_terms_of_service_agreement_with_accept_value
- Topic.validates_acceptance_of(:terms_of_service, :on => :create, :accept => "I agree.")
+ Topic.validates_acceptance_of(:terms_of_service, :accept => "I agree.")
- t = Topic.create("title" => "We should be confirmed", "terms_of_service" => "")
- assert !t.save
+ t = Topic.new("title" => "We should be confirmed", "terms_of_service" => "")
+ assert t.invalid?
assert_equal ["must be accepted"], t.errors[:terms_of_service]
t.terms_of_service = "I agree."
- assert t.save
+ assert t.valid?
end
def test_validates_acceptance_of_for_ruby_class
diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb
index 6866bfcf24..3cb95b4a00 100644
--- a/activemodel/test/cases/validations/conditional_validation_test.rb
+++ b/activemodel/test/cases/validations/conditional_validation_test.rb
@@ -1,21 +1,19 @@
# encoding: utf-8
require 'cases/helper'
-require 'cases/tests_database'
require 'models/topic'
class ConditionalValidationTest < ActiveModel::TestCase
- include ActiveModel::TestsDatabase
def teardown
Topic.reset_callbacks(:validate)
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?
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
@@ -23,15 +21,15 @@ class ConditionalValidationTest < ActiveModel::TestCase
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")
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
- assert !t.errors[:title].any?
+ assert t.errors[:title].empty?
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")
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
end
@@ -39,8 +37,8 @@ class ConditionalValidationTest < ActiveModel::TestCase
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?
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
@@ -48,8 +46,8 @@ class ConditionalValidationTest < ActiveModel::TestCase
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?
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
@@ -57,7 +55,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
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")
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
end
@@ -65,7 +63,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
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")
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
end
@@ -73,8 +71,8 @@ class ConditionalValidationTest < ActiveModel::TestCase
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?
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
@@ -83,8 +81,8 @@ class ConditionalValidationTest < ActiveModel::TestCase
# 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?
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
@@ -93,7 +91,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
# 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")
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
end
@@ -102,7 +100,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
# 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")
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
end
@@ -111,8 +109,8 @@ class ConditionalValidationTest < ActiveModel::TestCase
# 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?
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
@@ -132,7 +130,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
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.invalid?, "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"
diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb
index 55554d5054..d0418170fa 100644
--- a/activemodel/test/cases/validations/confirmation_validation_test.rb
+++ b/activemodel/test/cases/validations/confirmation_validation_test.rb
@@ -1,13 +1,10 @@
# 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
def teardown
Topic.reset_callbacks(:validate)
@@ -20,7 +17,7 @@ class ConfirmationValidationTest < ActiveModel::TestCase
assert t.valid?
t.title_confirmation = "Parallel Lives"
- assert !t.valid?
+ assert t.invalid?
t.title_confirmation = nil
t.title = "Parallel Lives"
@@ -33,11 +30,11 @@ class ConfirmationValidationTest < ActiveModel::TestCase
def test_title_confirmation
Topic.validates_confirmation_of(:title)
- t = Topic.create("title" => "We should be confirmed","title_confirmation" => "")
- assert !t.save
+ t = Topic.new("title" => "We should be confirmed","title_confirmation" => "")
+ assert t.invalid?
t.title_confirmation = "We should be confirmed"
- assert t.save
+ assert t.valid?
end
def test_validates_confirmation_of_for_ruby_class
diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb
index fffd290fa3..be9d98d644 100644
--- a/activemodel/test/cases/validations/exclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/exclusion_validation_test.rb
@@ -1,12 +1,10 @@
# encoding: utf-8
require 'cases/helper'
-require 'cases/tests_database'
require 'models/topic'
require 'models/person'
class ExclusionValidationTest < ActiveModel::TestCase
- include ActiveModel::TestsDatabase
def teardown
Topic.reset_callbacks(:validate)
@@ -15,17 +13,17 @@ class ExclusionValidationTest < ActiveModel::TestCase
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?
+ assert Topic.new("title" => "something", "content" => "abc").valid?
+ assert Topic.new("title" => "monkey", "content" => "abc").invalid?
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")
+ assert Topic.new("title" => "something", "content" => "abc")
- t = Topic.create("title" => "monkey")
- assert !t.valid?
+ t = Topic.new("title" => "monkey")
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["option monkey is restricted"], t.errors[:title]
end
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 1aa6e30f6b..6c4fb36d52 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -1,13 +1,10 @@
# 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
def teardown
Topic.reset_callbacks(:validate)
@@ -16,15 +13,14 @@ class PresenceValidationTest < ActiveModel::TestCase
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"
+ t = Topic.new("title" => "i'm incorrect", "content" => "Validation macros rule!")
+ assert t.invalid?, "Shouldn't be valid"
assert_equal ["is bad data"], t.errors[:title]
assert t.errors[:content].empty?
t.title = "Validation macros rule!"
- assert t.save
+ assert t.valid?
assert t.errors[:title].empty?
assert_raise(ArgumentError) { Topic.validates_format_of(:title, :content) }
@@ -32,43 +28,44 @@ class PresenceValidationTest < ActiveModel::TestCase
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?
+ assert Topic.new("title" => "Shouldn't be valid").invalid?
+ assert Topic.new("title" => "").valid?
+ assert Topic.new("title" => nil).valid?
+ assert Topic.new("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"
+ t = Topic.new("title" => "72x", "content" => "6789")
+ assert t.invalid?, "Shouldn't be valid"
+
assert_equal ["is bad data"], t.errors[:title]
assert t.errors[:content].empty?
t.title = "-11"
- assert !t.valid?, "Shouldn't be valid"
+ assert t.invalid?, "Shouldn't be valid"
t.title = "03"
- assert !t.valid?, "Shouldn't be valid"
+ assert t.invalid?, "Shouldn't be valid"
t.title = "z44"
- assert !t.valid?, "Shouldn't be valid"
+ assert t.invalid?, "Shouldn't be valid"
t.title = "5v7"
- assert !t.valid?, "Shouldn't be valid"
+ assert t.invalid?, "Shouldn't be valid"
t.title = "1"
- assert t.save
+ assert t.valid?
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')
+ t = Topic.new(:title => 'Invalid title')
+ assert t.invalid?
assert_equal ["can't be Invalid title"], t.errors[:title]
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
index 3a644c92c9..58a8d179ad 100644
--- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -1,5 +1,4 @@
require "cases/helper"
-require 'cases/tests_database'
require 'models/person'
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index d65d94d599..547d80f46e 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -1,11 +1,9 @@
# -*- coding: utf-8 -*-
require "cases/helper"
-require 'cases/tests_database'
require 'models/person'
class I18nValidationTest < ActiveModel::TestCase
- include ActiveModel::TestsDatabase
def setup
Person.reset_callbacks(:validate)
@@ -63,7 +61,7 @@ class I18nValidationTest < ActiveModel::TestCase
assert_equal ["Field Name empty"], @person.errors.full_messages
end
- # ActiveRecord::Validations
+ # ActiveModel::Validations
# validates_confirmation_of w/ mocha
def test_validates_confirmation_of_generates_message
Person.validates_confirmation_of :title
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 45ff0175d1..0716b4f087 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -1,13 +1,10 @@
# 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
def teardown
Topic.reset_callbacks(:validate)
@@ -16,14 +13,14 @@ class InclusionValidationTest < ActiveModel::TestCase
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?
+ assert Topic.new("title" => "a!", "content" => "abc").invalid?
+ assert Topic.new("title" => "a b", "content" => "abc").invalid?
+ assert Topic.new("title" => nil, "content" => "def").invalid?
- t = Topic.create("title" => "a", "content" => "I know you are but what am I?")
+ t = Topic.new("title" => "a", "content" => "I know you are but what am I?")
assert t.valid?
t.title = "uhoh"
- assert !t.valid?
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["is not included in the list"], t.errors[:title]
@@ -38,18 +35,18 @@ class InclusionValidationTest < ActiveModel::TestCase
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?
+ assert Topic.new("title" => "a!", "content" => "abc").invalid?
+ assert Topic.new("title" => "", "content" => "abc").invalid?
+ assert Topic.new("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?
+ assert Topic.new("title" => "a", "content" => "abc").valid?
- t = Topic.create("title" => "uhoh", "content" => "abc")
- assert !t.valid?
+ t = Topic.new("title" => "uhoh", "content" => "abc")
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["option uhoh is not in the list"], t.errors[:title]
end
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index 254e823b7c..012c5a2f37 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -1,13 +1,10 @@
# 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
def teardown
Topic.reset_callbacks(:validate)
@@ -16,53 +13,53 @@ class LengthValidationTest < ActiveModel::TestCase
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?
+ assert Topic.new("title" => "ab").invalid?
+ assert Topic.new("title" => "").invalid?
+ assert Topic.new("title" => nil).valid?
+ assert Topic.new("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?
+ assert Topic.new("title" => "ab").invalid?
+ assert Topic.new("title" => "").valid?
+ assert Topic.new("title" => nil).valid?
+ assert Topic.new("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")
+ t = Topic.new("title" => "valid", "content" => "whatever")
assert t.valid?
t.title = "not"
- assert !t.valid?
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["is too short (minimum is 5 characters)"], t.errors[:title]
t.title = ""
- assert !t.valid?
+ assert t.invalid?
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.invalid?
assert t.errors[:title].any?
assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"]
end
def test_validates_length_of_using_maximum_should_allow_nil
Topic.validates_length_of :title, :maximum => 10
- t = Topic.create
+ t = Topic.new
assert t.valid?
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")
+ t = Topic.new("title" => "valid", "content" => "whatever")
assert t.valid?
t.title = nil
@@ -72,11 +69,11 @@ class LengthValidationTest < ActiveModel::TestCase
def test_validates_length_of_using_maximum
Topic.validates_length_of :title, :maximum => 5
- t = Topic.create("title" => "valid", "content" => "whatever")
+ t = Topic.new("title" => "valid", "content" => "whatever")
assert t.valid?
t.title = "notvalid"
- assert !t.valid?
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["is too long (maximum is 5 characters)"], t.errors[:title]
@@ -87,7 +84,7 @@ class LengthValidationTest < ActiveModel::TestCase
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")
+ t = Topic.new("title" => "valid", "content" => "whatever")
assert t.valid?
t.title = nil
@@ -98,13 +95,13 @@ class LengthValidationTest < ActiveModel::TestCase
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 t.invalid?
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 t.invalid?
assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title]
assert_equal ["is too short (minimum is 3 characters)"], t.errors[:content]
@@ -120,7 +117,7 @@ class LengthValidationTest < ActiveModel::TestCase
assert t.valid?
t.title = "Now I'm 10"
- assert !t.valid?
+ assert t.invalid?
assert_equal ["is too long (maximum is 9 characters)"], t.errors[:title]
t.title = "Four"
@@ -130,77 +127,35 @@ class LengthValidationTest < ActiveModel::TestCase
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')
+ t = Topic.new('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")
+ t = Topic.new("title" => "valid", "content" => "whatever")
assert t.valid?
t.title = "notvalid"
- assert !t.valid?
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["is the wrong length (should be 5 characters)"], t.errors[:title]
t.title = ""
- assert !t.valid?
+ assert t.invalid?
t.title = nil
- assert !t.valid?
+ assert t.invalid?
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")
+ t = Topic.new("title" => "valid", "content" => "whatever")
assert t.valid?
t.title = nil
@@ -231,61 +186,61 @@ class LengthValidationTest < ActiveModel::TestCase
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?
+ t = Topic.new("title" => "uhoh", "content" => "whatever")
+ assert t.invalid?
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?
+ t = Topic.new("title" => "uhoh", "content" => "whatever")
+ assert t.invalid?
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?
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
+ assert t.invalid?
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?
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["hoo 10"], t.errors["title"]
- t = Topic.create("title" => "uhohuhohuhohuhohuhohuhohuhohuhoh", "content" => "whatever")
- assert !t.valid?
+ t = Topic.new("title" => "uhohuhohuhohuhohuhohuhohuhohuhoh", "content" => "whatever")
+ assert t.invalid?
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?
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
+ assert t.invalid?
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?
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
+ assert t.invalid?
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?
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
@@ -294,11 +249,11 @@ class LengthValidationTest < ActiveModel::TestCase
with_kcode('UTF8') do
Topic.validates_length_of :title, :minimum => 5
- t = Topic.create("title" => "一二三四五", "content" => "whatever")
+ t = Topic.new("title" => "一二三四五", "content" => "whatever")
assert t.valid?
t.title = "一二三四"
- assert !t.valid?
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"]
end
@@ -308,11 +263,11 @@ class LengthValidationTest < ActiveModel::TestCase
with_kcode('UTF8') do
Topic.validates_length_of :title, :maximum => 5
- t = Topic.create("title" => "一二三四五", "content" => "whatever")
+ t = Topic.new("title" => "一二三四五", "content" => "whatever")
assert t.valid?
t.title = "一二34五六"
- assert !t.valid?
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["is too long (maximum is 5 characters)"], t.errors["title"]
end
@@ -323,7 +278,7 @@ class LengthValidationTest < ActiveModel::TestCase
Topic.validates_length_of(:title, :content, :within => 3..5)
t = Topic.new("title" => "一二", "content" => "12三四五六七")
- assert !t.valid?
+ assert t.invalid?
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 = "一二三"
@@ -336,10 +291,10 @@ class LengthValidationTest < ActiveModel::TestCase
with_kcode('UTF8') do
Topic.validates_length_of :title, :within => 3..5, :allow_nil => true
- t = Topic.create(:title => "一二三四五")
+ t = Topic.new(:title => "一二三四五")
assert t.valid?, t.errors.inspect
- t = Topic.create(:title => "一二三")
+ t = Topic.new(:title => "一二三")
assert t.valid?, t.errors.inspect
t.title = nil
@@ -347,60 +302,15 @@ class LengthValidationTest < ActiveModel::TestCase
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")
+ t = Topic.new("title" => "一二345", "content" => "whatever")
assert t.valid?
t.title = "一二345六"
- assert !t.valid?
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["is the wrong length (should be 5 characters)"], t.errors["title"]
end
@@ -409,11 +319,11 @@ class LengthValidationTest < ActiveModel::TestCase
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")
+ t = Topic.new(:content => "this content should be long enough")
assert t.valid?
t.content = "not long enough"
- assert !t.valid?
+ assert t.invalid?
assert t.errors[:content].any?
assert_equal ["Your essay must be at least 5 words."], t.errors[:content]
end
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index 1e73744649..be620c53fa 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -1,13 +1,10 @@
# 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
def teardown
Topic.reset_callbacks(:validate)
@@ -33,8 +30,8 @@ class NumericalityValidationTest < ActiveModel::TestCase
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)
+ invalid!(JUNK + BLANK)
+ valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
end
def test_validates_numericality_of_with_integer_only
@@ -47,8 +44,8 @@ class NumericalityValidationTest < ActiveModel::TestCase
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)
+ invalid!(JUNK + BLANK + FLOATS + BIGDECIMAL + INFINITY)
+ valid!(NIL + INTEGERS)
end
def test_validates_numericality_with_greater_than
@@ -166,7 +163,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
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.invalid?, "#{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
diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb
index c4d787dadb..b1450586a8 100644
--- a/activemodel/test/cases/validations/presence_validation_test.rb
+++ b/activemodel/test/cases/validations/presence_validation_test.rb
@@ -1,14 +1,11 @@
# encoding: utf-8
require 'cases/helper'
-require 'cases/tests_database'
require 'models/topic'
-require 'models/developer'
require 'models/person'
require 'models/custom_reader'
class PresenceValidationTest < ActiveModel::TestCase
- include ActiveModel::TestsDatabase
teardown do
Topic.reset_callbacks(:validate)
@@ -19,26 +16,26 @@ class PresenceValidationTest < ActiveModel::TestCase
def test_validate_presences
Topic.validates_presence_of(:title, :content)
- t = Topic.create
- assert !t.save
+ t = Topic.new
+ assert t.invalid?
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 t.invalid?
assert_equal ["can't be blank"], t.errors[:content]
t.content = "like stuff"
- assert t.save
+ assert t.valid?
end
test 'accepts array arguments' do
Topic.validates_presence_of %w(title content)
t = Topic.new
- assert !t.valid?
+ assert t.invalid?
assert_equal ["can't be blank"], t.errors[:title]
assert_equal ["can't be blank"], t.errors[:content]
end
@@ -46,7 +43,7 @@ class PresenceValidationTest < ActiveModel::TestCase
def test_validates_acceptance_of_with_custom_error_using_quotes
Person.validates_presence_of :karma, :message => "This string contains 'single' and \"double\" quotes"
p = Person.new
- assert !p.valid?
+ assert p.invalid?
assert_equal "This string contains 'single' and \"double\" quotes", p.errors[:karma].last
end
diff --git a/activemodel/test/cases/validations/validations_context_test.rb b/activemodel/test/cases/validations/validations_context_test.rb
new file mode 100644
index 0000000000..15a49e38dd
--- /dev/null
+++ b/activemodel/test/cases/validations/validations_context_test.rb
@@ -0,0 +1,39 @@
+# encoding: utf-8
+require 'cases/helper'
+
+require 'models/topic'
+
+class ValidationsContextTest < ActiveModel::TestCase
+
+ def teardown
+ Topic.reset_callbacks(:validate)
+ Topic._validators.clear
+ end
+
+ ERROR_MESSAGE = "Validation error from validator"
+
+ class ValidatorThatAddsErrors < ActiveModel::Validator
+ def validate(record)
+ record.errors[:base] << ERROR_MESSAGE
+ end
+ end
+
+ test "with a class that adds errors on update and validating a new model with no arguments" do
+ Topic.validates_with(ValidatorThatAddsErrors, :on => :create)
+ topic = Topic.new
+ assert topic.valid?, "Validation doesn't run on create if 'on' is set to update"
+ end
+
+ test "with a class that adds errors on update and validating a new model" do
+ Topic.validates_with(ValidatorThatAddsErrors, :on => :update)
+ topic = Topic.new
+ assert topic.valid?(:create), "Validation doesn't run on create if 'on' is set to update"
+ end
+
+ test "with a class that adds errors on create and validating a new model" do
+ Topic.validates_with(ValidatorThatAddsErrors, :on => :create)
+ topic = Topic.new
+ assert topic.invalid?(:create), "Validation does run on create if 'on' is set to create"
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+ end
+end \ No newline at end of file
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index 92df4dd6cd..6d825cd316 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -1,11 +1,9 @@
# encoding: utf-8
require 'cases/helper'
-require 'cases/tests_database'
require 'models/topic'
-class ValidatesWithTest < ActiveRecord::TestCase
- include ActiveModel::TestsDatabase
+class ValidatesWithTest < ActiveModel::TestCase
def teardown
Topic.reset_callbacks(:validate)
@@ -55,7 +53,7 @@ class ValidatesWithTest < ActiveRecord::TestCase
test "vaidation with class that adds errors" do
Topic.validates_with(ValidatorThatAddsErrors)
topic = Topic.new
- assert !topic.valid?, "A class that adds errors causes the record to be invalid"
+ assert topic.invalid?, "A class that adds errors causes the record to be invalid"
assert topic.errors[:base].include?(ERROR_MESSAGE)
end
@@ -65,23 +63,10 @@ class ValidatesWithTest < ActiveRecord::TestCase
assert topic.valid?, "A class that does not add errors does not cause the record to be invalid"
end
- test "with a class that adds errors on update and a new record" do
- Topic.validates_with(ValidatorThatAddsErrors, :on => :update)
- topic = Topic.new
- assert topic.valid?, "Validation doesn't run on create if 'on' is set to update"
- end
-
- test "with a class that adds errors on create and a new record" do
- Topic.validates_with(ValidatorThatAddsErrors, :on => :create)
- topic = Topic.new
- assert !topic.valid?, "Validation does run on create if 'on' is set to create"
- assert topic.errors[:base].include?(ERROR_MESSAGE)
- end
-
test "with multiple classes" do
Topic.validates_with(ValidatorThatAddsErrors, OtherValidatorThatAddsErrors)
topic = Topic.new
- assert !topic.valid?
+ assert topic.invalid?
assert topic.errors[:base].include?(ERROR_MESSAGE)
assert topic.errors[:base].include?(OTHER_ERROR_MESSAGE)
end
@@ -95,7 +80,7 @@ class ValidatesWithTest < ActiveRecord::TestCase
test "with if statements that return true" do
Topic.validates_with(ValidatorThatAddsErrors, :if => "1 == 1")
topic = Topic.new
- assert !topic.valid?
+ assert topic.invalid?
assert topic.errors[:base].include?(ERROR_MESSAGE)
end
@@ -108,7 +93,7 @@ class ValidatesWithTest < ActiveRecord::TestCase
test "with unless statements that returns false" do
Topic.validates_with(ValidatorThatAddsErrors, :unless => "1 == 2")
topic = Topic.new
- assert !topic.valid?
+ assert topic.invalid?
assert topic.errors[:base].include?(ERROR_MESSAGE)
end
@@ -121,7 +106,7 @@ class ValidatesWithTest < ActiveRecord::TestCase
Topic.validates_with(validator, :if => "1 == 1", :foo => :bar)
assert topic.valid?
end
-
+
test "calls setup method of validator passing in self when validator has setup method" do
topic = Topic.new
validator = stub_everything
@@ -132,7 +117,7 @@ class ValidatesWithTest < ActiveRecord::TestCase
Topic.validates_with(validator)
assert topic.valid?
end
-
+
test "doesn't call setup method of validator when validator has no setup method" do
topic = Topic.new
validator = stub_everything
@@ -147,14 +132,14 @@ class ValidatesWithTest < ActiveRecord::TestCase
test "validates_with with options" do
Topic.validates_with(ValidatorThatValidatesOptions, :field => :first_name)
topic = Topic.new
- assert !topic.valid?
+ assert topic.invalid?
assert topic.errors[:base].include?(ERROR_MESSAGE)
end
test "validates_with each validator" do
Topic.validates_with(ValidatorPerEachAttribute, :attributes => [:title, :content])
topic = Topic.new :title => "Title", :content => "Content"
- assert !topic.valid?
+ assert topic.invalid?
assert_equal ["Value is Title"], topic.errors[:title]
assert_equal ["Value is Content"], topic.errors[:content]
end
@@ -174,7 +159,7 @@ class ValidatesWithTest < ActiveRecord::TestCase
test "each validator skip nil values if :allow_nil is set to true" do
Topic.validates_with(ValidatorPerEachAttribute, :attributes => [:title, :content], :allow_nil => true)
topic = Topic.new :content => ""
- assert !topic.valid?
+ assert topic.invalid?
assert topic.errors[:title].empty?
assert_equal ["Value is "], topic.errors[:content]
end
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 925a68da91..578ffc27dd 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -1,14 +1,11 @@
# encoding: utf-8
require 'cases/helper'
-require 'cases/tests_database'
require 'models/topic'
require 'models/reply'
-require 'models/developer'
require 'models/custom_reader'
class ValidationsTest < ActiveModel::TestCase
- include ActiveModel::TestsDatabase
def setup
Topic._validators.clear
@@ -23,7 +20,7 @@ class ValidationsTest < ActiveModel::TestCase
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"
+ assert r.invalid?, "A reply without content shouldn't be saveable"
r.content = "Messa content!"
assert r.valid?, "A reply with content should be saveable"
@@ -32,46 +29,46 @@ class ValidationsTest < ActiveModel::TestCase
def test_single_attr_validation_and_error_msg
r = Reply.new
r.title = "There's no content!"
- assert !r.valid?
+ assert r.invalid?
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 ["is 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.invalid?
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_equal ["is 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 ["is 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
+ r.valid?
errors = []
r.errors.each {|attr, messages| errors << [attr.to_s, messages] }
- assert errors.include?(["title", "Empty"])
- assert errors.include?(["content", "Empty"])
+ assert errors.include?(["title", "is Empty"])
+ assert errors.include?(["content", "is 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
+ r.title = ""
+ r.content = ""
+ r.valid?
errors = r.errors.to_a
- assert_equal "Title is Wrong Create", errors[0]
- assert_equal "Title is Content Mismatch", errors[1]
+ assert_equal "Content is Empty", errors[0]
+ assert_equal "Title is Empty", errors[1]
assert_equal 2, r.errors.count
end
@@ -84,7 +81,7 @@ class ValidationsTest < ActiveModel::TestCase
def test_errors_on_base
r = Reply.new
r.content = "Mismatch"
- r.save
+ r.valid?
r.errors[:base] << "Reply is not dignifying"
errors = []
@@ -92,7 +89,7 @@ class ValidationsTest < ActiveModel::TestCase
assert_equal ["Reply is not dignifying"], r.errors[:base]
- assert errors.include?("Title Empty")
+ assert errors.include?("Title is Empty")
assert errors.include?("Reply is not dignifying")
assert_equal 2, r.errors.count
end
@@ -110,12 +107,12 @@ class ValidationsTest < ActiveModel::TestCase
hits += 1
end
t = Topic.new("title" => "valid", "content" => "whatever")
- assert !t.save
+ assert t.invalid?
assert_equal 4, hits
assert_equal %w(gotcha gotcha), t.errors[:title]
assert_equal %w(gotcha gotcha), t.errors[:content]
end
-
+
def test_validates_each_custom_reader
hits = 0
CustomReader.validates_each(:title, :content, [:title, :content]) do |record, attr|
@@ -123,7 +120,7 @@ class ValidationsTest < ActiveModel::TestCase
hits += 1
end
t = CustomReader.new("title" => "valid", "content" => "whatever")
- assert !t.valid?
+ assert t.invalid?
assert_equal 4, hits
assert_equal %w(gotcha gotcha), t.errors[:title]
assert_equal %w(gotcha gotcha), t.errors[:content]
@@ -131,49 +128,51 @@ class ValidationsTest < ActiveModel::TestCase
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?
+ t = Topic.new("title" => "Title", "content" => "whatever")
+ assert t.invalid?
assert t.errors[:title].any?
assert_equal ["will never be valid"], t.errors["title"]
end
def test_invalid_validator
Topic.validate :i_dont_exist
- assert_raise(NameError) { t = Topic.create }
+ assert_raise(NameError) do
+ t = Topic.new
+ t.valid?
+ end
end
def test_errors_to_xml
r = Reply.new :title => "Wrong Create"
- assert !r.valid?
+ assert r.invalid?
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>")
+ assert xml.include?("<error>Content is Empty</error>")
end
def test_validation_order
- Topic.validates_presence_of :title
- Topic.validates_length_of :title, :minimum => 2
+ 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
+ t = Topic.new("title" => "")
+ assert t.invalid?
+ assert_equal "can't be blank", t.errors["title"].first
Topic.validates_presence_of :title, :author_name
Topic.validate {|topic| topic.errors.add('author_email_address', 'will never be valid')}
Topic.validates_length_of :title, :content, :minimum => 2
t = Topic.new :title => ''
- assert !t.valid?
-
- assert_equal :title, key = t.errors.keys.first
- assert_equal "can't be blank", t.errors[key].first
- assert_equal 'is too short (minimum is 2 characters)', t.errors[key].second
- assert_equal :author_name, key = t.errors.keys.second
- assert_equal "can't be blank", t.errors[key].first
- assert_equal :author_email_address, key = t.errors.keys.third
- assert_equal 'will never be valid', t.errors[key].first
- assert_equal :content, key = t.errors.keys.fourth
- assert_equal 'is too short (minimum is 2 characters)', t.errors[key].first
+ assert t.invalid?
+
+ assert_equal :title, key = t.errors.keys[0]
+ assert_equal "can't be blank", t.errors[key][0]
+ assert_equal 'is too short (minimum is 2 characters)', t.errors[key][1]
+ assert_equal :author_name, key = t.errors.keys[1]
+ assert_equal "can't be blank", t.errors[key][0]
+ assert_equal :author_email_address, key = t.errors.keys[2]
+ assert_equal 'will never be valid', t.errors[key][0]
+ assert_equal :content, key = t.errors.keys[3]
+ assert_equal 'is too short (minimum is 2 characters)', t.errors[key][0]
end
def test_invalid_should_be_the_opposite_of_valid
@@ -227,7 +226,7 @@ class ValidationsTest < ActiveModel::TestCase
Topic.validates_presence_of(:title, :message => proc { "no blanks here".upcase })
t = Topic.new
- assert !t.valid?
+ assert t.invalid?
assert ["NO BLANKS HERE"], t.errors[:title]
end
diff --git a/activemodel/test/fixtures/topics.yml b/activemodel/test/fixtures/topics.yml
deleted file mode 100644
index e4c61ce2d8..0000000000
--- a/activemodel/test/fixtures/topics.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-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
deleted file mode 100644
index 5e6eefeed1..0000000000
--- a/activemodel/test/models/developer.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-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/reply.rb b/activemodel/test/models/reply.rb
index e86692677f..ec1efeac19 100644
--- a/activemodel/test/models/reply.rb
+++ b/activemodel/test/models/reply.rb
@@ -2,33 +2,31 @@ require 'models/topic'
class Reply < Topic
validate :errors_on_empty_content
- validate :title_is_wrong_create, :on => :create
+ validate :title_is_wrong_create, :on => :create
validate :check_empty_title
validate :check_content_mismatch, :on => :create
- validate :check_wrong_update, :on => :update
-
- attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read
+ validate :check_wrong_update, :on => :update
def check_empty_title
- errors[:title] << "Empty" unless attribute_present?("title")
+ errors[:title] << "is Empty" unless title && title.size > 0
end
def errors_on_empty_content
- errors[:content] << "Empty" unless attribute_present?("content")
+ errors[:content] << "is Empty" unless content && content.size > 0
end
def check_content_mismatch
- if attribute_present?("title") && attribute_present?("content") && content == "Mismatch"
+ if title && 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"
+ errors[:title] << "is Wrong Create" if title && title == "Wrong Create"
end
def check_wrong_update
- errors[:title] << "is Wrong Update" if attribute_present?("title") && title == "Wrong Update"
+ errors[:title] << "is Wrong Update" if title && title == "Wrong Update"
end
end
diff --git a/activemodel/test/models/topic.rb b/activemodel/test/models/topic.rb
index 1350aa17e7..f25b774cd7 100644
--- a/activemodel/test/models/topic.rb
+++ b/activemodel/test/models/topic.rb
@@ -1,4 +1,14 @@
-class Topic < ActiveRecord::Base
+class Topic
+ include ActiveModel::Validations
+
+ attr_accessor :title, :author_name, :content, :approved
+
+ def initialize(attributes = {})
+ attributes.each do |key, value|
+ send "#{key}=", value
+ end
+ end
+
def condition_is_true
true
end
diff --git a/activemodel/test/schema.rb b/activemodel/test/schema.rb
deleted file mode 100644
index 56b824d445..0000000000
--- a/activemodel/test/schema.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-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/activerecord/CHANGELOG b/activerecord/CHANGELOG
index ac5bd8e635..3f42fa34ef 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.0.0 [beta 4/release candidate] (unreleased)*
+* find_or_create_by_attr(value, ...) works when attr is protected. #4457 [Santiago Pastorino, Marc-André Lafortune]
+
* New callbacks: after_commit and after_rollback. Do expensive operations like image thumbnailing after_commit instead of after_save. #2991 [Brian Durand]
* Serialized attributes are not converted to YAML if they are any of the formats that can be serialized to XML (like Hash, Array and Strings). [José Valim]
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 6a6485f35e..e2f2508ae8 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -61,6 +61,7 @@ module ActiveRecord
autoload :Base
autoload :Callbacks
+ autoload :CounterCache
autoload :DynamicFinderMatch
autoload :DynamicScopeMatch
autoload :Migration
@@ -68,6 +69,7 @@ module ActiveRecord
autoload :NamedScope
autoload :NestedAttributes
autoload :Observer
+ autoload :Persistence
autoload :QueryCache
autoload :Reflection
autoload :Schema
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 08389907ef..45aaea062d 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -253,6 +253,7 @@ module ActiveRecord
raise ArgumentError, 'Converter must be a symbol denoting the converter method to call or a Proc to be invoked.'
end
end
+
mapping.each { |pair| self[pair.first] = part.send(pair.last) }
instance_variable_set("@#{name}", part.freeze)
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 6c64210c92..0a3c7c6a60 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1304,14 +1304,14 @@ module ActiveRecord
# Don't use a before_destroy callback since users' before_destroy
# callbacks will be executed after the association is wiped out.
- old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
- class_eval <<-end_eval unless method_defined?(old_method)
- alias_method :#{old_method}, :destroy_without_callbacks # alias_method :destroy_without_habtm_shim_for_posts, :destroy_without_callbacks
- def destroy_without_callbacks # def destroy_without_callbacks
- #{reflection.name}.clear # posts.clear
- #{old_method} # destroy_without_habtm_shim_for_posts
- end # end
- end_eval
+ include Module.new {
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def destroy # def destroy
+ super # super
+ #{reflection.name}.clear # posts.clear
+ end # end
+ RUBY
+ }
add_association_callbacks(reflection.name, options)
end
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index b9d0fe3abe..e88618d278 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -51,7 +51,7 @@ module ActiveRecord
alias_method :proxy_respond_to?, :respond_to?
alias_method :proxy_extend, :extend
delegate :to_param, :to => :proxy_target
- instance_methods.each { |m| undef_method m unless m =~ /^(?:nil\?|send|object_id|to_a)$|^__|proxy_/ }
+ instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|proxy_/ }
def initialize(owner, reflection)
@owner, @reflection = owner, reflection
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 3a9a67e3a2..c117271c71 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -18,10 +18,19 @@ module ActiveRecord
def instance_method_already_implemented?(method_name)
method_name = method_name.to_s
@_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map {|m| m.to_s }.to_set
- @@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map{|m| m.to_s }.to_set
+ @@_defined_activerecord_methods ||= defined_activerecord_methods
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
@_defined_class_methods.include?(method_name)
end
+
+ def defined_activerecord_methods
+ active_record = ActiveRecord::Base
+ super_klass = ActiveRecord::Base.superclass
+ methods = active_record.public_instance_methods - super_klass.public_instance_methods
+ methods += active_record.private_instance_methods - super_klass.private_instance_methods
+ methods += active_record.protected_instance_methods - super_klass.protected_instance_methods
+ methods.map {|m| m.to_s }.to_set
+ end
end
def method_missing(method_id, *args, &block)
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 36f2a9777c..dd44bd8d51 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -5,20 +5,20 @@ module ActiveRecord
module Dirty
extend ActiveSupport::Concern
include ActiveModel::Dirty
+ include AttributeMethods::Write
included do
- alias_method_chain :save, :dirty
- alias_method_chain :save!, :dirty
- alias_method_chain :update, :dirty
- alias_method_chain :reload, :dirty
+ if self < Timestamp
+ raise "You cannot include Dirty after Timestamp"
+ end
superclass_delegating_accessor :partial_updates
self.partial_updates = true
end
# Attempts to +save+ the record and clears changed attributes if successful.
- def save_with_dirty(*args) #:nodoc:
- if status = save_without_dirty(*args)
+ def save(*) #:nodoc:
+ if status = super
@previously_changed = changes
@changed_attributes.clear
end
@@ -26,70 +26,70 @@ module ActiveRecord
end
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
- def save_with_dirty!(*args) #:nodoc:
- save_without_dirty!(*args).tap do
+ def save!(*) #:nodoc:
+ super.tap do
@previously_changed = changes
@changed_attributes.clear
end
end
# <tt>reload</tt> the record and clears changed attributes.
- def reload_with_dirty(*args) #:nodoc:
- reload_without_dirty(*args).tap do
+ def reload(*) #:nodoc:
+ super.tap do
@previously_changed.clear
@changed_attributes.clear
end
end
- private
- # Wrap write_attribute to remember original attribute value.
- def write_attribute(attr, value)
- attr = attr.to_s
+ private
+ # Wrap write_attribute to remember original attribute value.
+ def write_attribute(attr, value)
+ attr = attr.to_s
- # The attribute already has an unsaved change.
- if attribute_changed?(attr)
- old = @changed_attributes[attr]
- @changed_attributes.delete(attr) unless field_changed?(attr, old, value)
- else
- old = clone_attribute_value(:read_attribute, attr)
- # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
- old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
- @changed_attributes[attr] = old if field_changed?(attr, old, value)
- end
+ # The attribute already has an unsaved change.
+ if attribute_changed?(attr)
+ old = @changed_attributes[attr]
+ @changed_attributes.delete(attr) unless field_changed?(attr, old, value)
+ else
+ old = clone_attribute_value(:read_attribute, attr)
+ # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
+ old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
+ @changed_attributes[attr] = old if field_changed?(attr, old, value)
+ end
- # Carry on.
- super(attr, value)
+ # Carry on.
+ super(attr, value)
+ end
+
+ def update(*)
+ if partial_updates?
+ # Serialized attributes should always be written in case they've been
+ # changed in place.
+ super(changed | (attributes.keys & self.class.serialized_attributes.keys))
+ else
+ super
end
+ end
- def update_with_dirty
- if partial_updates?
- # Serialized attributes should always be written in case they've been
- # changed in place.
- update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
+ def field_changed?(attr, old, value)
+ if column = column_for_attribute(attr)
+ if column.number? && column.null && (old.nil? || old == 0) && value.blank?
+ # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
+ # Hence we don't record it as a change if the value changes from nil to ''.
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
+ # be typecast back to 0 (''.to_i => 0)
+ value = nil
else
- update_without_dirty
+ value = column.type_cast(value)
end
end
- def field_changed?(attr, old, value)
- if column = column_for_attribute(attr)
- if column.number? && column.null && (old.nil? || old == 0) && value.blank?
- # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
- # Hence we don't record it as a change if the value changes from nil to ''.
- # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
- # be typecast back to 0 (''.to_i => 0)
- value = nil
- else
- value = column.type_cast(value)
- end
- end
-
- old != value
- end
+ old != value
+ end
- def clone_with_time_zone_conversion_attribute?(attr, old)
- old.class.name == "Time" && time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
- end
+ def clone_with_time_zone_conversion_attribute?(attr, old)
+ old.class.name == "Time" && time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 325a8aa7ec..fd1082a268 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -130,8 +130,6 @@ module ActiveRecord
ASSOCIATION_TYPES = %w{ has_one belongs_to has_many has_and_belongs_to_many }
included do
- alias_method_chain :reload, :autosave_associations
-
ASSOCIATION_TYPES.each do |type|
send("valid_keys_for_#{type}_association") << :autosave
end
@@ -196,9 +194,9 @@ module ActiveRecord
end
# Reloads the attributes of the object as usual and removes a mark for destruction.
- def reload_with_autosave_associations(options = nil)
+ def reload(options = nil)
@marked_for_destruction = false
- reload_without_autosave_associations(options)
+ super
end
# Marks this record to be destroyed as part of the parents save transaction.
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 9ed53cc4af..650a91b385 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -480,110 +480,6 @@ module ActiveRecord #:nodoc:
connection.select_value(sql, "#{name} Count").to_i
end
- # Resets one or more counter caches to their correct value using an SQL
- # count query. This is useful when adding new counter caches, or if the
- # counter has been corrupted or modified directly by SQL.
- #
- # ==== Parameters
- #
- # * +id+ - The id of the object you wish to reset a counter on.
- # * +counters+ - One or more counter names to reset
- #
- # ==== Examples
- #
- # # For Post with id #1 records reset the comments_count
- # Post.reset_counters(1, :comments)
- def reset_counters(id, *counters)
- object = find(id)
- counters.each do |association|
- child_class = reflect_on_association(association).klass
- counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column
-
- connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE")
- end
- end
-
- # A generic "counter updater" implementation, intended primarily to be
- # used by increment_counter and decrement_counter, but which may also
- # be useful on its own. It simply does a direct SQL update for the record
- # with the given ID, altering the given hash of counters by the amount
- # given by the corresponding value:
- #
- # ==== Parameters
- #
- # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
- # * +counters+ - An Array of Hashes containing the names of the fields
- # to update as keys and the amount to update the field by as values.
- #
- # ==== Examples
- #
- # # For the Post with id of 5, decrement the comment_count by 1, and
- # # increment the action_count by 1
- # Post.update_counters 5, :comment_count => -1, :action_count => 1
- # # Executes the following SQL:
- # # UPDATE posts
- # # SET comment_count = comment_count - 1,
- # # action_count = action_count + 1
- # # WHERE id = 5
- #
- # # For the Posts with id of 10 and 15, increment the comment_count by 1
- # Post.update_counters [10, 15], :comment_count => 1
- # # Executes the following SQL:
- # # UPDATE posts
- # # SET comment_count = comment_count + 1,
- # # WHERE id IN (10, 15)
- def update_counters(id, counters)
- updates = counters.inject([]) { |list, (counter_name, increment)|
- sign = increment < 0 ? "-" : "+"
- list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
- }.join(", ")
-
- if id.is_a?(Array)
- ids_list = id.map {|i| quote_value(i)}.join(', ')
- condition = "IN (#{ids_list})"
- else
- condition = "= #{quote_value(id)}"
- end
-
- update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}")
- end
-
- # Increment a number field by one, usually representing a count.
- #
- # This is used for caching aggregate values, so that they don't need to be computed every time.
- # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
- # shown it would have to run an SQL query to find how many posts and comments there are.
- #
- # ==== Parameters
- #
- # * +counter_name+ - The name of the field that should be incremented.
- # * +id+ - The id of the object that should be incremented.
- #
- # ==== Examples
- #
- # # Increment the post_count column for the record with an id of 5
- # DiscussionBoard.increment_counter(:post_count, 5)
- def increment_counter(counter_name, id)
- update_counters(id, counter_name => 1)
- end
-
- # Decrement a number field by one, usually representing a count.
- #
- # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
- #
- # ==== Parameters
- #
- # * +counter_name+ - The name of the field that should be decremented.
- # * +id+ - The id of the object that should be decremented.
- #
- # ==== Examples
- #
- # # Decrement the post_count column for the record with an id of 5
- # DiscussionBoard.decrement_counter(:post_count, 5)
- def decrement_counter(counter_name, id)
- update_counters(id, counter_name => -1)
- end
-
# Attributes named in this macro are protected from mass-assignment,
# such as <tt>new(attributes)</tt>,
# <tt>update_attributes(attributes)</tt>, or
@@ -1623,186 +1519,6 @@ module ActiveRecord #:nodoc:
quote_value(id, column_for_attribute(self.class.primary_key))
end
- # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
- def new_record?
- @new_record
- end
-
- # Returns true if this object has been destroyed, otherwise returns false.
- def destroyed?
- @destroyed
- end
-
- # Returns if the record is persisted, i.e. it's not a new record and it was not destroyed.
- def persisted?
- !(new_record? || destroyed?)
- end
-
- # :call-seq:
- # save(options)
- #
- # Saves the model.
- #
- # If the model is new a record gets created in the database, otherwise
- # the existing record gets updated.
- #
- # By default, save always run validations. If any of them fail the action
- # is cancelled and +save+ returns +false+. However, if you supply
- # :validate => false, validations are bypassed altogether. See
- # ActiveRecord::Validations for more information.
- #
- # There's a series of callbacks associated with +save+. If any of the
- # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
- # +save+ returns +false+. See ActiveRecord::Callbacks for further
- # details.
- def save
- create_or_update
- end
-
- # Saves the model.
- #
- # If the model is new a record gets created in the database, otherwise
- # the existing record gets updated.
- #
- # With <tt>save!</tt> validations always run. If any of them fail
- # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
- # for more information.
- #
- # There's a series of callbacks associated with <tt>save!</tt>. If any of
- # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
- # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
- # ActiveRecord::Callbacks for further details.
- def save!
- create_or_update || raise(RecordNotSaved)
- end
-
- # Deletes the record in the database and freezes this instance to
- # reflect that no changes should be made (since they can't be
- # persisted). Returns the frozen instance.
- #
- # The row is simply removed with a SQL +DELETE+ statement on the
- # record's primary key, and no callbacks are executed.
- #
- # To enforce the object's +before_destroy+ and +after_destroy+
- # callbacks, Observer methods, or any <tt>:dependent</tt> association
- # options, use <tt>#destroy</tt>.
- def delete
- self.class.delete(id) if persisted?
- @destroyed = true
- freeze
- end
-
- # Deletes the record in the database and freezes this instance to reflect that no changes should
- # be made (since they can't be persisted).
- def destroy
- if persisted?
- self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
- end
-
- @destroyed = true
- freeze
- end
-
- # Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to
- # single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record
- # identification in Action Pack to allow, say, <tt>Client < Company</tt> to do something like render <tt>:partial => @client.becomes(Company)</tt>
- # to render that instance using the companies/company partial instead of clients/client.
- #
- # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
- # instance will affect the other.
- def becomes(klass)
- became = klass.new
- became.instance_variable_set("@attributes", @attributes)
- became.instance_variable_set("@attributes_cache", @attributes_cache)
- became.instance_variable_set("@new_record", new_record?)
- became.instance_variable_set("@destroyed", destroyed?)
- became
- end
-
- # Updates a single attribute and saves the record without going through the normal validation procedure.
- # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
- # in Base is replaced with this when the validations module is mixed in, which it is by default.
- def update_attribute(name, value)
- send("#{name}=", value)
- save(:validate => false)
- end
-
- # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
- # fail and false will be returned.
- def update_attributes(attributes)
- self.attributes = attributes
- save
- end
-
- # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
- def update_attributes!(attributes)
- self.attributes = attributes
- save!
- end
-
- # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
- # The increment is performed directly on the underlying attribute, no setter is invoked.
- # Only makes sense for number-based attributes. Returns +self+.
- def increment(attribute, by = 1)
- self[attribute] ||= 0
- self[attribute] += by
- self
- end
-
- # Wrapper around +increment+ that saves the record. This method differs from
- # its non-bang version in that it passes through the attribute setter.
- # Saving is not subjected to validation checks. Returns +true+ if the
- # record could be saved.
- def increment!(attribute, by = 1)
- increment(attribute, by).update_attribute(attribute, self[attribute])
- end
-
- # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
- # The decrement is performed directly on the underlying attribute, no setter is invoked.
- # Only makes sense for number-based attributes. Returns +self+.
- def decrement(attribute, by = 1)
- self[attribute] ||= 0
- self[attribute] -= by
- self
- end
-
- # Wrapper around +decrement+ that saves the record. This method differs from
- # its non-bang version in that it passes through the attribute setter.
- # Saving is not subjected to validation checks. Returns +true+ if the
- # record could be saved.
- def decrement!(attribute, by = 1)
- decrement(attribute, by).update_attribute(attribute, self[attribute])
- end
-
- # Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
- # if the predicate returns +true+ the attribute will become +false+. This
- # method toggles directly the underlying value without calling any setter.
- # Returns +self+.
- def toggle(attribute)
- self[attribute] = !send("#{attribute}?")
- self
- end
-
- # Wrapper around +toggle+ that saves the record. This method differs from
- # its non-bang version in that it passes through the attribute setter.
- # Saving is not subjected to validation checks. Returns +true+ if the
- # record could be saved.
- def toggle!(attribute)
- toggle(attribute).update_attribute(attribute, self[attribute])
- end
-
- # Reloads the attributes of this object from the database.
- # The optional options argument is passed to find when reloading so you
- # may do e.g. record.reload(:lock => true) to reload the same record with
- # an exclusive row lock.
- def reload(options = nil)
- clear_aggregation_cache
- clear_association_cache
- @attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
- @attributes_cache = {}
- self
- end
-
# Returns true if the given attribute is in the attributes hash
def has_attribute?(attr_name)
@attributes.has_key?(attr_name.to_s)
@@ -1980,40 +1696,6 @@ module ActiveRecord #:nodoc:
end
private
- def create_or_update
- raise ReadOnlyRecord if readonly?
- result = new_record? ? create : update
- result != false
- end
-
- # Updates the associated record with values matching those of the instance attributes.
- # Returns the number of affected rows.
- def update(attribute_names = @attributes.keys)
- attributes_with_values = arel_attributes_values(false, false, attribute_names)
- return 0 if attributes_with_values.empty?
- self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
- end
-
- # Creates a record with values matching those of the instance attributes
- # and returns its id.
- def create
- if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
- self.id = connection.next_sequence_value(self.class.sequence_name)
- end
-
- attributes_values = arel_attributes_values
-
- new_id = if attributes_values.empty?
- self.class.unscoped.insert connection.empty_insert_statement_value
- else
- self.class.unscoped.insert attributes_values
- end
-
- self.id ||= new_id
-
- @new_record = false
- id
- end
# Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendant.
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to
@@ -2099,17 +1781,6 @@ module ActiveRecord #:nodoc:
instance_eval("%@#{sql.gsub('@', '\@')}@")
end
- # Initializes the attributes array with keys matching the columns from the linked table and
- # the values matching the corresponding default value of that column, so
- # that a new instance, or one populated from a passed-in Hash, still has all the attributes
- # that instances loaded from the database would.
- def attributes_from_column_definition
- self.class.columns.inject({}) do |attributes, column|
- attributes[column.name] = column.default unless column.name == self.class.primary_key
- attributes
- end
- end
-
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
@@ -2225,12 +1896,14 @@ module ActiveRecord #:nodoc:
end
Base.class_eval do
+ include ActiveRecord::Persistence
extend ActiveModel::Naming
extend QueryCache::ClassMethods
extend ActiveSupport::Benchmarkable
include ActiveModel::Conversion
include Validations
+ extend CounterCache
include Locking::Optimistic, Locking::Pessimistic
include AttributeMethods
include AttributeMethods::Read, AttributeMethods::Write, AttributeMethods::BeforeTypeCast, AttributeMethods::Query
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 7ebeb6079e..498836aca4 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -233,10 +233,6 @@ module ActiveRecord
]
included do
- [:create_or_update, :valid?, :create, :update, :destroy].each do |method|
- alias_method_chain method, :callbacks
- end
-
extend ActiveModel::Callbacks
define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
@@ -273,45 +269,33 @@ module ActiveRecord
end
end
- def create_or_update_with_callbacks #:nodoc:
- _run_save_callbacks do
- create_or_update_without_callbacks
- end
+ def valid?(*) #:nodoc:
+ @_on_validate = new_record? ? :create : :update
+ _run_validation_callbacks { super }
end
- private :create_or_update_with_callbacks
- def create_with_callbacks #:nodoc:
- _run_create_callbacks do
- create_without_callbacks
- end
+ def destroy #:nodoc:
+ _run_destroy_callbacks { super }
end
- private :create_with_callbacks
- def update_with_callbacks(*args) #:nodoc:
- _run_update_callbacks do
- update_without_callbacks(*args)
+ def deprecated_callback_method(symbol) #:nodoc:
+ if respond_to?(symbol, true)
+ ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead")
+ send(symbol)
end
end
- private :update_with_callbacks
- def valid_with_callbacks? #:nodoc:
- @_on_validate = new_record? ? :create : :update
- _run_validation_callbacks do
- valid_without_callbacks?
- end
+ private
+ def create_or_update #:nodoc:
+ _run_save_callbacks { super }
end
- def destroy_with_callbacks #:nodoc:
- _run_destroy_callbacks do
- destroy_without_callbacks
- end
+ def create #:nodoc:
+ _run_create_callbacks { super }
end
- def deprecated_callback_method(symbol) #:nodoc:
- if respond_to?(symbol, true)
- ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead")
- send(symbol)
- end
+ def update(*) #:nodoc:
+ _run_update_callbacks { super }
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index 2493095a04..db17bb348a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -10,7 +10,7 @@ module ActiveRecord
##
# :singleton-method:
# The connection handler
- class_inheritable_accessor :connection_handler, :instance_writer => false
+ class_attribute :connection_handler, :instance_writer => false
self.connection_handler = ConnectionAdapters::ConnectionHandler.new
# Returns the connection currently associated with the class. This can
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 533a7bb8e6..78fffaff6e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -5,23 +5,16 @@ module ActiveRecord
module QueryCache
class << self
def included(base)
- base.class_eval do
- alias_method_chain :columns, :query_cache
- alias_method_chain :select_all, :query_cache
- end
-
dirties_query_cache base, :insert, :update, :delete
end
def dirties_query_cache(base, *method_names)
method_names.each do |method_name|
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
- def #{method_name}_with_query_dirty(*args) # def update_with_query_dirty(*args)
- clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
- #{method_name}_without_query_dirty(*args) # update_without_query_dirty(*args)
- end # end
- #
- alias_method_chain :#{method_name}, :query_dirty # alias_method_chain :update, :query_dirty
+ def #{method_name}(*) # def update_with_query_dirty(*args)
+ clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
+ super # update_without_query_dirty(*args)
+ end # end
end_code
end
end
@@ -56,19 +49,19 @@ module ActiveRecord
@query_cache.clear
end
- def select_all_with_query_cache(*args)
+ def select_all(*args)
if @query_cache_enabled
- cache_sql(args.first) { select_all_without_query_cache(*args) }
+ cache_sql(args.first) { super }
else
- select_all_without_query_cache(*args)
+ super
end
end
- def columns_with_query_cache(*args)
+ def columns(*)
if @query_cache_enabled
- @query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args)
+ @query_cache["SHOW FIELDS FROM #{args.first}"] ||= super
else
- columns_without_query_cache(*args)
+ super
end
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
new file mode 100644
index 0000000000..cbebded995
--- /dev/null
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -0,0 +1,107 @@
+module ActiveRecord
+ module CounterCache
+ # Resets one or more counter caches to their correct value using an SQL
+ # count query. This is useful when adding new counter caches, or if the
+ # counter has been corrupted or modified directly by SQL.
+ #
+ # ==== Parameters
+ #
+ # * +id+ - The id of the object you wish to reset a counter on.
+ # * +counters+ - One or more counter names to reset
+ #
+ # ==== Examples
+ #
+ # # For Post with id #1 records reset the comments_count
+ # Post.reset_counters(1, :comments)
+ def reset_counters(id, *counters)
+ object = find(id)
+ counters.each do |association|
+ child_class = reflect_on_association(association).klass
+ counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column
+
+ connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE")
+ end
+ end
+
+ # A generic "counter updater" implementation, intended primarily to be
+ # used by increment_counter and decrement_counter, but which may also
+ # be useful on its own. It simply does a direct SQL update for the record
+ # with the given ID, altering the given hash of counters by the amount
+ # given by the corresponding value:
+ #
+ # ==== Parameters
+ #
+ # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
+ # * +counters+ - An Array of Hashes containing the names of the fields
+ # to update as keys and the amount to update the field by as values.
+ #
+ # ==== Examples
+ #
+ # # For the Post with id of 5, decrement the comment_count by 1, and
+ # # increment the action_count by 1
+ # Post.update_counters 5, :comment_count => -1, :action_count => 1
+ # # Executes the following SQL:
+ # # UPDATE posts
+ # # SET comment_count = comment_count - 1,
+ # # action_count = action_count + 1
+ # # WHERE id = 5
+ #
+ # # For the Posts with id of 10 and 15, increment the comment_count by 1
+ # Post.update_counters [10, 15], :comment_count => 1
+ # # Executes the following SQL:
+ # # UPDATE posts
+ # # SET comment_count = comment_count + 1,
+ # # WHERE id IN (10, 15)
+ def update_counters(id, counters)
+ updates = counters.inject([]) { |list, (counter_name, increment)|
+ sign = increment < 0 ? "-" : "+"
+ list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
+ }.join(", ")
+
+ if id.is_a?(Array)
+ ids_list = id.map {|i| quote_value(i)}.join(', ')
+ condition = "IN (#{ids_list})"
+ else
+ condition = "= #{quote_value(id)}"
+ end
+
+ update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}")
+ end
+
+ # Increment a number field by one, usually representing a count.
+ #
+ # This is used for caching aggregate values, so that they don't need to be computed every time.
+ # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
+ # shown it would have to run an SQL query to find how many posts and comments there are.
+ #
+ # ==== Parameters
+ #
+ # * +counter_name+ - The name of the field that should be incremented.
+ # * +id+ - The id of the object that should be incremented.
+ #
+ # ==== Examples
+ #
+ # # Increment the post_count column for the record with an id of 5
+ # DiscussionBoard.increment_counter(:post_count, 5)
+ def increment_counter(counter_name, id)
+ update_counters(id, counter_name => 1)
+ end
+
+ # Decrement a number field by one, usually representing a count.
+ #
+ # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
+ #
+ # ==== Parameters
+ #
+ # * +counter_name+ - The name of the field that should be decremented.
+ # * +id+ - The id of the object that should be decremented.
+ #
+ # ==== Examples
+ #
+ # # Decrement the post_count column for the record with an id of 5
+ # DiscussionBoard.decrement_counter(:post_count, 5)
+ def decrement_counter(counter_name, id)
+ update_counters(id, counter_name => -1)
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 60ad23f38c..71057efa15 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -48,10 +48,6 @@ module ActiveRecord
cattr_accessor :lock_optimistically, :instance_writer => false
self.lock_optimistically = true
- alias_method_chain :update, :lock
- alias_method_chain :destroy, :lock
- alias_method_chain :attributes_from_column_definition, :lock
-
class << self
alias_method :locking_column=, :set_locking_column
end
@@ -62,8 +58,8 @@ module ActiveRecord
end
private
- def attributes_from_column_definition_with_lock
- result = attributes_from_column_definition_without_lock
+ def attributes_from_column_definition
+ result = super
# If the locking column has no default value set,
# start the lock version at zero. Note we can't use
@@ -77,8 +73,8 @@ module ActiveRecord
return result
end
- def update_with_lock(attribute_names = @attributes.keys) #:nodoc:
- return update_without_lock(attribute_names) unless locking_enabled?
+ def update(attribute_names = @attributes.keys) #:nodoc:
+ return super unless locking_enabled?
return 0 if attribute_names.empty?
lock_col = self.class.locking_column
@@ -97,7 +93,6 @@ module ActiveRecord
)
).arel.update(arel_attributes_values(false, false, attribute_names))
-
unless affected_rows == 1
raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
end
@@ -111,8 +106,8 @@ module ActiveRecord
end
end
- def destroy_with_lock #:nodoc:
- return destroy_without_lock unless locking_enabled?
+ def destroy #:nodoc:
+ return super unless locking_enabled?
unless new_record?
lock_col = self.class.locking_column
@@ -136,12 +131,6 @@ module ActiveRecord
module ClassMethods
DEFAULT_LOCKING_COLUMN = 'lock_version'
- def self.extended(base)
- class <<base
- alias_method_chain :update_counters, :lock
- end
- end
-
# Is optimistic locking enabled for this table? Returns true if the
# +lock_optimistically+ flag is set to true (which it is, by default)
# and the table includes the +locking_column+ column (defaults to
@@ -173,9 +162,9 @@ module ActiveRecord
# Make sure the lock version column gets updated when counters are
# updated.
- def update_counters_with_lock(id, counters)
+ def update_counters(id, counters)
counters = counters.merge(locking_column => 1) if locking_enabled?
- update_counters_without_lock(id, counters)
+ super
end
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 6718b4a69d..eb9e792dd8 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -355,7 +355,7 @@ module ActiveRecord
association.to_a
else
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
- attribute_ids.present? ? association.all(:conditions => {:id => attribute_ids}) : []
+ attribute_ids.present? ? association.all(:conditions => {association.primary_key => attribute_ids}) : []
end
attributes_collection.each do |attributes|
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
new file mode 100644
index 0000000000..10788630a5
--- /dev/null
+++ b/activerecord/lib/active_record/persistence.rb
@@ -0,0 +1,230 @@
+module ActiveRecord
+ module Persistence
+ # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
+ def new_record?
+ @new_record
+ end
+
+ # Returns true if this object has been destroyed, otherwise returns false.
+ def destroyed?
+ @destroyed
+ end
+
+ # Returns if the record is persisted, i.e. it's not a new record and it was not destroyed.
+ def persisted?
+ !(new_record? || destroyed?)
+ end
+
+ # :call-seq:
+ # save(options)
+ #
+ # Saves the model.
+ #
+ # If the model is new a record gets created in the database, otherwise
+ # the existing record gets updated.
+ #
+ # By default, save always run validations. If any of them fail the action
+ # is cancelled and +save+ returns +false+. However, if you supply
+ # :validate => false, validations are bypassed altogether. See
+ # ActiveRecord::Validations for more information.
+ #
+ # There's a series of callbacks associated with +save+. If any of the
+ # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
+ # +save+ returns +false+. See ActiveRecord::Callbacks for further
+ # details.
+ def save(*)
+ create_or_update
+ end
+
+ # Saves the model.
+ #
+ # If the model is new a record gets created in the database, otherwise
+ # the existing record gets updated.
+ #
+ # With <tt>save!</tt> validations always run. If any of them fail
+ # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
+ # for more information.
+ #
+ # There's a series of callbacks associated with <tt>save!</tt>. If any of
+ # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
+ # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
+ # ActiveRecord::Callbacks for further details.
+ def save!(*)
+ create_or_update || raise(RecordNotSaved)
+ end
+
+ # Deletes the record in the database and freezes this instance to
+ # reflect that no changes should be made (since they can't be
+ # persisted). Returns the frozen instance.
+ #
+ # The row is simply removed with a SQL +DELETE+ statement on the
+ # record's primary key, and no callbacks are executed.
+ #
+ # To enforce the object's +before_destroy+ and +after_destroy+
+ # callbacks, Observer methods, or any <tt>:dependent</tt> association
+ # options, use <tt>#destroy</tt>.
+ def delete
+ self.class.delete(id) if persisted?
+ @destroyed = true
+ freeze
+ end
+
+ # Deletes the record in the database and freezes this instance to reflect that no changes should
+ # be made (since they can't be persisted).
+ def destroy
+ if persisted?
+ self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
+ end
+
+ @destroyed = true
+ freeze
+ end
+
+ # Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to
+ # single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record
+ # identification in Action Pack to allow, say, <tt>Client < Company</tt> to do something like render <tt>:partial => @client.becomes(Company)</tt>
+ # to render that instance using the companies/company partial instead of clients/client.
+ #
+ # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
+ # instance will affect the other.
+ def becomes(klass)
+ became = klass.new
+ became.instance_variable_set("@attributes", @attributes)
+ became.instance_variable_set("@attributes_cache", @attributes_cache)
+ became.instance_variable_set("@new_record", new_record?)
+ became.instance_variable_set("@destroyed", destroyed?)
+ became
+ end
+
+ # Updates a single attribute and saves the record without going through the normal validation procedure.
+ # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
+ # in Base is replaced with this when the validations module is mixed in, which it is by default.
+ def update_attribute(name, value)
+ send("#{name}=", value)
+ save(:validate => false)
+ end
+
+ # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
+ # fail and false will be returned.
+ def update_attributes(attributes)
+ self.attributes = attributes
+ save
+ end
+
+ # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
+ def update_attributes!(attributes)
+ self.attributes = attributes
+ save!
+ end
+
+ # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
+ # The increment is performed directly on the underlying attribute, no setter is invoked.
+ # Only makes sense for number-based attributes. Returns +self+.
+ def increment(attribute, by = 1)
+ self[attribute] ||= 0
+ self[attribute] += by
+ self
+ end
+
+ # Wrapper around +increment+ that saves the record. This method differs from
+ # its non-bang version in that it passes through the attribute setter.
+ # Saving is not subjected to validation checks. Returns +true+ if the
+ # record could be saved.
+ def increment!(attribute, by = 1)
+ increment(attribute, by).update_attribute(attribute, self[attribute])
+ end
+
+ # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
+ # The decrement is performed directly on the underlying attribute, no setter is invoked.
+ # Only makes sense for number-based attributes. Returns +self+.
+ def decrement(attribute, by = 1)
+ self[attribute] ||= 0
+ self[attribute] -= by
+ self
+ end
+
+ # Wrapper around +decrement+ that saves the record. This method differs from
+ # its non-bang version in that it passes through the attribute setter.
+ # Saving is not subjected to validation checks. Returns +true+ if the
+ # record could be saved.
+ def decrement!(attribute, by = 1)
+ decrement(attribute, by).update_attribute(attribute, self[attribute])
+ end
+
+ # Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
+ # if the predicate returns +true+ the attribute will become +false+. This
+ # method toggles directly the underlying value without calling any setter.
+ # Returns +self+.
+ def toggle(attribute)
+ self[attribute] = !send("#{attribute}?")
+ self
+ end
+
+ # Wrapper around +toggle+ that saves the record. This method differs from
+ # its non-bang version in that it passes through the attribute setter.
+ # Saving is not subjected to validation checks. Returns +true+ if the
+ # record could be saved.
+ def toggle!(attribute)
+ toggle(attribute).update_attribute(attribute, self[attribute])
+ end
+
+ # Reloads the attributes of this object from the database.
+ # The optional options argument is passed to find when reloading so you
+ # may do e.g. record.reload(:lock => true) to reload the same record with
+ # an exclusive row lock.
+ def reload(options = nil)
+ clear_aggregation_cache
+ clear_association_cache
+ @attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
+ @attributes_cache = {}
+ self
+ end
+
+ private
+ def create_or_update
+ raise ReadOnlyRecord if readonly?
+ result = new_record? ? create : update
+ result != false
+ end
+
+ # Updates the associated record with values matching those of the instance attributes.
+ # Returns the number of affected rows.
+ def update(attribute_names = @attributes.keys)
+ attributes_with_values = arel_attributes_values(false, false, attribute_names)
+ return 0 if attributes_with_values.empty?
+ self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
+ end
+
+ # Creates a record with values matching those of the instance attributes
+ # and returns its id.
+ def create
+ if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
+ self.id = connection.next_sequence_value(self.class.sequence_name)
+ end
+
+ attributes_values = arel_attributes_values
+
+ new_id = if attributes_values.empty?
+ self.class.unscoped.insert connection.empty_insert_statement_value
+ else
+ self.class.unscoped.insert attributes_values
+ end
+
+ self.id ||= new_id
+
+ @new_record = false
+ id
+ end
+
+ # Initializes the attributes array with keys matching the columns from the linked table and
+ # the values matching the corresponding default value of that column, so
+ # that a new instance, or one populated from a passed-in Hash, still has all the attributes
+ # that instances loaded from the database would.
+ def attributes_from_column_definition
+ self.class.columns.inject({}) do |attributes, column|
+ attributes[column.name] = column.default unless column.name == self.class.primary_key
+ attributes
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index a5ea6e7e3a..858d298470 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -195,7 +195,7 @@ module ActiveRecord
select_statement << ", #{group_field} AS #{group_alias}"
- relation = select(select_statement).group(group)
+ relation = except(:group).select(select_statement).group(group)
calculated_data = @klass.connection.select_all(relation.to_sql)
@@ -239,11 +239,15 @@ module ActiveRecord
end
def type_cast_calculated_value(value, column, operation = nil)
- case operation
- when 'count' then value.to_i
- when 'sum' then type_cast_using_column(value || '0', column)
- when 'average' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d
- else type_cast_using_column(value, column)
+ if value.is_a?(String) || value.nil?
+ case operation
+ when 'count' then value.to_i
+ when 'sum' then type_cast_using_column(value || '0', column)
+ when 'average' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d
+ else type_cast_using_column(value, column)
+ end
+ else
+ value
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index d6144dc206..7a0c9dc612 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -234,20 +234,24 @@ module ActiveRecord
end
def find_or_instantiator_by_attributes(match, attributes, *args)
- guard_protected_attributes = false
-
- if args[0].is_a?(Hash)
- guard_protected_attributes = true
- attributes_for_create = args[0].with_indifferent_access
- conditions = attributes_for_create.slice(*attributes).symbolize_keys
- else
- attributes_for_create = conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h}
+ protected_attributes_for_create, unprotected_attributes_for_create = {}, {}
+ args.each_with_index do |arg, i|
+ if arg.is_a?(Hash)
+ protected_attributes_for_create = args[i].with_indifferent_access
+ else
+ unprotected_attributes_for_create[attributes[i]] = args[i]
+ end
end
+ conditions = (protected_attributes_for_create.merge(unprotected_attributes_for_create)).slice(*attributes).symbolize_keys
+
record = where(conditions).first
unless record
- record = @klass.new { |r| r.send(:attributes=, attributes_for_create, guard_protected_attributes) }
+ record = @klass.new do |r|
+ r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty?
+ r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty?
+ end
yield(record) if block_given?
record.save if match.instantiator == :create
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 7bca12d85e..8d8bb659e1 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -162,13 +162,9 @@ module ActiveRecord
arel = arel.take(@limit_value) if @limit_value.present?
arel = arel.skip(@offset_value) if @offset_value.present?
- @group_values.uniq.each do |g|
- arel = arel.group(g) if g.present?
- end
+ arel = arel.group(*@group_values.uniq.select{|g| g.present?})
- @order_values.uniq.each do |o|
- arel = arel.order(Arel::SqlLiteral.new(o.to_s)) if o.present?
- end
+ arel = arel.order(*@order_values.uniq.select{|o| o.present?}.map(&:to_s))
selects = @select_values.uniq
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 8fdd64afcc..bb1f138f5b 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -80,10 +80,15 @@ module ActiveRecord
options.assert_valid_keys(VALID_FIND_OPTIONS)
- [:joins, :select, :group, :having, :order, :limit, :offset, :from, :lock, :readonly].each do |finder|
+ [:joins, :select, :group, :having, :limit, :offset, :from, :lock, :readonly].each do |finder|
relation = relation.send(finder, options[finder]) if options.has_key?(finder)
end
+ # Give precedence to newly-applied orders and groups to play nicely with with_scope
+ [:group, :order].each do |finder|
+ relation.send("#{finder}_values=", Array.wrap(options[finder]) + relation.send("#{finder}_values")) if options.has_key?(finder)
+ end
+
relation = relation.where(options[:conditions]) if options.has_key?(:conditions)
relation = relation.includes(options[:include]) if options.has_key?(:include)
relation = relation.extending(options[:extend]) if options.has_key?(:extend)
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index da075dabd3..9fba8f0aca 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -11,9 +11,6 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- alias_method_chain :create, :timestamps
- alias_method_chain :update, :timestamps
-
class_inheritable_accessor :record_timestamps, :instance_writer => false
self.record_timestamps = true
end
@@ -39,35 +36,34 @@ module ActiveRecord
save!
end
+ private
+ def create #:nodoc:
+ if record_timestamps
+ current_time = current_time_from_proper_timezone
- private
- def create_with_timestamps #:nodoc:
- if record_timestamps
- current_time = current_time_from_proper_timezone
-
- write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil?
- write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil?
-
- write_attribute('updated_at', current_time) if respond_to?(:updated_at) && updated_at.nil?
- write_attribute('updated_on', current_time) if respond_to?(:updated_on) && updated_on.nil?
- end
+ write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil?
+ write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil?
- create_without_timestamps
+ write_attribute('updated_at', current_time) if respond_to?(:updated_at) && updated_at.nil?
+ write_attribute('updated_on', current_time) if respond_to?(:updated_on) && updated_on.nil?
end
- def update_with_timestamps(*args) #:nodoc:
- if record_timestamps && (!partial_updates? || changed?)
- current_time = current_time_from_proper_timezone
+ super
+ end
- write_attribute('updated_at', current_time) if respond_to?(:updated_at)
- write_attribute('updated_on', current_time) if respond_to?(:updated_on)
- end
+ def update(*args) #:nodoc:
+ if record_timestamps && (!partial_updates? || changed?)
+ current_time = current_time_from_proper_timezone
- update_without_timestamps(*args)
- end
-
- def current_time_from_proper_timezone
- self.class.default_timezone == :utc ? Time.now.utc : Time.now
+ write_attribute('updated_at', current_time) if respond_to?(:updated_at)
+ write_attribute('updated_on', current_time) if respond_to?(:updated_on)
end
+
+ super
+ end
+
+ def current_time_from_proper_timezone
+ self.class.default_timezone == :utc ? Time.now.utc : Time.now
+ end
end
end \ No newline at end of file
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 1a195fbb81..5a8e2ce880 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -9,10 +9,6 @@ module ActiveRecord
end
included do
- [:destroy, :save, :save!].each do |method|
- alias_method_chain method, :transactions
- end
-
define_model_callbacks :commit, :commit_on_update, :commit_on_create, :commit_on_destroy, :only => :after
define_model_callbacks :rollback, :rollback_on_update, :rollback_on_create, :rollback_on_destroy
end
@@ -213,16 +209,18 @@ module ActiveRecord
self.class.transaction(&block)
end
- def destroy_with_transactions #:nodoc:
- with_transaction_returning_status(:destroy_without_transactions)
+ def destroy #:nodoc:
+ with_transaction_returning_status { super }
end
- def save_with_transactions(*args) #:nodoc:
- rollback_active_record_state! { with_transaction_returning_status(:save_without_transactions, *args) }
+ def save(*) #:nodoc:
+ rollback_active_record_state! do
+ with_transaction_returning_status { super }
+ end
end
- def save_with_transactions! #:nodoc:
- with_transaction_returning_status(:save_without_transactions!)
+ def save!(*) #:nodoc:
+ with_transaction_returning_status { super }
end
# Reset id and @new_record if the transaction rolls back.
@@ -279,11 +277,11 @@ module ActiveRecord
#
# This method is available within the context of an ActiveRecord::Base
# instance.
- def with_transaction_returning_status(method, *args)
+ def with_transaction_returning_status
status = nil
self.class.transaction do
add_to_transaction
- status = send(method, *args)
+ status = yield
raise ActiveRecord::Rollback unless status
end
status
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 8b266be638..55c4236874 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -19,11 +19,6 @@ module ActiveRecord
extend ActiveSupport::Concern
include ActiveModel::Validations
- included do
- alias_method_chain :save, :validation
- alias_method_chain :save!, :validation
- end
-
module ClassMethods
# Creates an object just like Base.create but calls save! instead of save
# so an exception is raised if the record is invalid.
@@ -39,42 +34,40 @@ module ActiveRecord
end
end
- 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(options=nil)
- perform_validation = case options
- when NilClass
- true
- when Hash
- options[:validate] != false
- else
- ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
- options
- 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(options=nil)
+ return super if valid?(options)
+ false
+ end
- if perform_validation && valid? || !perform_validation
- save_without_validation
- else
- false
- end
- end
+ def save_without_validation!
+ save!(:validate => false)
+ 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!(options = nil)
+ return super if valid?(options)
+ raise RecordInvalid.new(self)
+ 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!
+ # Runs all the specified validations and returns true if no errors were added otherwise false.
+ def valid?(options = nil)
+ perform_validation = case options
+ when NilClass
+ true
+ when Hash
+ options[:validate] != false
else
- raise RecordInvalid.new(self)
- end
+ ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
+ options
end
- # Runs all the specified validations and returns true if no errors were added otherwise false.
- def valid?
+ if perform_validation
errors.clear
- @_on_validate = new_record? ? :create : :update
+ self.validation_context = new_record? ? :create : :update
_run_validate_callbacks
deprecated_callback_method(:validate)
@@ -86,12 +79,12 @@ module ActiveRecord
end
errors.empty?
+ else
+ true
end
end
end
end
-Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
- filename = File.basename(path)
- require "active_record/validations/#{filename}"
-end
+require "active_record/validations/associated"
+require "active_record/validations/uniqueness"
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 3623680de9..bbc4e543d5 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1994,6 +1994,16 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal last, Developer.find(:all, :order => 'developers.name, developers.salary DESC').last
end
+ def test_find_keeps_multiple_order_values
+ combined = Developer.find(:all, :order => 'developers.name, developers.salary')
+ assert_equal combined, Developer.find(:all, :order => ['developers.name', 'developers.salary'])
+ end
+
+ def test_find_keeps_multiple_group_values
+ combined = Developer.find(:all, :group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at')
+ assert_equal combined, Developer.find(:all, :group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at'])
+ end
+
def test_find_symbol_ordered_last
last = Developer.find :last, :order => :salary
assert_equal last, Developer.find(:all, :order => :salary).last
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 77b2b748b1..e78db8969d 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -840,7 +840,7 @@ class FinderTest < ActiveRecord::TestCase
assert c.new_record?
end
- def test_find_or_create_from_one_attribute_should_set_not_attribute_even_when_protected
+ def test_find_or_create_from_one_attribute_should_not_set_attribute_even_when_protected
c = Company.find_or_create_by_name({:name => "Fortune 1000", :rating => 1000})
assert_equal "Fortune 1000", c.name
assert_not_equal 1000, c.rating
@@ -864,6 +864,22 @@ class FinderTest < ActiveRecord::TestCase
assert !c.new_record?
end
+ def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
+ c = Company.find_or_initialize_by_rating(1000, {:name => "Fortune 1000"})
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert c.new_record?
+ end
+
+ def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
+ c = Company.find_or_create_by_rating(1000, {:name => "Fortune 1000"})
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert !c.new_record?
+ end
+
def test_find_or_initialize_should_set_protected_attributes_if_given_as_block
c = Company.find_or_initialize_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
assert_equal "Fortune 1000", c.name
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index aa2d9527f9..66874cdad1 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -195,7 +195,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotFound) { Person.find(p1.id) }
assert_raises(ActiveRecord::RecordNotFound) { LegacyThing.find(t.id) }
end
-
+
def test_quote_table_name
ref = references(:michael_magician)
ref.favourite = !ref.favourite
@@ -206,8 +206,11 @@ class OptimisticLockingTest < ActiveRecord::TestCase
# is nothing else being updated.
def test_update_without_attributes_does_not_only_update_lock_version
assert_nothing_raised do
- p1 = Person.new(:first_name => 'anika')
- p1.send(:update_with_lock, [])
+ p1 = Person.create!(:first_name => 'anika')
+ lock_version = p1.lock_version
+ p1.save
+ p1.reload
+ assert_equal lock_version, p1.lock_version
end
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index eae8ae7e39..fadd62b5a1 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -6,6 +6,8 @@ require "models/parrot"
require "models/treasure"
require "models/man"
require "models/interest"
+require "models/owner"
+require "models/pet"
require 'active_support/hash_with_indifferent_access'
module AssertRaiseWithMessage
@@ -707,3 +709,26 @@ class TestNestedAttributesLimit < ActiveRecord::TestCase
end
end
end
+
+class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase
+ fixtures :owners, :pets
+
+ def setup
+ Owner.accepts_nested_attributes_for :pets
+
+ @owner = owners(:ashley)
+ @pet1, @pet2 = pets(:chew), pets(:mochi)
+
+ @params = {
+ :pets_attributes => {
+ '0' => { :id => @pet1.id, :name => 'Foo' },
+ '1' => { :id => @pet2.id, :name => 'Bar' }
+ }
+ }
+ end
+
+ def test_should_update_existing_records_with_non_standard_primary_key
+ @owner.update_attributes(@params)
+ assert_equal ['Foo', 'Bar'], @owner.pets.map(&:name)
+ end
+end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 937e08ac68..e1fb911cc9 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -44,7 +44,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_error_on_create
r = WrongReply.new
r.title = "Wrong Create"
- assert !r.valid?
+ assert !r.save
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
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 0652a20035..6146cc6a97 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,11 @@
*Rails 3.0.0 [beta 4/release candidate] (unreleased)*
+* Aliases Date#sunday to Date#end_of_week. [fxn]
+
+* Backports Date#>> from 1.9 so that calculations do the right thing around the calendar reform. [fxn]
+
+* Date#to_time handles properly years in the range 0..138. [fxn]
+
* Deprecate {{}} as interpolation syntax for I18n in favor of %{} [José Valim]
* Array#to_xml is more powerful and able to handle the same types as Hash#to_xml #4490 [Neeraj Singh]
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index fef49e1003..3038729d34 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -4,6 +4,24 @@ require 'active_support/core_ext/time/zones'
require 'active_support/core_ext/object/acts_like'
class Date
+ if RUBY_VERSION < '1.9'
+ undef :>>
+
+ # Backported from 1.9. The one in 1.8 leads to incorrect next_month and
+ # friends for dates where the calendar reform is involved. It additionally
+ # prevents an infinite loop fixed in r27013.
+ def >>(n)
+ y, m = (year * 12 + (mon - 1) + n).divmod(12)
+ m, = (m + 1) .divmod(1)
+ d = mday
+ until jd2 = self.class.valid_civil?(y, m, d, start)
+ d -= 1
+ raise ArgumentError, 'invalid date' unless d > 0
+ end
+ self + (jd2 - jd)
+ end
+ end
+
class << self
# Returns a new Date representing the date 1 day ago (i.e. yesterday's date).
def yesterday
@@ -163,6 +181,7 @@ class Date
result = self + days_to_sunday.days
self.acts_like?(:time) ? result.end_of_day : result
end
+ alias :sunday :end_of_week
alias :at_end_of_week :end_of_week
# Returns a new Date/DateTime representing the start of the given day in next week (default is Monday).
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index a9f821b01e..24168c7825 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -1,5 +1,6 @@
require 'active_support/inflector'
require 'active_support/core_ext/time/conversions'
+require 'active_support/core_ext/date_time/calculations'
class DateTime
# Ruby 1.9 has DateTime#to_time which internally relies on Time. We define our own #to_time which allows
@@ -72,6 +73,11 @@ class DateTime
self
end unless method_defined?(:to_datetime)
+ def self.civil_from_format(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0)
+ offset = utc_or_local.to_sym == :local ? local_offset : 0
+ civil(year, month, day, hour, min, sec, offset)
+ end
+
# Converts datetime to an appropriate format for use in XML
def xmlschema
strftime("%Y-%m-%dT%H:%M:%S%Z")
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 98906bc5c0..2b47ecd543 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -1,6 +1,7 @@
require 'active_support/duration'
require 'active_support/core_ext/date/acts_like'
require 'active_support/core_ext/date/calculations'
+require 'active_support/core_ext/date_time/conversions'
class Time
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
@@ -23,10 +24,11 @@ class Time
# (i.e., if year is within either 1970..2038 or 1902..2038, depending on system architecture);
# otherwise returns a DateTime
def time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0)
- ::Time.send(utc_or_local, year, month, day, hour, min, sec, usec)
+ time = ::Time.send(utc_or_local, year, month, day, hour, min, sec, usec)
+ # This check is needed because Time.utc(y) returns a time object in the 2000s for 0 <= y <= 138.
+ time.year == year ? time : ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
rescue
- offset = utc_or_local.to_sym == :local ? ::DateTime.local_offset : 0
- ::DateTime.civil(year, month, day, hour, min, sec, offset)
+ ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
end
# Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:utc</tt>.
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index c403d7fb11..2b66fd03d0 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -20,6 +20,13 @@ class DateExtCalculationsTest < Test::Unit::TestCase
def test_to_time
assert_equal Time.local(2005, 2, 21), Date.new(2005, 2, 21).to_time
assert_equal Time.local_time(2039, 2, 21), Date.new(2039, 2, 21).to_time
+ silence_warnings do
+ 0.upto(138) do |year|
+ [:utc, :local].each do |format|
+ assert_equal year, Date.new(year).to_time(format).year
+ end
+ end
+ end
end
def test_to_datetime
@@ -50,6 +57,10 @@ class DateExtCalculationsTest < Test::Unit::TestCase
assert_equal Date.new(2005,11,28), Date.new(2005,12,04).beginning_of_week #sunday
end
+ def test_beginning_of_week_in_calendar_reform
+ assert_equal Date.new(1582,10,1), Date.new(1582,10,15).beginning_of_week #friday
+ end
+
def test_beginning_of_month
assert_equal Date.new(2005,2,1), Date.new(2005,2,22).beginning_of_month
end
@@ -72,6 +83,10 @@ class DateExtCalculationsTest < Test::Unit::TestCase
assert_equal Date.new(2008,3,2), Date.new(2008,3,02).end_of_week #sunday
end
+ def test_end_of_week_in_calendar_reform
+ assert_equal Date.new(1582,10,17), Date.new(1582,10,4).end_of_week #thursday
+ end
+
def test_end_of_quarter
assert_equal Date.new(2008,3,31), Date.new(2008,2,15).end_of_quarter
assert_equal Date.new(2008,3,31), Date.new(2008,3,31).end_of_quarter
@@ -89,7 +104,6 @@ class DateExtCalculationsTest < Test::Unit::TestCase
assert_equal Date.new(2005,3,31), Date.new(2005,3,20).end_of_month
assert_equal Date.new(2005,2,28), Date.new(2005,2,20).end_of_month
assert_equal Date.new(2005,4,30), Date.new(2005,4,20).end_of_month
-
end
def test_beginning_of_year
@@ -135,20 +149,44 @@ class DateExtCalculationsTest < Test::Unit::TestCase
assert_equal Date.new(2004,6,5), Date.new(2005,6,5).last_year
end
+ def test_last_year_in_leap_years
+ assert_equal Date.new(1999,2,28), Date.new(2000,2,29).last_year
+ end
+
+ def test_last_year_in_calendar_reform
+ assert_equal Date.new(1582,10,4), Date.new(1583,10,14).last_year
+ end
+
def test_next_year
assert_equal Date.new(2006,6,5), Date.new(2005,6,5).next_year
end
+ def test_next_year_in_leap_years
+ assert_equal Date.new(2001,2,28), Date.new(2000,2,29).next_year
+ end
+
+ def test_next_year_in_calendar_reform
+ assert_equal Date.new(1582,10,4), Date.new(1581,10,10).next_year
+ end
+
def test_yesterday
assert_equal Date.new(2005,2,21), Date.new(2005,2,22).yesterday
assert_equal Date.new(2005,2,28), Date.new(2005,3,2).yesterday.yesterday
end
+ def test_yesterday_in_calendar_reform
+ assert_equal Date.new(1582,10,4), Date.new(1582,10,15).yesterday
+ end
+
def test_tomorrow
assert_equal Date.new(2005,2,23), Date.new(2005,2,22).tomorrow
assert_equal Date.new(2005,3,2), Date.new(2005,2,28).tomorrow.tomorrow
end
+ def test_tomorrow_in_calendar_reform
+ assert_equal Date.new(1582,10,15), Date.new(1582,10,4).tomorrow
+ end
+
def test_advance
assert_equal Date.new(2006,2,28), Date.new(2005,2,28).advance(:years => 1)
assert_equal Date.new(2005,6,28), Date.new(2005,2,28).advance(:months => 4)
@@ -160,6 +198,17 @@ class DateExtCalculationsTest < Test::Unit::TestCase
assert_equal Date.new(2005,2,28), Date.new(2004,2,29).advance(:years => 1) #leap day plus one year
end
+ def test_advance_in_calendar_reform
+ assert_equal Date.new(1582,10,15), Date.new(1582,10,4).advance(:days => 1)
+ assert_equal Date.new(1582,10,4), Date.new(1582,10,15).advance(:days => -1)
+ 5.upto(14) do |day|
+ assert_equal Date.new(1582,10,4), Date.new(1582,9,day).advance(:months => 1)
+ assert_equal Date.new(1582,10,4), Date.new(1582,11,day).advance(:months => -1)
+ assert_equal Date.new(1582,10,4), Date.new(1581,10,day).advance(:years => 1)
+ assert_equal Date.new(1582,10,4), Date.new(1583,10,day).advance(:years => -1)
+ end
+ end
+
def test_next_week
assert_equal Date.new(2005,2,28), Date.new(2005,2,22).next_week
assert_equal Date.new(2005,3,4), Date.new(2005,2,22).next_week(:friday)
@@ -167,6 +216,11 @@ class DateExtCalculationsTest < Test::Unit::TestCase
assert_equal Date.new(2006,11,1), Date.new(2006,10,23).next_week(:wednesday)
end
+ def test_next_week_in_calendar_reform
+ assert_equal Date.new(1582,10,15), Date.new(1582,9,30).next_week(:friday)
+ assert_equal Date.new(1582,10,18), Date.new(1582,10,4).next_week
+ end
+
def test_next_month_on_31st
assert_equal Date.new(2005, 9, 30), Date.new(2005, 8, 31).next_month
end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 278c05797b..f9af059acd 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -40,6 +40,11 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
assert_equal DateTime.new(2005, 2, 21, 10, 11, 12, Rational(-5, 24)), DateTime.new(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).to_time
end
+ def test_civil_from_format
+ assert_equal DateTime.civil(2010, 5, 4, 0, 0, 0, DateTime.local_offset), DateTime.civil_from_format(:local, 2010, 5, 4)
+ assert_equal DateTime.civil(2010, 5, 4, 0, 0, 0, 0), DateTime.civil_from_format(:utc, 2010, 5, 4)
+ end
+
def test_seconds_since_midnight
assert_equal 1,DateTime.civil(2005,1,1,0,0,1).seconds_since_midnight
assert_equal 60,DateTime.civil(2005,1,1,0,1,0).seconds_since_midnight
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index c24c8619c6..342d6ab577 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -587,6 +587,13 @@ class TimeExtCalculationsTest < Test::Unit::TestCase
DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0)
assert_equal ::Date::ITALY, Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).start # use Ruby's default start value
end
+ silence_warnings do
+ 0.upto(138) do |year|
+ [:utc, :local].each do |format|
+ assert_equal year, Time.time_with_datetime_fallback(format, year).year
+ end
+ end
+ end
end
def test_utc_time
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index 8957f11724..5bbaf725df 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -55,7 +55,7 @@ module Rails
end
case config["adapter"]
- when "mysql"
+ when /^mysql/
args = {
'host' => '--host',
'port' => '--port',
@@ -114,4 +114,4 @@ end
# Has to set the RAILS_ENV before config/application is required
if ARGV.first && !ARGV.first.index("-") && env = ARGV.first
ENV['RAILS_ENV'] = %w(production development test).find { |e| e.index(env) } || env
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 9bc019b152..fe8a6c0b94 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -166,6 +166,38 @@ module Rails
end
end
+ def self.hidden_namespaces
+ @hidden_namespaces ||= begin
+ orm = options[:rails][:orm]
+ test = options[:rails][:test_framework]
+ template = options[:rails][:template_engine]
+
+ [
+ "rails",
+ "#{orm}:migration",
+ "#{orm}:model",
+ "#{orm}:observer",
+ "#{test}:controller",
+ "#{test}:helper",
+ "#{test}:integration",
+ "#{test}:mailer",
+ "#{test}:model",
+ "#{test}:observer",
+ "#{test}:scaffold",
+ "#{test}:view",
+ "#{template}:controller",
+ "#{template}:scaffold"
+ ]
+ end
+ end
+
+ class << self
+ def hide_namespaces(*namespaces)
+ hidden_namespaces.concat(namespaces)
+ end
+ alias hide_namespace hide_namespaces
+ end
+
# Show help message with available generators.
def self.help(command = 'generate')
lookup!
@@ -197,9 +229,7 @@ module Rails
rails.delete("app")
print_list("rails", rails)
- groups.delete("active_record") if options[:rails][:orm] == :active_record
- groups.delete("test_unit") if options[:rails][:test_framework] == :test_unit
- groups.delete("erb") if options[:rails][:template_engine] == :erb
+ hidden_namespaces.each {|n| groups.delete(n.to_s) }
groups.sort.each { |b, n| print_list(b, n) }
end
@@ -208,9 +238,17 @@ module Rails
# Prints a list of generators.
def self.print_list(base, namespaces) #:nodoc:
+ namespaces = namespaces.reject do |n|
+ hidden_namespaces.include?(n)
+ end
+
return if namespaces.empty?
puts "#{base.camelize}:"
- namespaces.each { |namespace| puts(" #{namespace}") }
+
+ namespaces.each do |namespace|
+ puts(" #{namespace}")
+ end
+
puts
end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 10d8b8f85a..0a0b033738 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -200,6 +200,9 @@ module Rails
def initialize(*args)
raise Error, "Options should be given after the application name. For details run: rails --help" if args[0].blank?
+
+ @original_wd = Dir.pwd
+
super
if !options[:skip_activerecord] && !DATABASES.include?(options[:database])
@@ -316,7 +319,7 @@ module Rails
if URI(path).is_a?(URI::HTTP)
contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read }
else
- contents = open(path) {|io| io.read }
+ contents = open(File.expand_path(path, @original_wd)) {|io| io.read }
end
prok = eval("proc { #{contents} }", TOPLEVEL_BINDING, path, 1)
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 1a93867013..8743defe82 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -238,6 +238,14 @@ class CustomAppGeneratorTest < Rails::Generators::TestCase
assert_file "config.ru", %[run proc { |env| [200, { "Content-Type" => "text/html" }, ["Hello World"]] }]
end
+ def test_builder_option_with_relative_path
+ here = File.expand_path(File.dirname(__FILE__))
+ FileUtils.cd(here)
+ run_generator([destination_root, "-b", "../fixtures/lib/simple_builder.rb"])
+ (DEFAULT_APP_FILES - ['config.ru']).each{ |path| assert_no_file path }
+ assert_file "config.ru", %[run proc { |env| [200, { "Content-Type" => "text/html" }, ["Hello World"]] }]
+ end
+
def test_builder_option_with_tweak_app_builder
FileUtils.cd(Rails.root)
run_generator([destination_root, "-b", "#{Rails.root}/lib/tweak_builder.rb"])