aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md23
-rw-r--r--activerecord/README.rdoc18
-rw-r--r--activerecord/lib/active_record/associations.rb9
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb1
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb1
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb9
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb3
-rw-r--r--activerecord/lib/active_record/enum.rb6
-rw-r--r--activerecord/lib/active_record/railties/databases.rake3
-rw-r--r--activerecord/lib/active_record/reflection.rb14
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb17
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb6
-rw-r--r--activerecord/test/cases/associations/eager_test.rb31
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb24
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb10
-rw-r--r--activerecord/test/cases/persistence_test.rb14
-rw-r--r--activerecord/test/cases/validations_test.rb18
-rw-r--r--activerecord/test/models/author.rb2
-rw-r--r--activerecord/test/models/developer.rb2
22 files changed, 193 insertions, 36 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 4164b928bd..f40fe33bcd 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,26 @@
+* Deprecate joining, eager loading and preloading of instance dependent
+ associations without replacement. These operations happen before instances
+ are created. The current behavior is unexpected and can result in broken
+ behavior.
+
+ Fixes #15024.
+
+ *Yves Senn*
+
+* Fixed HABTM's CollectionAssociation size calculation.
+
+ HABTM should fall back to using the normal CollectionAssociation's size
+ calculation if the collection is not cached or loaded.
+
+ Fixes #14913 and #14914.
+
+ *Fred Wu*
+
+* Return a non zero status when running `rake db:migrate:status` and migration table does
+ not exist.
+
+ *Paul B.*
+
* Add support for module-level `table_name_suffix` in models.
This makes `table_name_suffix` work the same way as `table_name_prefix` when
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index e04abe9b37..e5b68750e4 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -1,4 +1,4 @@
-= Active Record -- Object-relational mapping put on rails
+= Active Record -- Object-relational mapping in Rails
Active Record connects classes to relational database tables to establish an
almost zero-configuration persistence layer for applications. The library
@@ -19,9 +19,11 @@ A short rundown of some of the major features:
class Product < ActiveRecord::Base
end
-
- The Product class is automatically mapped to the table named "products",
- which might look like this:
+
+ {Learn more}[link:classes/ActiveRecord/Base.html]
+
+The Product class is automatically mapped to the table named "products",
+which might look like this:
CREATE TABLE products (
id int(11) NOT NULL auto_increment,
@@ -29,11 +31,9 @@ A short rundown of some of the major features:
PRIMARY KEY (id)
);
- This would also define the following accessors: `Product#name` and
- `Product#name=(new_name)`
-
- {Learn more}[link:classes/ActiveRecord/Base.html]
-
+This would also define the following accessors: `Product#name` and
+`Product#name=(new_name)`.
+
* Associations between objects defined by simple class methods.
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index ac1479ad8f..727ee5f65f 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -419,6 +419,10 @@ module ActiveRecord
# has_many :birthday_events, ->(user) { where starts_on: user.birthday }, class_name: 'Event'
# end
#
+ # Note: Joining, eager loading and preloading of these associations is not fully possible.
+ # These operations happen before instance creation and the scope will be called with a +nil+ argument.
+ # This can lead to unexpected behavior and is deprecated.
+ #
# == Association callbacks
#
# Similar to the normal callbacks that hook into the life cycle of an Active Record object,
@@ -1576,6 +1580,11 @@ module ActiveRecord
join_model = builder.through_model
+ # FIXME: we should move this to the internal constants. Also people
+ # should never directly access this constant so I'm not happy about
+ # setting it.
+ const_set join_model.name, join_model
+
middle_reflection = builder.middle_reflection join_model
Builder::HasMany.define_callbacks self, middle_reflection
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 0b122d2070..aeb77e2753 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -23,7 +23,7 @@ module ActiveRecord
elsif loaded?
target.size
else
- count
+ super
end
end
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index b7dc037a65..5842be3a7b 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -215,6 +215,7 @@ module ActiveRecord
associations.map do |name, right|
reflection = find_reflection base_klass, name
reflection.check_validity!
+ reflection.check_eager_loadable!
if reflection.options[:polymorphic]
raise EagerLoadPolymorphicError.new(reflection)
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 311684d886..20bd4947dc 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -175,6 +175,7 @@ module ActiveRecord
if owners.first.association(reflection.name).loaded?
return AlreadyLoaded
end
+ reflection.check_preloadable!
case reflection.macro
when :has_many
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 30fa2c8ba5..87ecbe54f1 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -11,6 +11,15 @@ module ActiveRecord
# If the passed hash responds to <tt>permitted?</tt> method and the return value
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
# exception is raised.
+ #
+ # cat = Cat.new(name: "Gorby", status: "yawning")
+ # cat.attributes # => { "name" => "Gorby", "status" => "yawning" }
+ # cat.assign_attributes(status: "sleeping")
+ # cat.attributes # => { "name" => "Gorby", "status" => "sleeping" }
+ #
+ # New attributes will be persisted in the database when the object is saved.
+ #
+ # Aliased to <tt>attributes=</tt>.
def assign_attributes(new_attributes)
if !new_attributes.respond_to?(:stringify_keys)
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 4b1733619a..7fd7accc6b 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -326,13 +326,13 @@ module ActiveRecord
# class Task < ActiveRecord::Base
# end
#
- # person = Task.new(title: '', is_done: false)
- # person.attribute_present?(:title) # => false
- # person.attribute_present?(:is_done) # => true
- # person.name = 'Francesco'
- # person.is_done = true
- # person.attribute_present?(:title) # => true
- # person.attribute_present?(:is_done) # => true
+ # task = Task.new(title: '', is_done: false)
+ # task.attribute_present?(:title) # => false
+ # task.attribute_present?(:is_done) # => true
+ # task.title = 'Buy milk'
+ # task.is_done = true
+ # task.attribute_present?(:title) # => true
+ # task.attribute_present?(:is_done) # => true
def attribute_present?(attribute)
value = read_attribute(attribute)
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 187eefb9e4..38efebeaf3 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -72,6 +72,8 @@ module ActiveRecord
end
# Casts a Ruby value to something appropriate for writing to the database.
+ # Numeric columns will typecast boolean and string to appropriate numeric
+ # values.
def type_cast_for_write(value)
return value unless number?
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
index 2cbcd5fd50..1d22b56964 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -98,6 +98,9 @@ module ActiveRecord
end
end
+ # Casts a Ruby value to something appropriate for writing to PostgreSQL.
+ # see ActiveRecord::ConnectionAdapters::Class#type_cast_for_write
+ # see ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID::Type
def type_cast_for_write(value)
if @oid_type.respond_to?(:type_cast_for_write)
@oid_type.type_cast_for_write(value)
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 18f1ca26de..c7ec093824 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -27,8 +27,10 @@ module ActiveRecord
# conversation.status # => nil
#
# Scopes based on the allowed values of the enum field will be provided
- # as well. With the above example, it will create an +active+ and +archived+
- # scope.
+ # as well. With the above example:
+ #
+ # Conversation.active
+ # Conversation.archived
#
# You can set the default value from the database declaration, like:
#
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 9538ead5f1..9e8e5fe94b 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -83,8 +83,7 @@ db_namespace = namespace :db do
desc 'Display status of migrations'
task :status => [:environment, :load_config] do
unless ActiveRecord::Base.connection.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
- puts 'Schema migrations table does not exist yet.'
- next # means "return" for rake task
+ abort 'Schema migrations table does not exist yet.'
end
db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}")
db_list.map! { |version| "%.3d" % version }
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 7f4d77849a..0eec6774a0 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -278,6 +278,20 @@ module ActiveRecord
end
end
+ def check_preloadable!
+ return unless scope
+
+ if scope.arity > 0
+ ActiveSupport::Deprecation.warn <<-WARNING
+The association scope '#{name}' is instance dependent (the scope block takes an argument).
+Preloading happens before the individual instances are created. This means that there is no instance
+being passed to the association scope. This will most likely result in broken or incorrect behavior.
+Joining, Preloading and eager loading of these associations is deprecated and will be removed in the future.
+ WARNING
+ end
+ end
+ alias :check_eager_loadable! :check_preloadable!
+
def join_id_for(owner) #:nodoc:
key = (source_macro == :belongs_to) ? foreign_key : active_record_primary_key
owner[key]
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index b7791078db..49f5ec250f 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -396,6 +396,23 @@ module ActiveRecord
reset_connection
end
+ def test_unparsed_defaults_are_at_least_set_when_saving
+ with_example_table "id SERIAL PRIMARY KEY, number INTEGER NOT NULL DEFAULT (4 + 4) * 2 / 4" do
+ number_klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'ex'
+ end
+ column = number_klass.columns_hash["number"]
+ assert_nil column.default
+ assert_nil column.default_function
+
+ first_number = number_klass.new
+ assert_nil first_number.number
+
+ first_number.save!
+ assert_equal 4, first_number.reload.number
+ end
+ end
+
private
def insert(ctx, data)
binds = data.map { |name, value|
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index bdf8e15e3e..ffce7c0d5f 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -45,12 +45,14 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
UUIDType.reset_column_information
column = UUIDType.columns.find { |c| c.name == 'thingy' }
assert_equal "uuid_generate_v1()", column.default_function
-
+
@connection.change_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v4()"
-
+
UUIDType.reset_column_information
column = UUIDType.columns.find { |c| c.name == 'thingy' }
assert_equal "uuid_generate_v4()", column.default_function
+ ensure
+ UUIDType.reset_column_information
end
def test_data_type_of_uuid_types
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 7eaa5adc86..07903a3441 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -826,11 +826,15 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_preload_with_interpolation
- post = Post.includes(:comments_with_interpolated_conditions).find(posts(:welcome).id)
- assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions
+ assert_deprecated do
+ post = Post.includes(:comments_with_interpolated_conditions).find(posts(:welcome).id)
+ assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions
+ end
- post = Post.joins(:comments_with_interpolated_conditions).find(posts(:welcome).id)
- assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions
+ assert_deprecated do
+ post = Post.joins(:comments_with_interpolated_conditions).find(posts(:welcome).id)
+ assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions
+ end
end
def test_polymorphic_type_condition
@@ -1232,4 +1236,23 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal 2, author.posts.size
}
end
+
+ test "include instance dependent associations is deprecated" do
+ message = "association scope 'posts_with_signature' is"
+ assert_deprecated message do
+ begin
+ Author.includes(:posts_with_signature).to_a
+ rescue NoMethodError
+ # it's expected that preloading of this association fails
+ end
+ end
+
+ assert_deprecated message do
+ Author.preload(:posts_with_signature).to_a rescue NoMethodError
+ end
+
+ assert_deprecated message do
+ Author.eager_load(:posts_with_signature).to_a
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 5d33634da2..cfdfff6af9 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -83,6 +83,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
country.treaties << treaty
end
+ def test_marshal_dump
+ post = posts :welcome
+ preloaded = Post.includes(:categories).find post.id
+ assert_equal preloaded, Marshal.load(Marshal.dump(preloaded))
+ end
+
def test_should_property_quote_string_primary_keys
setup_data_for_habtm_case
@@ -217,6 +223,24 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal developers(:poor_jamis, :jamis, :david), projects(:active_record).developers
end
+ def test_habtm_collection_size_from_build
+ devel = Developer.create("name" => "Fred Wu")
+ devel.projects << Project.create("name" => "Grimetime")
+ devel.projects.build
+
+ assert_equal 2, devel.projects.size
+ end
+
+ def test_habtm_collection_size_from_params
+ devel = Developer.new({
+ projects_attributes: {
+ '0' => {}
+ }
+ })
+
+ assert_equal 1, devel.projects.size
+ end
+
def test_build
devel = Developer.find(1)
proj = assert_no_queries { devel.projects.build("name" => "Projekt") }
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 2453d6cf58..5f01352ab4 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1894,4 +1894,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_not pirate.valid?(:conference)
assert_equal "can't be blank", ship.errors[:name].first
end
+
+ test 'association with instance dependent scope' do
+ bob = authors(:bob)
+ Post.create!(title: "signed post by bob", body: "stuff", author: authors(:bob))
+ Post.create!(title: "anonymous post", body: "more stuff", author: authors(:bob))
+ assert_equal ["misc post by bob", "other post by bob",
+ "signed post by bob"], bob.posts_with_signature.map(&:title).sort
+
+ assert_equal [], authors(:david).posts_with_signature.map(&:title)
+ end
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 823146b399..5d963098fb 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'models/aircraft'
require 'models/post'
require 'models/comment'
require 'models/author'
@@ -829,4 +830,17 @@ class PersistenceTest < ActiveRecord::TestCase
end
end
+ def test_persist_inherited_class_with_different_table_name
+ minimalistic_aircrafts = Class.new(Minimalistic) do
+ self.table_name = "aircraft"
+ end
+
+ assert_difference "Aircraft.count", 1 do
+ aircraft = minimalistic_aircrafts.create(name: "Wright Flyer")
+ aircraft.name = "Wright Glider"
+ aircraft.save
+ end
+
+ assert_equal "Wright Glider", Aircraft.last.name
+ end
end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index d80da06e27..a6e1dc72e5 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -14,28 +14,28 @@ class ValidationsTest < ActiveRecord::TestCase
# Other classes we mess with will be dealt with in the specific tests
repair_validations(Topic)
- def test_error_on_create
+ def test_valid_uses_create_context_when_new
r = WrongReply.new
r.title = "Wrong Create"
- assert !r.save
+ assert_not r.valid?
assert r.errors[:title].any?, "A reply with a bad title should mark that attribute as invalid"
assert_equal ["is Wrong Create"], r.errors[:title], "A reply with a bad content should contain an error"
end
- def test_error_on_update
+ def test_valid_uses_update_context_when_persisted
r = WrongReply.new
r.title = "Bad"
r.content = "Good"
- assert r.save, "First save should be successful"
+ assert r.save, "First validation should be successful"
r.title = "Wrong Update"
- assert !r.save, "Second save should fail"
+ assert_not r.valid?, "Second validation should fail"
assert r.errors[:title].any?, "A reply with a bad title should mark that attribute as invalid"
assert_equal ["is Wrong Update"], r.errors[:title], "A reply with a bad content should contain an error"
end
- def test_error_on_given_context
+ def test_valid_using_special_context
r = WrongReply.new(:title => "Valid title")
assert !r.valid?(:special_case)
assert_equal "Invalid", r.errors[:author_name].join
@@ -45,11 +45,11 @@ class ValidationsTest < ActiveRecord::TestCase
assert r.valid?(:special_case)
r.author_name = nil
- assert !r.save(:context => :special_case)
+ assert_not r.valid?(:special_case)
assert_equal "Invalid", r.errors[:author_name].join
r.author_name = "secret"
- assert r.save(:context => :special_case)
+ assert r.valid?(:special_case)
end
def test_validate
@@ -100,7 +100,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
end
- def test_create_without_validation
+ def test_save_without_validation
reply = WrongReply.new
assert !reply.save
assert reply.save(:validate => false)
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index c197951c71..8949cf5826 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -140,6 +140,8 @@ class Author < ActiveRecord::Base
has_many :posts_with_default_include, :class_name => 'PostWithDefaultInclude'
has_many :comments_on_posts_with_default_include, :through => :posts_with_default_include, :source => :comments
+ has_many :posts_with_signature, ->(record) { where("posts.title LIKE ?", "%by #{record.name.downcase}%") }, class_name: "Post"
+
scope :relation_include_posts, -> { includes(:posts) }
scope :relation_include_tags, -> { includes(:tags) }
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 762259ffa3..0a614c3bfd 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -13,6 +13,8 @@ class Developer < ActiveRecord::Base
end
end
+ accepts_nested_attributes_for :projects
+
has_and_belongs_to_many :projects_extended_by_name,
-> { extending(DeveloperProjectsAssociationExtension) },
:class_name => "Project",