aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md137
-rw-r--r--activerecord/README.rdoc2
-rw-r--r--activerecord/RUNNING_UNIT_TESTS.rdoc42
-rw-r--r--activerecord/Rakefile3
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/association_relation.rb18
-rw-r--r--activerecord/lib/active_record/associations.rb64
-rw-r--r--activerecord/lib/active_record/associations/association.rb13
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb28
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb124
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb8
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb10
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb11
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb8
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb17
-rw-r--r--activerecord/lib/active_record/autosave_association.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb68
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb40
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/counter_cache.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb13
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb6
-rw-r--r--activerecord/lib/active_record/inheritance.rb14
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb26
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb11
-rw-r--r--activerecord/lib/active_record/null_relation.rb8
-rw-r--r--activerecord/lib/active_record/persistence.rb42
-rw-r--r--activerecord/lib/active_record/querying.rb7
-rw-r--r--activerecord/lib/active_record/railtie.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake4
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb6
-rw-r--r--activerecord/lib/active_record/reflection.rb177
-rw-r--r--activerecord/lib/active_record/relation.rb76
-rw-r--r--activerecord/lib/active_record/relation/batches.rb58
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb28
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb137
-rw-r--r--activerecord/lib/active_record/relation/merger.rb49
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb10
-rw-r--r--activerecord/lib/active_record/schema.rb2
-rw-r--r--activerecord/lib/active_record/schema_migration.rb42
-rw-r--r--activerecord/lib/active_record/test_case.rb4
-rw-r--r--activerecord/lib/active_record/transactions.rb4
-rw-r--r--activerecord/lib/active_record/validations/associated.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb7
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb19
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb17
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb58
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb14
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb70
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb2
-rw-r--r--activerecord/test/cases/ar_schema_test.rb20
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb14
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb8
-rw-r--r--activerecord/test/cases/associations_test.rb15
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb18
-rw-r--r--activerecord/test/cases/base_test.rb83
-rw-r--r--activerecord/test/cases/batches_test.rb12
-rw-r--r--activerecord/test/cases/calculations_test.rb26
-rw-r--r--activerecord/test/cases/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/connection_pool_test.rb2
-rw-r--r--activerecord/test/cases/core_test.rb33
-rw-r--r--activerecord/test/cases/counter_cache_test.rb12
-rw-r--r--activerecord/test/cases/disconnected_test.rb26
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb5
-rw-r--r--activerecord/test/cases/finder_test.rb12
-rw-r--r--activerecord/test/cases/fixtures_test.rb16
-rw-r--r--activerecord/test/cases/locking_test.rb2
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb31
-rw-r--r--activerecord/test/cases/migration/index_test.rb10
-rw-r--r--activerecord/test/cases/migration_test.rb9
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb20
-rw-r--r--activerecord/test/cases/persistence_test.rb15
-rw-r--r--activerecord/test/cases/primary_keys_test.rb2
-rw-r--r--activerecord/test/cases/readonly_test.rb18
-rw-r--r--activerecord/test/cases/reflection_test.rb6
-rw-r--r--activerecord/test/cases/relation_test.rb7
-rw-r--r--activerecord/test/cases/relations_test.rb27
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb6
-rw-r--r--activerecord/test/cases/store_test.rb5
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb17
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb2
-rw-r--r--activerecord/test/models/author.rb2
-rw-r--r--activerecord/test/models/club.rb2
-rw-r--r--activerecord/test/models/company.rb2
-rw-r--r--activerecord/test/models/interest.rb2
-rw-r--r--activerecord/test/models/man.rb2
-rw-r--r--activerecord/test/models/member.rb2
-rw-r--r--activerecord/test/models/member_detail.rb2
-rw-r--r--activerecord/test/models/person.rb1
-rw-r--r--activerecord/test/models/reply.rb1
-rw-r--r--activerecord/test/schema/schema.rb1
111 files changed, 1354 insertions, 781 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index a3b27425f7..21b0a52180 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,136 @@
+* Fixture setup does no longer depend on `ActiveRecord::Base.configurations`.
+ This is relevant when `ENV["DATABASE_URL"]` is used in place of a `database.yml`.
+
+ *Yves Senn*
+
+* Fix mysql2 adapter raises the correct exception when executing a query on a
+ closed connection.
+
+ *Yves Senn*
+
+* Ambiguous reflections are on :through relationships are no longer supported.
+ For example, you need to change this:
+
+ class Author < ActiveRecord::Base
+ has_many :posts
+ has_many :taggings, :through => :posts
+ end
+
+ class Post < ActiveRecord::Base
+ has_one :tagging
+ has_many :taggings
+ end
+
+ class Tagging < ActiveRecord::Base
+ end
+
+ To this:
+
+ class Author < ActiveRecord::Base
+ has_many :posts
+ has_many :taggings, :through => :posts, :source => :tagging
+ end
+
+ class Post < ActiveRecord::Base
+ has_one :tagging
+ has_many :taggings
+ end
+
+ class Tagging < ActiveRecord::Base
+ end
+
+* Remove column restrictions for `count`, let the database raise if the SQL is
+ invalid. The previous behavior was untested and surprising for the user.
+ Fixes #5554.
+
+ Example:
+
+ User.select("name, username").count
+ # Before => SELECT count(*) FROM users
+ # After => ActiveRecord::StatementInvalid
+
+ # you can still use `count(:all)` to perform a query unrelated to the
+ # selected columns
+ User.select("name, username").count(:all) # => SELECT count(*) FROM users
+
+ *Yves Senn*
+
+* Rails now automatically detects inverse associations. If you do not set the
+ `:inverse_of` option on the association, then Active Record will guess the
+ inverse association based on heuristics.
+
+ Note that automatic inverse detection only works on `has_many`, `has_one`,
+ and `belongs_to` associations. Extra options on the associations will
+ also prevent the association's inverse from being found automatically.
+
+ The automatic guessing of the inverse association uses a heuristic based
+ on the name of the class, so it may not work for all associations,
+ especially the ones with non-standard names.
+
+ You can turn off the automatic detection of inverse associations by setting
+ the `:inverse_of` option to `false` like so:
+
+ class Taggable < ActiveRecord::Base
+ belongs_to :tag, inverse_of: false
+ end
+
+ *John Wang*
+
+* Fix `add_column` with `array` option when using PostgreSQL. Fixes #10432
+
+ *Adam Anderson*
+
+* Usage of `implicit_readonly` is being removed`. Please use `readonly` method
+ explicitly to mark records as `readonly.
+ Fixes #10615.
+
+ Example:
+
+ user = User.joins(:todos).select("users.*, todos.title as todos_title").readonly(true).first
+ user.todos_title = 'clean pet'
+ user.save! # will raise error
+
+ *Yves Senn*
+
+* Fix the `:primary_key` option for `has_many` associations.
+ Fixes #10693.
+
+ *Yves Senn*
+
+* Fix bug where tiny types are incorrectly coerced as boolean when the length is more than 1.
+
+ Fixes #10620.
+
+ *Aaron Peterson*
+
+* Also support extensions in PostgreSQL 9.1. This feature has been supported since 9.1.
+
+ *kennyj*
+
+* Deprecate `ConnectionAdapters::SchemaStatements#distinct`,
+ as it is no longer used by internals.
+
+ *Ben Woosley#
+
+* Fix pending migrations error when loading schema and `ActiveRecord::Base.table_name_prefix`
+ is not blank.
+
+ Call `assume_migrated_upto_version` on connection to prevent it from first
+ being picked up in `method_missing`.
+
+ In the base class, `Migration`, `method_missing` expects the argument to be a
+ table name, and calls `proper_table_name` on the arguments before sending to
+ `connection`. If `table_name_prefix` or `table_name_suffix` is used, the schema
+ version changes to `prefix_version_suffix`, breaking `rake test:prepare`.
+
+ Fixes #10411.
+
+ *Kyle Stevens*
+
+* Method `read_attribute_before_type_cast` should accept input as symbol.
+
+ *Neeraj Singh*
+
* Confirm a record has not already been destroyed before decrementing counter cache.
*Ben Tucker*
@@ -47,8 +180,8 @@
*Olek Janiszewski*
-* fixes bug introduced by #3329. Now, when autosaving associations,
- deletions happen before inserts and saves. This prevents a 'duplicate
+* fixes bug introduced by #3329. Now, when autosaving associations,
+ deletions happen before inserts and saves. This prevents a 'duplicate
unique value' database error that would occur if a record being created had
the same value on a unique indexed field as that of a record being destroyed.
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index 822e460918..e04abe9b37 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -175,7 +175,7 @@ by relying on a number of conventions that make it easy for Active Record to inf
complex relations and structures from a minimal amount of explicit direction.
Convention over Configuration:
-* No XML-files!
+* No XML files!
* Lots of reflection and run-time extension
* Magic is not inherently a bad word
diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc
index 2f3d516c43..c3ee34da55 100644
--- a/activerecord/RUNNING_UNIT_TESTS.rdoc
+++ b/activerecord/RUNNING_UNIT_TESTS.rdoc
@@ -1,31 +1,43 @@
== Setup
-If you don't have the environment set make sure to read
-
- http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#testing-active-record
+If you don't have an environment for running tests, read
+http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#setting-up-a-development-environment
== Running the Tests
-You can run a particular test file from the command line, e.g.
+To run a specific test:
+
+ $ ruby -Itest test/cases/base_test.rb -n method_name
+
+To run a set of tests:
$ ruby -Itest test/cases/base_test.rb
-To run a specific test:
+You can also run tests that depend upon a specific database backend. For
+example:
- $ ruby -Itest test/cases/base_test.rb -n test_something_works
+ $ bundle exec rake test_sqlite3
-You can run with a database other than the default you set in test/config.yml, using the ARCONN
-environment variable:
+Simply executing <tt>bundle exec rake test</tt> is equivalent to the following:
- $ ARCONN=postgresql ruby -Itest test/cases/base_test.rb
+ $ bundle exec rake test_mysql
+ $ bundle exec rake test_mysql2
+ $ bundle exec rake test_postgresql
+ $ bundle exec rake test_sqlite3
-You can run all the tests for a given database via rake:
+There should be tests available for each database backend listed in the {Config
+File}[rdoc-label:label-Config+File]. (the exact set of available tests is
+defined in +Rakefile+)
- $ rake test_mysql
+== Config File
-The 'rake test' task will run all the tests for mysql, mysql2, sqlite3 and postgresql.
+If +test/config.yml+ is present, it's parameters are obeyed. Otherwise, the
+parameters in +test/config.example.yml+ are obeyed.
-== Custom Config file
+You can override the +connections:+ parameter in either file using the +ARCONN+
+(Active Record CONNection) environment variable:
+
+ $ ARCONN=postgresql ruby -Itest test/cases/base_test.rb
-By default, the config file is expected to be at the path test/config.yml. You can specify a
-custom location with the ARCONFIG environment variable.
+You can specify a custom location for the config file using the +ARCONFIG+
+environment variable.
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 0523314128..136bb1dfbc 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -1,5 +1,4 @@
require 'rake/testtask'
-require 'rake/packagetask'
require 'rubygems/package_task'
require File.expand_path(File.dirname(__FILE__)) + "/test/config"
@@ -125,8 +124,6 @@ namespace :postgresql do
%w(arunit arunit2).each do |db|
if version < "9.1.0"
puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html"
- else
- %x( psql #{config[db]['database']} -c "CREATE EXTENSION hstore;" )
end
end
end
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 0330c0f37f..994cacb4f9 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -76,6 +76,7 @@ module ActiveRecord
autoload :AutosaveAssociation
autoload :Relation
+ autoload :AssociationRelation
autoload :NullRelation
autoload_under 'relation' do
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
new file mode 100644
index 0000000000..20516bba0c
--- /dev/null
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -0,0 +1,18 @@
+module ActiveRecord
+ class AssociationRelation < Relation
+ def initialize(klass, table, association)
+ super(klass, table)
+ @association = association
+ end
+
+ def proxy_association
+ @association
+ end
+
+ private
+
+ def exec_queries
+ super.each { |r| @association.set_inverse_instance r }
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 5e5995f566..6fd4f3042c 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -172,7 +172,7 @@ module ActiveRecord
@association_cache[name] = association
end
- # Associations are a set of macro-like class methods for tying objects together through
+ # \Associations are a set of macro-like class methods for tying objects together through
# foreign keys. They express relationships like "Project has one Project Manager"
# or "Project belongs to a Portfolio". Each macro adds a number of methods to the
# class which are specialized according to the collection or association symbol and the
@@ -365,11 +365,11 @@ module ActiveRecord
# there is some special behavior you should be aware of, mostly involving the saving of
# associated objects.
#
- # You can set the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
+ # You can set the <tt>:autosave</tt> option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
# <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association. Setting it
# to +true+ will _always_ save the members, whereas setting it to +false+ will
- # _never_ save the members. More details about :autosave option is available at
- # autosave_association.rb .
+ # _never_ save the members. More details about <tt>:autosave</tt> option is available at
+ # AutosaveAssociation.
#
# === One-to-one associations
#
@@ -402,7 +402,7 @@ module ActiveRecord
#
# == Customizing the query
#
- # Associations are built from <tt>Relation</tt>s, and you can use the <tt>Relation</tt> syntax
+ # \Associations are built from <tt>Relation</tt>s, and you can use the <tt>Relation</tt> syntax
# to customize them. For example, to add a condition:
#
# class Blog < ActiveRecord::Base
@@ -568,6 +568,8 @@ module ActiveRecord
# @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
# @group.avatars.delete(@group.avatars.last) # so would this
#
+ # == Setting Inverses
+ #
# If you are using a +belongs_to+ on the join model, it is a good idea to set the
# <tt>:inverse_of</tt> option on the +belongs_to+, which will mean that the following example
# works correctly (where <tt>tags</tt> is a +has_many+ <tt>:through</tt> association):
@@ -584,7 +586,27 @@ module ActiveRecord
# belongs_to :tag, inverse_of: :taggings
# end
#
- # == Nested Associations
+ # If you do not set the <tt>:inverse_of</tt> record, the association will
+ # do its best to match itself up with the correct inverse. Automatic
+ # inverse detection only works on <tt>has_many</tt>, <tt>has_one</tt>, and
+ # <tt>belongs_to</tt> associations.
+ #
+ # Extra options on the associations, as defined in the
+ # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will
+ # also prevent the association's inverse from being found automatically.
+ #
+ # The automatic guessing of the inverse association uses a heuristic based
+ # on the name of the class, so it may not work for all associations,
+ # especially the ones with non-standard names.
+ #
+ # You can turn off the automatic detection of inverse associations by setting
+ # the <tt>:inverse_of</tt> option to <tt>false</tt> like so:
+ #
+ # class Taggable < ActiveRecord::Base
+ # belongs_to :tag, inverse_of: false
+ # end
+ #
+ # == Nested \Associations
#
# You can actually specify *any* association with the <tt>:through</tt> option, including an
# association which has a <tt>:through</tt> option itself. For example:
@@ -627,7 +649,7 @@ module ActiveRecord
# add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
# intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
#
- # == Polymorphic Associations
+ # == Polymorphic \Associations
#
# Polymorphic associations on models are not restricted on what types of models they
# can be associated with. Rather, they specify an interface that a +has_many+ association
@@ -789,7 +811,7 @@ module ActiveRecord
# For example if all the addressables are either of class Person or Company then a total
# of 3 queries will be executed. The list of addressable types to load is determined on
# the back of the addresses loaded. This is not supported if Active Record has to fallback
- # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError.
+ # to the previous implementation of eager loading and will raise <tt>ActiveRecord::EagerLoadPolymorphicError</tt>.
# The reason is that the parent model's type is a column value so its corresponding table
# name cannot be put in the +FROM+/+JOIN+ clauses of that query.
#
@@ -1024,7 +1046,7 @@ module ActiveRecord
# An empty array is returned if none are found.
# [collection<<(object, ...)]
# Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
- # Note that this operation instantly fires update sql without waiting for the save or update call on the
+ # Note that this operation instantly fires update SQL without waiting for the save or update call on the
# parent object, unless the parent object is a new record.
# [collection.delete(object, ...)]
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
@@ -1060,10 +1082,10 @@ module ActiveRecord
# [collection.size]
# Returns the number of associated objects.
# [collection.find(...)]
- # Finds an associated object according to the same rules as ActiveRecord::Base.find.
+ # Finds an associated object according to the same rules as <tt>ActiveRecord::Base.find</tt>.
# [collection.exists?(...)]
# Checks whether an associated object with the given conditions exists.
- # Uses the same rules as ActiveRecord::Base.exists?.
+ # Uses the same rules as <tt>ActiveRecord::Base.exists?</tt>.
# [collection.build(attributes = {}, ...)]
# Returns one or more new objects of the collection type that have been instantiated
# with +attributes+ and linked to this object through a foreign key, but have not yet
@@ -1082,7 +1104,7 @@ module ActiveRecord
#
# === Example
#
- # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
+ # A <tt>Firm</tt> class declares <tt>has_many :clients</tt>, which will add:
# * <tt>Firm#clients</tt> (similar to <tt>Client.where(firm_id: id)</tt>)
# * <tt>Firm#clients<<</tt>
# * <tt>Firm#clients.delete</tt>
@@ -1116,8 +1138,8 @@ module ActiveRecord
# Controls what happens to the associated objects when
# their owner is destroyed. Note that these are implemented as
# callbacks, and Rails executes callbacks in order. Therefore, other
- # similar callbacks may affect the :dependent behavior, and the
- # :dependent behavior may affect other callbacks.
+ # similar callbacks may affect the <tt>:dependent</tt> behavior, and the
+ # <tt>:dependent</tt> behavior may affect other callbacks.
#
# * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
# * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
@@ -1163,8 +1185,8 @@ module ActiveRecord
# If true, always save the associated objects or destroy them if marked for destruction,
# when saving the parent object. If false, never save or destroy the associated objects.
# By default, only save associated objects that are new records. This option is implemented as a
- # before_save callback. Because callbacks are run in the order they are defined, associated objects
- # may need to be explicitly saved in any user-defined before_save callbacks.
+ # +before_save+ callback. Because callbacks are run in the order they are defined, associated objects
+ # may need to be explicitly saved in any user-defined +before_save+ callbacks.
#
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
# [:inverse_of]
@@ -1189,7 +1211,7 @@ module ActiveRecord
# Specifies a one-to-one association with another class. This method should only be used
# if the other class contains the foreign key. If the current class contains the foreign key,
# then you should use +belongs_to+ instead. See also ActiveRecord::Associations::ClassMethods's overview
- # on when to use has_one and when to use belongs_to.
+ # on when to use +has_one+ and when to use +belongs_to+.
#
# The following methods for retrieval and query of a single associated object will be added:
#
@@ -1357,7 +1379,7 @@ module ActiveRecord
# class is created and decremented when it's destroyed. This requires that a column
# named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
# is used on the associate class (such as a Post class) - that is the migration for
- # <tt>#{table_name}_count</tt> is created on the associate class (such that Post.comments_count will
+ # <tt>#{table_name}_count</tt> is created on the associate class (such that <tt>Post.comments_count</tt> will
# return the count cached, see note below). You can also specify a custom counter
# cache column by providing a column name instead of a +true+/+false+ value to this
# option (e.g., <tt>counter_cache: :my_custom_counter</tt>.)
@@ -1439,7 +1461,7 @@ module ActiveRecord
# [collection<<(object, ...)]
# Adds one or more objects to the collection by creating associations in the join table
# (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
- # Note that this operation instantly fires update sql without waiting for the save or update call on the
+ # Note that this operation instantly fires update SQL without waiting for the save or update call on the
# parent object, unless the parent object is a new record.
# [collection.delete(object, ...)]
# Removes one or more objects from the collection by removing their associations from the join table.
@@ -1462,10 +1484,10 @@ module ActiveRecord
# [collection.find(id)]
# Finds an associated object responding to the +id+ and that
# meets the condition that it has to be associated with this object.
- # Uses the same rules as ActiveRecord::Base.find.
+ # Uses the same rules as <tt>ActiveRecord::Base.find</tt>.
# [collection.exists?(...)]
# Checks whether an associated object with the given conditions exists.
- # Uses the same rules as ActiveRecord::Base.exists?.
+ # Uses the same rules as <tt>ActiveRecord::Base.exists?</tt>.
# [collection.build(attributes = {})]
# Returns a new object of the collection type that has been instantiated
# with +attributes+ and linked to this object through the join table, but has not yet been saved.
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 710babe024..ee62298793 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -122,7 +122,11 @@ module ActiveRecord
# Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
# through association's scope)
def target_scope
- klass.all
+ all = klass.all
+ scope = AssociationRelation.new(klass, klass.arel_table, self)
+ scope.merge! all
+ scope.default_scoped = all.default_scoped?
+ scope
end
# Loads the \target if needed and returns it.
@@ -196,13 +200,14 @@ module ActiveRecord
creation_attributes.each { |key, value| record[key] = value }
end
- # Should be true if there is a foreign key present on the owner which
+ # Returns true if there is a foreign key present on the owner which
# references the target. This is used to determine whether we can load
# the target if the owner is currently a new record (and therefore
- # without a key).
+ # without a key). If the owner is a new record then foreign_key must
+ # be present in order to load target.
#
# Currently implemented by belongs_to (vanilla and polymorphic) and
- # has_one/has_many :through associations which go through a belongs_to
+ # has_one/has_many :through associations which go through a belongs_to.
def foreign_key_present?
false
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index aa5551fe0c..f1bec5787a 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -96,7 +96,7 @@ module ActiveRecord
item = eval_scope(klass, scope_chain_item)
if scope_chain_item == self.reflection.scope
- scope.merge! item.except(:where, :includes)
+ scope.merge! item.except(:where, :includes, :bind)
end
scope.includes! item.includes_values
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 5c37f42794..3254da4677 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -1,3 +1,15 @@
+# This is the parent Association class which defines the variables
+# used by all associations.
+#
+# The hierarchy is defined as follows:
+# Association
+# - SingularAssociation
+# - BelongsToAssociation
+# - HasOneAssociation
+# - CollectionAssociation
+# - HasManyAssociation
+# - HasAndBelongsToManyAssociation
+
module ActiveRecord::Associations::Builder
class Association #:nodoc:
class << self
@@ -58,6 +70,13 @@ module ActiveRecord::Associations::Builder
def validate_options
options.assert_valid_keys(valid_options)
end
+
+ # Defines the setter and getter methods for the association
+ # class Post < ActiveRecord::Base
+ # has_many :comments
+ # end
+ #
+ # Post.first.comments and Post.first.comments= methods are defined by this method...
def define_accessors
define_readers
@@ -92,13 +111,8 @@ module ActiveRecord::Associations::Builder
)
end
- mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{macro}_dependent_for_#{name}
- association(:#{name}).handle_dependency
- end
- CODE
-
- model.before_destroy "#{macro}_dependent_for_#{name}"
+ n = name
+ model.before_destroy lambda { |o| o.association(n).handle_dependency }
end
def valid_dependent_options
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 63e9526436..d4e1a0dda1 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -19,82 +19,116 @@ module ActiveRecord::Associations::Builder
reflection
end
- def add_counter_cache_callbacks(reflection)
- cache_column = reflection.counter_cache_column
- foreign_key = reflection.foreign_key
+ def valid_dependent_options
+ [:destroy, :delete]
+ end
+
+ private
- mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def belongs_to_counter_cache_after_create_for_#{name}
- if record = #{name}
- record.class.increment_counter(:#{cache_column}, record.id)
+ def add_counter_cache_methods(mixin)
+ return if mixin.method_defined? :belongs_to_counter_cache_after_create
+
+ mixin.class_eval do
+ def belongs_to_counter_cache_after_create(association, reflection)
+ if record = send(association.name)
+ cache_column = reflection.counter_cache_column
+ record.class.increment_counter(cache_column, record.id)
@_after_create_counter_called = true
end
end
- def belongs_to_counter_cache_before_destroy_for_#{name}
- unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == #{foreign_key.to_sym.inspect}
- record = #{name}
+ def belongs_to_counter_cache_before_destroy(association, reflection)
+ foreign_key = reflection.foreign_key.to_sym
+ unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
+ record = send association.name
if record && !self.destroyed?
- record.class.decrement_counter(:#{cache_column}, record.id)
+ cache_column = reflection.counter_cache_column
+ record.class.decrement_counter(cache_column, record.id)
end
end
end
- def belongs_to_counter_cache_after_update_for_#{name}
+ def belongs_to_counter_cache_after_update(association, reflection)
+ foreign_key = reflection.foreign_key
+ cache_column = reflection.counter_cache_column
+
if (@_after_create_counter_called ||= false)
@_after_create_counter_called = false
- elsif self.#{foreign_key}_changed? && !new_record? && defined?(#{name.to_s.camelize})
- model = #{name.to_s.camelize}
- foreign_key_was = self.#{foreign_key}_was
- foreign_key = self.#{foreign_key}
+ elsif attribute_changed?(foreign_key) && !new_record? && association.constructable?
+ model = reflection.klass
+ foreign_key_was = attribute_was foreign_key
+ foreign_key = attribute foreign_key
if foreign_key && model.respond_to?(:increment_counter)
- model.increment_counter(:#{cache_column}, foreign_key)
+ model.increment_counter(cache_column, foreign_key)
end
if foreign_key_was && model.respond_to?(:decrement_counter)
- model.decrement_counter(:#{cache_column}, foreign_key_was)
+ model.decrement_counter(cache_column, foreign_key_was)
end
end
end
- CODE
+ end
+ end
+
+ def add_counter_cache_callbacks(reflection)
+ cache_column = reflection.counter_cache_column
+ add_counter_cache_methods mixin
+ association = self
+
+ model.after_create lambda { |record|
+ record.belongs_to_counter_cache_after_create(association, reflection)
+ }
- model.after_create "belongs_to_counter_cache_after_create_for_#{name}"
- model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}"
- model.after_update "belongs_to_counter_cache_after_update_for_#{name}"
+ model.before_destroy lambda { |record|
+ record.belongs_to_counter_cache_before_destroy(association, reflection)
+ }
+
+ model.after_update lambda { |record|
+ record.belongs_to_counter_cache_after_update(association, reflection)
+ }
klass = reflection.class_name.safe_constantize
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
end
- def add_touch_callbacks(reflection)
- mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def belongs_to_touch_after_save_or_destroy_for_#{name}
- foreign_key_field = #{reflection.foreign_key.inspect}
- old_foreign_id = attribute_was(foreign_key_field)
+ def self.touch_record(o, foreign_key, name, touch) # :nodoc:
+ old_foreign_id = o.attribute_was(foreign_key)
- if old_foreign_id
- klass = association(#{name.inspect}).klass
- old_record = klass.find_by(klass.primary_key => old_foreign_id)
+ if old_foreign_id
+ klass = o.association(name).klass
+ old_record = klass.find_by(klass.primary_key => old_foreign_id)
- if old_record
- old_record.touch #{options[:touch].inspect if options[:touch] != true}
- end
- end
-
- record = #{name}
- unless record.nil? || record.new_record?
- record.touch #{options[:touch].inspect if options[:touch] != true}
+ if old_record
+ if touch != true
+ old_record.touch touch
+ else
+ old_record.touch
end
end
- CODE
-
- model.after_save "belongs_to_touch_after_save_or_destroy_for_#{name}"
- model.after_touch "belongs_to_touch_after_save_or_destroy_for_#{name}"
- model.after_destroy "belongs_to_touch_after_save_or_destroy_for_#{name}"
+ end
+
+ record = o.send name
+ unless record.nil? || record.new_record?
+ if touch != true
+ record.touch touch
+ else
+ record.touch
+ end
+ end
end
- def valid_dependent_options
- [:destroy, :delete]
+ def add_touch_callbacks(reflection)
+ foreign_key = reflection.foreign_key
+ n = name
+ touch = options[:touch]
+
+ callback = lambda { |record|
+ BelongsTo.touch_record(record, foreign_key, n, touch)
+ }
+
+ model.after_save callback
+ model.after_touch callback
+ model.after_destroy callback
end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index fdead16761..9c6690b721 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -1,3 +1,5 @@
+# This class is inherited by the has_many and has_many_and_belongs_to_many association classes
+
require 'active_record/associations'
module ActiveRecord::Associations::Builder
@@ -66,6 +68,8 @@ module ActiveRecord::Associations::Builder
model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
end
+ # Defines the setter and getter methods for the collection_singular_ids.
+
def define_readers
super
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index 429def5455..0d1bdd21ee 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
end
def valid_options
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :automatic_inverse_of, :counter_cache]
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
end
def valid_dependent_options
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index f06426a09d..76e48e66e5 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -1,7 +1,9 @@
+# This class is inherited by the has_one and belongs_to association classes
+
module ActiveRecord::Associations::Builder
class SingularAssociation < Association #:nodoc:
def valid_options
- super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of, :automatic_inverse_of]
+ super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of]
end
def constructable?
@@ -13,6 +15,8 @@ module ActiveRecord::Associations::Builder
define_constructors if constructable?
end
+ # Defines the (build|create)_association methods for belongs_to or has_one association
+
def define_constructors
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def build_#{name}(*args, &block)
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 2a00ac1386..efd7ecb97c 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -237,11 +237,11 @@ module ActiveRecord
end
end
- # Destroy +records+ and remove them from this association calling
- # +before_remove+ and +after_remove+ callbacks.
+ # Deletes the +records+ and removes them from this association calling
+ # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
#
- # Note that this method will _always_ remove records from the database
- # ignoring the +:dependent+ option.
+ # Note that this method removes records from the database ignoring the
+ # +:dependent+ option.
def destroy(*records)
records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
delete_or_destroy(records, :destroy)
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 56e57cc36e..e82c195335 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -422,9 +422,9 @@ module ActiveRecord
@association.delete_all
end
- # Deletes the records of the collection directly from the database.
- # This will _always_ remove the records ignoring the +:dependent+
- # option.
+ # Deletes the records of the collection directly from the database
+ # ignoring the +:dependent+ option. It invokes +before_remove+,
+ # +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
#
# class Person < ActiveRecord::Base
# has_many :pets
@@ -847,9 +847,7 @@ module ActiveRecord
# Returns a <tt>Relation</tt> object for the records in this association
def scope
- @association.scope.tap do |scope|
- scope.proxy_association = @association
- end
+ @association.scope
end
# :nodoc:
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 29fae809da..cf8a589496 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -115,8 +115,7 @@ module ActiveRecord
if records == :all
scope = self.scope
else
- keys = records.map { |r| r[reflection.association_primary_key] }
- scope = self.scope.where(reflection.association_primary_key => keys)
+ scope = self.scope.where(reflection.klass.primary_key => records)
end
if method == :delete_all
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 28e081c03c..5aa17e5fbb 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -48,13 +48,19 @@ module ActiveRecord
end
def join_associations
- join_parts.last(join_parts.length - 1)
+ join_parts.drop 1
end
def join_base
join_parts.first
end
+ def join_relation(relation)
+ join_associations.inject(relation) do |rel,association|
+ association.join_relation(rel)
+ end
+ end
+
def columns
join_parts.collect { |join_part|
table = join_part.aliased_table
@@ -125,8 +131,7 @@ module ActiveRecord
ref[association.reflection.name] ||= {}
end
- def build(associations, parent = nil, join_type = Arel::InnerJoin)
- parent ||= join_parts.last
+ def build(associations, parent = join_parts.last, join_type = Arel::InnerJoin)
case associations
when Symbol, String
reflection = parent.reflections[associations.intern] or
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index e4d17451dc..b81aecb4e5 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -106,12 +106,16 @@ module ActiveRecord
]
end
- scope_chain_items.each do |item|
+ constraint = scope_chain_items.inject(constraint) do |chain, item|
unless item.is_a?(Relation)
item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item)
end
- constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty?
+ if item.arel.constraints.empty?
+ chain
+ else
+ chain.and(item.arel.constraints)
+ end
end
manager.from(join(table, constraint))
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index e536f5ebcc..75377bba57 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -1,3 +1,4 @@
+require 'active_model/forbidden_attributes_protection'
module ActiveRecord
module AttributeAssignment
@@ -44,7 +45,7 @@ module ActiveRecord
if respond_to?("#{k}=")
raise
else
- raise UnknownAttributeError, "unknown attribute: #{k}"
+ raise UnknownAttributeError.new(self, k)
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index a23baeaced..f596a8b02e 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -41,8 +41,9 @@ module ActiveRecord
# task.read_attribute_before_type_cast('id') # => '1'
# task.read_attribute('completed_on') # => Sun, 21 Oct 2012
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
+ # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
def read_attribute_before_type_cast(attr_name)
- @attributes[attr_name]
+ @attributes[attr_name.to_s]
end
# Returns a hash of attributes before typecasting and deserialization.
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 7f1ebab4cd..9caf73e627 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -50,20 +50,13 @@ module ActiveRecord
end
end
- # *DEPRECATED*: Use ActiveRecord::AttributeMethods::Serialization::ClassMethods#serialized_attributes class level method instead.
- def serialized_attributes
- message = "Instance level serialized_attributes method is deprecated, please use class level method."
- ActiveSupport::Deprecation.warn message
- defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes
- end
-
class Type # :nodoc:
def initialize(column)
@column = column
end
def type_cast(value)
- value.unserialized_value
+ value.unserialized_value @column.type_cast value.value
end
def type
@@ -72,17 +65,17 @@ module ActiveRecord
end
class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
- def unserialized_value
- state == :serialized ? unserialize : value
+ def unserialized_value(v = value)
+ state == :serialized ? unserialize(v) : value
end
def serialized_value
state == :unserialized ? serialize : value
end
- def unserialize
+ def unserialize(v)
self.state = :unserialized
- self.value = coder.load(value)
+ self.value = coder.load(v)
end
def serialize
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 87d4daa6d9..a8a1847554 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -301,7 +301,7 @@ module ActiveRecord
def association_valid?(reflection, record)
return true if record.destroyed? || record.marked_for_destruction?
- unless valid = record.valid?(validation_context)
+ unless valid = record.valid?
if reflection.options[:autosave]
record.errors.each do |attribute, message|
attribute = "#{reflection.name}.#{attribute}"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 566550cbe2..0be4b5cb19 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -25,6 +25,9 @@ module ActiveRecord
end
end
+ class ChangeColumnDefinition < Struct.new(:column, :type, :options) #:nodoc:
+ end
+
# Represents the schema of an SQL table in an abstract way. This class
# provides methods for manipulating the schema representation.
#
@@ -65,8 +68,7 @@ module ActiveRecord
# Appends a primary key definition to the table definition.
# Can be called multiple times, but this is probably not a good idea.
def primary_key(name, type = :primary_key, options = {})
- options[:primary_key] = true
- column(name, type, options)
+ column(name, type, options.merge(:primary_key => true))
end
# Returns a ColumnDefinition for the column with name +name+.
@@ -270,6 +272,7 @@ module ActiveRecord
end
column.limit = limit
+ column.array = options[:array] if column.respond_to?(:array)
column.precision = options[:precision]
column.scale = options[:scale]
column.default = options[:default]
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 9c0c4e3ef0..3ac55a0f11 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -606,7 +606,7 @@ module ActiveRecord
index_options = options.delete(:index)
add_column(table_name, "#{ref_name}_id", :integer, options)
add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
- add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
+ add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
end
alias :add_belongs_to :add_reference
@@ -694,24 +694,22 @@ module ActiveRecord
end
end
- def add_column_options!(sql, options) #:nodoc:
- sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
- # must explicitly check for :null to allow change_column to work on migrations
- if options[:null] == false
- sql << " NOT NULL"
- end
- if options[:auto_increment] == true
- sql << " AUTO_INCREMENT"
- end
- end
-
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
- # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
#
- # distinct("posts.id", "posts.created_at desc")
+ # distinct("posts.id", ["posts.created_at desc"])
#
def distinct(columns, order_by)
- "DISTINCT #{columns}"
+ ActiveSupport::Deprecation.warn("#distinct is deprecated and shall be removed from future releases.")
+ "DISTINCT #{columns_for_distinct(columns, order_by)}"
+ end
+
+ # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
+ # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax - they
+ # require the order columns appear in the SELECT.
+ #
+ # columns_for_distinct("posts.id", ["posts.created_at desc"])
+ def columns_for_distinct(columns, orders) # :nodoc:
+ columns
end
# Adds timestamps (+created_at+ and +updated_at+) columns to the named table.
@@ -766,37 +764,23 @@ module ActiveRecord
column_names = Array(column_name)
index_name = index_name(table_name, column: column_names)
- if Hash === options # legacy support, since this param was a string
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
- index_type = options[:unique] ? "UNIQUE" : ""
- index_type = options[:type].to_s if options.key?(:type)
- index_name = options[:name].to_s if options.key?(:name)
- max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
+ index_type = options[:unique] ? "UNIQUE" : ""
+ index_type = options[:type].to_s if options.key?(:type)
+ index_name = options[:name].to_s if options.key?(:name)
+ max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
- if options.key?(:algorithm)
- algorithm = index_algorithms.fetch(options[:algorithm]) {
- raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
- }
- end
-
- using = "USING #{options[:using]}" if options[:using].present?
-
- if supports_partial_index?
- index_options = options[:where] ? " WHERE #{options[:where]}" : ""
- end
- else
- if options
- message = "Passing a string as third argument of `add_index` is deprecated and will" +
- " be removed in Rails 4.1." +
- " Use add_index(#{table_name.inspect}, #{column_name.inspect}, unique: true) instead"
+ if options.key?(:algorithm)
+ algorithm = index_algorithms.fetch(options[:algorithm]) {
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
+ }
+ end
- ActiveSupport::Deprecation.warn message
- end
+ using = "USING #{options[:using]}" if options[:using].present?
- index_type = options
- max_index_length = allowed_index_name_length
- algorithm = using = nil
+ if supports_partial_index?
+ index_options = options[:where] ? " WHERE #{options[:where]}" : ""
end
if index_name.length > max_index_length
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 26586f0974..e232cad982 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -116,6 +116,12 @@ module ActiveRecord
send m, o
end
+ def visit_AddColumn(o)
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ sql = "ADD #{quote_column_name(o.name)} #{sql_type}"
+ add_column_options!(sql, column_options(o))
+ end
+
private
def visit_AlterTable(o)
@@ -123,12 +129,6 @@ module ActiveRecord
sql << o.adds.map { |col| visit_AddColumn col }.join(' ')
end
- def visit_AddColumn(o)
- sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
- sql = "ADD #{quote_column_name(o.name)} #{sql_type}"
- add_column_options!(sql, column_options(o))
- end
-
def visit_ColumnDefinition(o)
sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
column_sql = "#{quote_column_name(o.name)} #{sql_type}"
@@ -149,6 +149,8 @@ module ActiveRecord
column_options[:null] = o.null unless o.null.nil?
column_options[:default] = o.default unless o.default.nil?
column_options[:column] = o
+ column_options[:first] = o.first
+ column_options[:after] = o.after
column_options
end
@@ -164,9 +166,20 @@ module ActiveRecord
@conn.type_to_sql type.to_sym, limit, precision, scale
end
- def add_column_options!(column_sql, column_options)
- @conn.add_column_options! column_sql, column_options
- column_sql
+ def add_column_options!(sql, options)
+ sql << " DEFAULT #{@conn.quote(options[:default], options[:column])}" if options_include_default?(options)
+ # must explicitly check for :null to allow change_column to work on migrations
+ if options[:null] == false
+ sql << " NOT NULL"
+ end
+ if options[:auto_increment] == true
+ sql << " AUTO_INCREMENT"
+ end
+ sql
+ end
+
+ def options_include_default?(options)
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index d098ded77c..5b25b26164 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -4,17 +4,26 @@ module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter < AbstractAdapter
class SchemaCreation < AbstractAdapter::SchemaCreation
- private
def visit_AddColumn(o)
- add_column_position!(super, o)
+ add_column_position!(super, column_options(o))
+ end
+
+ private
+ def visit_ChangeColumnDefinition(o)
+ column = o.column
+ options = o.options
+ sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
+ change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
+ add_column_options!(change_column_sql, options)
+ add_column_position!(change_column_sql, options)
end
- def add_column_position!(sql, column)
- if column.first
+ def add_column_position!(sql, options)
+ if options[:first]
sql << " FIRST"
- elsif column.after
- sql << " AFTER #{quote_column_name(column.after)}"
+ elsif options[:after]
+ sql << " AFTER #{quote_column_name(options[:after])}"
end
sql
end
@@ -661,10 +670,9 @@ module ActiveRecord
end
def add_column_sql(table_name, column_name, type, options = {})
- add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(add_column_sql, options)
- add_column_position!(add_column_sql, options)
- add_column_sql
+ td = create_table_definition table_name, options[:temporary], options[:options]
+ cd = td.new_column_definition(column_name, type, options)
+ schema_creation.visit_AddColumn cd
end
def change_column_sql(table_name, column_name, type, options = {})
@@ -678,14 +686,12 @@ module ActiveRecord
options[:null] = column.null
end
- change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(change_column_sql, options)
- add_column_position!(change_column_sql, options)
- change_column_sql
+ options[:name] = column.name
+ schema_creation.accept ChangeColumnDefinition.new column, type, options
end
def rename_column_sql(table_name, column_name, new_column_name)
- options = {}
+ options = { name: new_column_name }
if column = columns(table_name).find { |c| c.name == column_name.to_s }
options[:default] = column.default
@@ -696,9 +702,7 @@ module ActiveRecord
end
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
- rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
- add_column_options!(rename_column_sql, options)
- rename_column_sql
+ schema_creation.accept ChangeColumnDefinition.new column, current_type, options
end
def remove_column_sql(table_name, column_name, type = nil, options = {})
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 530a27d099..edeb338310 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -213,9 +213,11 @@ module ActiveRecord
# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil)
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
- # made since we established the connection
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
+ if @connection
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
+ # made since we established the connection
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
+ end
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index f23521430d..1826d88500 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -393,6 +393,14 @@ module ActiveRecord
TYPES[new] = TYPES[old]
end
+ def self.find_type(field)
+ if field.type == Mysql::Field::TYPE_TINY && field.length > 1
+ TYPES[Mysql::Field::TYPE_LONG]
+ else
+ TYPES.fetch(field.type) { Fields::Identity.new }
+ end
+ end
+
register_type Mysql::Field::TYPE_TINY, Fields::Boolean.new
register_type Mysql::Field::TYPE_LONG, Fields::Integer.new
alias_type Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_LONG
@@ -425,9 +433,7 @@ module ActiveRecord
if field.decimals > 0
types[field.name] = Fields::Decimal.new
else
- types[field.name] = Fields::TYPES.fetch(field.type) {
- Fields::Identity.new
- }
+ types[field.name] = Fields.find_type field
end
}
result_set = ActiveRecord::Result.new(types.keys, result.to_a, types)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index a9ef11aa83..a73f0ac57f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -60,7 +60,7 @@ module ActiveRecord
end
def json_to_string(object)
- if Hash === object
+ if Hash === object || Array === object
ActiveSupport::JSON.encode(object)
else
object
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 40a3b82839..e9daa5d7ff 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -30,6 +30,7 @@ module ActiveRecord
when Array
case sql_type
when 'point' then super(PostgreSQLColumn.point_to_string(value))
+ when 'json' then super(PostgreSQLColumn.json_to_string(value))
else
if column.array
"'#{PostgreSQLColumn.array_to_string(value, column, self).gsub(/'/, "''")}'"
@@ -98,6 +99,7 @@ module ActiveRecord
when Array
case column.sql_type
when 'point' then PostgreSQLColumn.point_to_string(value)
+ when 'json' then PostgreSQLColumn.json_to_string(value)
else
return super(value, column) unless column.array
PostgreSQLColumn.array_to_string(value, column, self)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 98916b06a5..a651b6c32e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -467,22 +467,17 @@ module ActiveRecord
end
end
- # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
- #
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
# requires that the ORDER BY include the distinct column.
- #
- # distinct("posts.id", ["posts.created_at desc"])
- # # => "DISTINCT posts.id, posts.created_at AS alias_0"
- def distinct(columns, orders) #:nodoc:
- order_columns = orders.map{ |s|
+ def columns_for_distinct(columns, orders) #:nodoc:
+ order_columns = orders.reject(&:blank?).map{ |s|
# Convert Arel node to string
s = s.to_sql unless s.is_a?(String)
# Remove any ASC/DESC modifiers
s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
- [super].concat(order_columns).join(', ')
+ [super, *order_columns].join(', ')
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 88b09e7999..d5a603cadc 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -622,9 +622,9 @@ module ActiveRecord
true
end
- # Returns true if pg > 9.2
+ # Returns true if pg > 9.1
def supports_extensions?
- postgresql_version >= 90200
+ postgresql_version >= 90100
end
# Range datatypes weren't introduced until PostgreSQL 9.2
@@ -646,9 +646,9 @@ module ActiveRecord
def extension_enabled?(name)
if supports_extensions?
- res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL)",
+ res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled",
'SCHEMA'
- res.column_types['exists'].type_cast res.rows.first.first
+ res.column_types['enabled'].type_cast res.rows.first.first
end
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 81cca37939..e1faadf1ab 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -50,7 +50,7 @@ module ActiveRecord
# ==== 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
+ # * +counters+ - A Hash containing the names of the fields
# to update as keys and the amount to update the field by as values.
#
# ==== Examples
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index cd31147414..7e38719811 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -69,10 +69,6 @@ module ActiveRecord
end
end
- # Raised when SQL statement is invalid and the application gets a blank result.
- class ThrowResult < ActiveRecordError
- end
-
# Defunct wrapper class kept for compatibility.
# +StatementInvalid+ wraps the original exception now.
class WrappedDatabaseException < StatementInvalid
@@ -159,6 +155,15 @@ module ActiveRecord
# Raised when unknown attributes are supplied via mass assignment.
class UnknownAttributeError < NoMethodError
+
+ attr_reader :record, :attribute
+
+ def initialize(record, attribute)
+ @record = record
+ @attribute = attribute.to_s
+ super("unknown attribute: #{attribute}")
+ end
+
end
# Raised when an error occurred while doing a mass assignment to an attribute through the
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index a3bc56d600..6a49936644 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -19,7 +19,7 @@ module ActiveRecord
# On the other hand, we want to monitor the performance of our real database
# queries, not the performance of the access to the query cache.
IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE)
- EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)/i
+ EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)\b/i
def ignore_payload?(payload)
payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 45dc26f0ed..70eda332b3 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -567,7 +567,7 @@ module ActiveRecord
# interpolate the fixture label
row.each do |key, value|
- row[key] = label if value == "$LABEL"
+ row[key] = label if "$LABEL" == value
end
# generate a primary key if necessary
@@ -841,8 +841,6 @@ module ActiveRecord
end
def setup_fixtures
- return if ActiveRecord::Base.configurations.blank?
-
if pre_loaded_fixtures && !use_transactional_fixtures
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
end
@@ -875,8 +873,6 @@ module ActiveRecord
end
def teardown_fixtures
- return if ActiveRecord::Base.configurations.blank?
-
# Rollback changes if a transaction is active.
if run_in_transaction?
@fixture_connections.each do |connection|
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 8df76c7f5f..e826762def 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -5,7 +5,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- # Determine whether to store the full constant name including namespace when using STI
+ # Determines whether to store the full constant name including namespace when using STI.
class_attribute :store_full_sti_class, instance_writer: false
self.store_full_sti_class = true
end
@@ -13,7 +13,7 @@ module ActiveRecord
module ClassMethods
# Determines if one of the attributes passed in is the inheritance column,
# and if the inheritance column is attr accessible, it initializes an
- # instance of the given subclass instead of the base class
+ # instance of the given subclass instead of the base class.
def new(*args, &block)
if abstract_class? || self == Base
raise NotImplementedError, "#{self} is an abstract class and can not be instantiated."
@@ -27,7 +27,8 @@ module ActiveRecord
super
end
- # True if this isn't a concrete subclass needing a STI type condition.
+ # Returns +true+ if this does not need STI type condition. Returns
+ # +false+ if STI type condition needs to be applied.
def descends_from_active_record?
if self == Base
false
@@ -116,9 +117,10 @@ module ActiveRecord
begin
constant = ActiveSupport::Dependencies.constantize(candidate)
return constant if candidate == constant.to_s
- rescue NameError => e
- # We don't want to swallow NoMethodError < NameError errors
- raise e unless e.instance_of?(NameError)
+ # We don't want to swallow NoMethodError < NameError errors
+ rescue NoMethodError
+ raise
+ rescue NameError
end
end
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index 8e4ddcac82..ddf2afca0c 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -64,7 +64,7 @@ module ActiveRecord
end
# Wraps the passed block in a transaction, locking the object
- # before yielding. You pass can the SQL locking clause
+ # before yielding. You can pass the SQL locking clause
# as argument (see <tt>lock!</tt>).
def with_lock(lock = true)
transaction do
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 6c020e1d57..e96c347f6f 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -357,11 +357,14 @@ module ActiveRecord
class CheckPending
def initialize(app)
@app = app
+ @last_check = 0
end
def call(env)
- ActiveRecord::Base.logger.silence do
+ mtime = ActiveRecord::Migrator.last_migration.mtime.to_i
+ if @last_check < mtime
ActiveRecord::Migration.check_pending!
+ @last_check = mtime
end
@app.call(env)
end
@@ -668,6 +671,7 @@ module ActiveRecord
copied
end
+ # Determines the version number of the next migration.
def next_migration_number(number)
if ActiveRecord::Base.timestamped_migrations
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
@@ -699,6 +703,10 @@ module ActiveRecord
File.basename(filename)
end
+ def mtime
+ File.mtime filename
+ end
+
delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration
private
@@ -714,6 +722,16 @@ module ActiveRecord
end
+ class NullMigration < MigrationProxy #:nodoc:
+ def initialize
+ super(nil, 0, nil, nil)
+ end
+
+ def mtime
+ 0
+ end
+ end
+
class Migrator#:nodoc:
class << self
attr_writer :migrations_paths
@@ -784,7 +802,11 @@ module ActiveRecord
end
def last_version
- migrations(migrations_paths).last.try(:version)||0
+ last_migration.version
+ end
+
+ def last_migration #:nodoc:
+ migrations(migrations_paths).last || NullMigration.new
end
def proper_table_name(name)
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 8bdaeef924..e53e8553ad 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -230,6 +230,10 @@ module ActiveRecord
# validates_presence_of :member
# end
#
+ # Note that if you do not specify the <tt>inverse_of</tt> option, then
+ # Active Record will try to automatically guess the inverse association
+ # based on heuristics.
+ #
# For one-to-one nested associations, if you build the new (in-memory)
# child object yourself before assignment, then this module will not
# overwrite it, e.g.:
@@ -302,14 +306,9 @@ module ActiveRecord
attr_names.each do |association_name|
if reflection = reflect_on_association(association_name)
- reflection.options[:autosave] = true
+ reflection.autosave = true
add_autosave_association_callbacks(reflection)
- # Clear cached values of any inverse associations found in the
- # reflection and prevent the reflection from finding inverses
- # automatically in the future.
- reflection.remove_automatic_inverse_of!
-
nested_attributes_options = self.nested_attributes_options.dup
nested_attributes_options[association_name.to_sym] = options
self.nested_attributes_options = nested_attributes_options
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 711fc8b883..d166f0dd66 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -39,7 +39,7 @@ module ActiveRecord
end
def to_sql
- @to_sql ||= ""
+ ""
end
def where_values_hash
@@ -55,7 +55,11 @@ module ActiveRecord
end
def calculate(_operation, _column_name, _options = {})
- nil
+ if _operation == :count
+ 0
+ else
+ nil
+ end
end
def exists?(_id = false)
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index a8905ed739..582006ea7d 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -333,10 +333,44 @@ module ActiveRecord
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.
+ # Reloads the record from the database.
+ #
+ # This method modifies the receiver in-place. Attributes are updated, and
+ # caches busted, in particular the associations cache.
+ #
+ # If the record no longer exists in the database <tt>ActiveRecord::RecordNotFound</tt>
+ # is raised. Otherwise, in addition to the in-place modification the method
+ # returns +self+ for convenience.
+ #
+ # The optional <tt>:lock</tt> flag option allows you to lock the reloaded record:
+ #
+ # reload(lock: true) # reload with pessimistic locking
+ #
+ # Reloading is commonly used in test suites to test something is actually
+ # written to the database, or when some action modifies the corresponding
+ # row in the database but not the object in memory:
+ #
+ # assert account.deposit!(25)
+ # assert_equal 25, account.credit # check it is updated in memory
+ # assert_equal 25, account.reload.credit # check it is also persisted
+ #
+ # Another commom use case is optimistic locking handling:
+ #
+ # def with_optimistic_retry
+ # begin
+ # yield
+ # rescue ActiveRecord::StaleObjectError
+ # begin
+ # # Reload lock_version in particular.
+ # reload
+ # rescue ActiveRecord::RecordNotFound
+ # # If the record is gone there is nothing to do.
+ # else
+ # retry
+ # end
+ # end
+ # end
+ #
def reload(options = nil)
clear_aggregation_cache
clear_association_cache
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index f78ccb01aa..3d85898c41 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -14,7 +14,7 @@ module ActiveRecord
# Executes a custom SQL query against your database and returns all the results. The results will
# be returned as an array with columns requested encapsulated as attributes of the model you call
# this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
- # a Product object with the attributes you specified in the SQL query.
+ # a +Product+ object with the attributes you specified in the SQL query.
#
# If you call a complicated SQL query which spans multiple tables the columns specified by the
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
@@ -29,9 +29,10 @@ module ActiveRecord
# Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
# # => [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
#
- # # You can use the same string replacement techniques as you can with ActiveRecord#find
+ # You can use the same string replacement techniques as you can with <tt>ActiveRecord::QueryMethods#where</tt>:
+ #
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
- # # => [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
+ # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
def find_by_sql(sql, binds = [])
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
column_types = {}
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index e36888d4a8..31a0ace864 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -44,7 +44,7 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a
ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures'
- if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
+ if defined?(APP_RAKEFILE) && engine = Rails::Engine.find(find_engine_path(APP_RAKEFILE))
if engine.paths['db/migrate'].existent
ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 434af3c5f8..0b74553bf8 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -324,7 +324,7 @@ db_namespace = namespace :db do
ActiveRecord::Schema.verbose = false
db_namespace["schema:load"].invoke
ensure
- ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Rails.env])
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env])
end
end
@@ -360,7 +360,7 @@ db_namespace = namespace :db do
end
# desc 'Check for pending migrations and load the test schema'
- task :prepare do
+ task :prepare => :load_config do
unless ActiveRecord::Base.configurations.blank?
db_namespace['test:load'].invoke
end
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index 8499bb16e7..b3ddfd63d4 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -20,11 +20,5 @@ module ActiveRecord
self._attr_readonly
end
end
-
- def _attr_readonly
- message = "Instance level _attr_readonly method is deprecated, please use class level method."
- ActiveSupport::Deprecation.warn message
- defined?(@_attr_readonly) ? @_attr_readonly : self.class._attr_readonly
- end
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0ba860a186..8a9488656b 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -5,10 +5,12 @@ module ActiveRecord
included do
class_attribute :reflections
+ class_attribute :aggregate_reflections
self.reflections = {}
+ self.aggregate_reflections = {}
end
- # Reflection enables to interrogate Active Record classes and objects
+ # \Reflection enables to interrogate Active Record classes and objects
# about their associations and aggregations. This information can,
# for example, be used in a form builder that takes an Active Record object
# and creates input fields for all of the attributes depending on their type
@@ -21,18 +23,24 @@ module ActiveRecord
case macro
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
klass = options[:through] ? ThroughReflection : AssociationReflection
- reflection = klass.new(macro, name, scope, options, active_record)
when :composed_of
- reflection = AggregateReflection.new(macro, name, scope, options, active_record)
+ klass = AggregateReflection
+ end
+
+ reflection = klass.new(macro, name, scope, options, active_record)
+
+ if klass == AggregateReflection
+ self.aggregate_reflections = self.aggregate_reflections.merge(name => reflection)
+ else
+ self.reflections = self.reflections.merge(name => reflection)
end
- self.reflections = self.reflections.merge(name => reflection)
reflection
end
# Returns an array of AggregateReflection objects for all the aggregations in the class.
def reflect_on_all_aggregations
- reflections.values.grep(AggregateReflection)
+ aggregate_reflections.values
end
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
@@ -40,8 +48,7 @@ module ActiveRecord
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
#
def reflect_on_aggregation(aggregation)
- reflection = reflections[aggregation]
- reflection if reflection.is_a?(AggregateReflection)
+ aggregate_reflections[aggregation]
end
# Returns an array of AssociationReflection objects for all the
@@ -55,7 +62,7 @@ module ActiveRecord
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
#
def reflect_on_all_associations(macro = nil)
- association_reflections = reflections.values.grep(AssociationReflection)
+ association_reflections = reflections.values
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
end
@@ -65,8 +72,7 @@ module ActiveRecord
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
#
def reflect_on_association(association)
- reflection = reflections[association]
- reflection if reflection.is_a?(AssociationReflection)
+ reflections[association]
end
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
@@ -100,7 +106,7 @@ module ActiveRecord
# Returns the hash of options used for the macro.
#
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
- # <tt>has_many :clients</tt> returns +{}+
+ # <tt>has_many :clients</tt> returns <tt>{}</tt>
attr_reader :options
attr_reader :active_record
@@ -117,6 +123,11 @@ module ActiveRecord
name.to_s.pluralize : name.to_s
end
+ def autosave=(autosave)
+ @automatic_inverse_of = false
+ @options[:autosave] = autosave
+ end
+
# Returns the class for the macro.
#
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
@@ -178,10 +189,14 @@ module ActiveRecord
@klass ||= active_record.send(:compute_type, class_name)
end
+ attr_reader :type, :foreign_type
+
def initialize(*args)
super
@collection = [:has_many, :has_and_belongs_to_many].include?(macro)
@automatic_inverse_of = nil
+ @type = options[:as] && "#{options[:as]}_type"
+ @foreign_type = options[:foreign_type] || "#{name}_type"
end
# Returns a new, unsaved instance of the associated class. +attributes+ will
@@ -191,11 +206,11 @@ module ActiveRecord
end
def table_name
- @table_name ||= klass.table_name
+ klass.table_name
end
def quoted_table_name
- @quoted_table_name ||= klass.quoted_table_name
+ klass.quoted_table_name
end
def join_table
@@ -206,16 +221,8 @@ module ActiveRecord
@foreign_key ||= options[:foreign_key] || derive_foreign_key
end
- def foreign_type
- @foreign_type ||= options[:foreign_type] || "#{name}_type"
- end
-
- def type
- @type ||= options[:as] && "#{options[:as]}_type"
- end
-
def primary_key_column
- @primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
+ klass.columns_hash[klass.primary_key]
end
def association_foreign_key
@@ -239,14 +246,6 @@ module ActiveRecord
end
end
- def columns(tbl_name)
- @columns ||= klass.connection.columns(tbl_name)
- end
-
- def reset_column_information
- @columns = nil
- end
-
def check_validity!
check_validity_of_inverse!
@@ -290,30 +289,13 @@ module ActiveRecord
alias :source_macro :macro
def has_inverse?
- @options[:inverse_of] || find_inverse_of_automatically
+ inverse_name
end
def inverse_of
- @inverse_of ||= if options[:inverse_of]
- klass.reflect_on_association(options[:inverse_of])
- else
- find_inverse_of_automatically
- end
- end
+ return unless inverse_name
- # Clears the cached value of +@inverse_of+ on this object. This will
- # not remove the :inverse_of option however, so future calls on the
- # +inverse_of+ will have to recompute the inverse.
- def clear_inverse_of_cache!
- @inverse_of = nil
- end
-
- # Removes the cached inverse association that was found automatically
- # and prevents this object from finding the inverse association
- # automatically in the future.
- def remove_automatic_inverse_of!
- @automatic_inverse_of = nil
- options[:automatic_inverse_of] = false
+ @inverse_of ||= klass.reflect_on_association inverse_name
end
def polymorphic_inverse_of(associated_class)
@@ -388,26 +370,21 @@ module ActiveRecord
INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
private
- # Attempts to find the inverse association automatically.
- # If it cannot find a suitable inverse association, it returns
+ # Attempts to find the inverse association name automatically.
+ # If it cannot find a suitable inverse association name, it returns
# nil.
- def find_inverse_of_automatically
- if @automatic_inverse_of == false
- nil
- elsif @automatic_inverse_of.nil?
- set_automatic_inverse_of
- else
- klass.reflect_on_association(@automatic_inverse_of)
+ def inverse_name
+ options.fetch(:inverse_of) do
+ if @automatic_inverse_of == false
+ nil
+ else
+ @automatic_inverse_of ||= automatic_inverse_of
+ end
end
end
- # Sets the +@automatic_inverse_of+ instance variable, and returns
- # either nil or the inverse association that it finds.
- #
- # This method caches the inverse association that is found so that
- # future calls to +find_inverse_of_automatically+ have much less
- # overhead.
- def set_automatic_inverse_of
+ # returns either nil or the inverse association name that it finds.
+ def automatic_inverse_of
if can_find_inverse_of_automatically?(self)
inverse_name = active_record.name.downcase.to_sym
@@ -420,16 +397,11 @@ module ActiveRecord
end
if valid_inverse_reflection?(reflection)
- @automatic_inverse_of = inverse_name
- reflection
- else
- @automatic_inverse_of = false
- nil
+ return inverse_name
end
- else
- @automatic_inverse_of = false
- nil
end
+
+ false
end
# Checks if the inverse reflection that is returned from the
@@ -441,22 +413,23 @@ module ActiveRecord
# from calling +klass+, +reflection+ will already be set to false.
def valid_inverse_reflection?(reflection)
reflection &&
- klass.name == reflection.active_record.try(:name) &&
+ klass.name == reflection.active_record.name &&
klass.primary_key == reflection.active_record_primary_key &&
can_find_inverse_of_automatically?(reflection)
end
# Checks to see if the reflection doesn't have any options that prevent
# us from being able to guess the inverse automatically. First, the
- # +automatic_inverse_of+ option cannot be set to false. Second, we must
- # have :has_many, :has_one, :belongs_to associations. Third, we must
- # not have options such as :class_name or :polymorphic which prevent us
- # from correctly guessing the inverse association.
+ # <tt>inverse_of</tt> option cannot be set to false. Second, we must
+ # have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
+ # Third, we must not have options such as <tt>:polymorphic</tt> or
+ # <tt>:foreign_key</tt> which prevent us from correctly guessing the
+ # inverse association.
#
# Anything with a scope can additionally ruin our attempt at finding an
# inverse, so we exclude reflections with scopes.
def can_find_inverse_of_automatically?(reflection)
- reflection.options[:automatic_inverse_of] != false &&
+ reflection.options[:inverse_of] != false &&
VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
!INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
!reflection.scope
@@ -493,7 +466,12 @@ module ActiveRecord
delegate :foreign_key, :foreign_type, :association_foreign_key,
:active_record_primary_key, :type, :to => :source_reflection
- # Gets the source of the through reflection. It checks both a singularized
+ def initialize(macro, name, scope, options, active_record)
+ super
+ @source_reflection_name = options[:source]
+ end
+
+ # Returns the source of the through reflection. It checks both a singularized
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
#
# class Post < ActiveRecord::Base
@@ -507,12 +485,11 @@ module ActiveRecord
# end
#
# tags_reflection = Post.reflect_on_association(:tags)
- #
- # taggings_reflection = tags_reflection.source_reflection
+ # tags_reflection.source_reflection
# # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
#
def source_reflection
- @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
+ through_reflection.klass.reflect_on_association(source_reflection_name)
end
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
@@ -524,10 +501,11 @@ module ActiveRecord
# end
#
# tags_reflection = Post.reflect_on_association(:tags)
- # taggings_reflection = tags_reflection.through_reflection
+ # tags_reflection.through_reflection
+ # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings">
#
def through_reflection
- @through_reflection ||= active_record.reflect_on_association(options[:through])
+ active_record.reflect_on_association(options[:through])
end
# Returns an array of reflections which are involved in this association. Each item in the
@@ -629,7 +607,32 @@ module ActiveRecord
# # => [:tag, :tags]
#
def source_reflection_names
- @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
+ (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq
+ end
+
+ def source_reflection_name # :nodoc:
+ return @source_reflection_name if @source_reflection_name
+
+ names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
+ names = names.find_all { |n|
+ through_reflection.klass.reflect_on_association(n)
+ }
+
+ if names.length > 1
+ example_options = options.dup
+ example_options[:source] = source_reflection_names.first
+ ActiveSupport::Deprecation.warn <<-eowarn
+Ambiguous source reflection for through association. Please specify a :source
+directive on your declaration like:
+
+ class #{active_record.name} < ActiveRecord::Base
+ #{macro} :#{name}, #{example_options}
+ end
+
+ eowarn
+ end
+
+ @source_reflection_name = names.first
end
def source_options
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 913f6f88f2..d37471e9ad 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -17,7 +17,7 @@ module ActiveRecord
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
attr_reader :table, :klass, :loaded
- attr_accessor :default_scoped, :proxy_association
+ attr_accessor :default_scoped
alias :model :klass
alias :loaded? :loaded
alias :default_scoped? :default_scoped
@@ -26,7 +26,6 @@ module ActiveRecord
@klass = klass
@table = table
@values = values
- @implicit_readonly = nil
@loaded = false
@default_scoped = false
end
@@ -76,10 +75,10 @@ module ActiveRecord
def update_record(values, id, id_was) # :nodoc:
substitutes, binds = substitute_values values
um = @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes)
-
+
@klass.connection.update(
- um,
- 'SQL',
+ um,
+ 'SQL',
binds)
end
@@ -94,7 +93,7 @@ module ActiveRecord
end
[substitutes, binds]
- end
+ end
# Initializes new record from relation while maintaining the current
# scope.
@@ -155,34 +154,58 @@ module ActiveRecord
first || new(attributes, &block)
end
- # Finds the first record with the given attributes, or creates a record with the attributes
- # if one is not found.
+ # Finds the first record with the given attributes, or creates a record
+ # with the attributes if one is not found:
#
- # ==== Examples
- # # Find the first user named Penélope or create a new one.
+ # # Find the first user named "Penélope" or create a new one.
# User.find_or_create_by(first_name: 'Penélope')
- # # => <User id: 1, first_name: 'Penélope', last_name: nil>
+ # # => #<User id: 1, first_name: "Penélope", last_name: nil>
#
- # # Find the first user named Penélope or create a new one.
+ # # Find the first user named "Penélope" or create a new one.
# # We already have one so the existing record will be returned.
# User.find_or_create_by(first_name: 'Penélope')
- # # => <User id: 1, first_name: 'Penélope', last_name: nil>
+ # # => #<User id: 1, first_name: "Penélope", last_name: nil>
#
- # # Find the first user named Scarlett or create a new one with a particular last name.
+ # # Find the first user named "Scarlett" or create a new one with
+ # # a particular last name.
# User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett')
- # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
+ # # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
#
- # # Find the first user named Scarlett or create a new one with a different last name.
- # # We already have one so the existing record will be returned.
+ # This method accepts a block, which is passed down to +create+. The last example
+ # above can be alternatively written this way:
+ #
+ # # Find the first user named "Scarlett" or create a new one with a
+ # # different last name.
# User.find_or_create_by(first_name: 'Scarlett') do |user|
- # user.last_name = "O'Hara"
+ # user.last_name = 'Johansson'
# end
- # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
+ # # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
+ #
+ # This method always returns a record, but if creation was attempted and
+ # failed due to validation errors it won't be persisted, you get what
+ # +create+ returns in such situation.
+ #
+ # Please note *this method is not atomic*, it runs first a SELECT, and if
+ # there are no results an INSERT is attempted. If there are other threads
+ # or processes there is a race condition between both calls and it could
+ # be the case that you end up with two similar records.
+ #
+ # Whether that is a problem or not depends on the logic of the
+ # application, but in the particular case in which rows have a UNIQUE
+ # constraint an exception may be raised, just retry:
+ #
+ # begin
+ # CreditAccount.find_or_create_by(user_id: user.id)
+ # rescue ActiveRecord::RecordNotUnique
+ # retry
+ # end
+ #
def find_or_create_by(attributes, &block)
find_by(attributes) || create(attributes, &block)
end
- # Like <tt>find_or_create_by</tt>, but calls <tt>create!</tt> so an exception is raised if the created record is invalid.
+ # Like <tt>find_or_create_by</tt>, but calls <tt>create!</tt> so an exception
+ # is raised if the created record is invalid.
def find_or_create_by!(attributes, &block)
find_by(attributes) || create!(attributes, &block)
end
@@ -224,7 +247,7 @@ module ActiveRecord
def empty?
return @records.empty? if loaded?
- c = count
+ c = count(:all)
c.respond_to?(:zero?) ? c.zero? : c.empty?
end
@@ -489,11 +512,13 @@ module ActiveRecord
# User.where(name: 'Oscar').where_values_hash
# # => {name: "Oscar"}
def where_values_hash
- equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
+ scope = with_default_scope
+ equalities = scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
node.left.relation.name == table_name
}
- binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
+ binds = Hash[scope.bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
+ binds.merge!(Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }])
Hash[equalities.map { |where|
name = where.left.name
@@ -580,10 +605,7 @@ module ActiveRecord
ActiveRecord::Associations::Preloader.new(@records, associations).run
end
- # @readonly_value is true only if set explicitly. @implicit_readonly is true if there
- # are JOINS and no explicit SELECT.
- readonly = readonly_value.nil? ? @implicit_readonly : readonly_value
- @records.each { |record| record.readonly! } if readonly
+ @records.each { |record| record.readonly! } if readonly_value
else
@records = default_scoped.to_a
end
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index b921f2eddb..41291844fc 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -11,7 +11,7 @@ module ActiveRecord
# The #find_each method uses #find_in_batches with a batch size of 1000 (or as
# specified by the +:batch_size+ option).
#
- # Person.all.find_each do |person|
+ # Person.find_each do |person|
# person.do_awesome_stuff
# end
#
@@ -19,8 +19,26 @@ module ActiveRecord
# person.party_all_night!
# end
#
- # You can also pass the +:start+ option to specify
- # an offset to control the starting point.
+ # ==== Options
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
+ # * <tt>:start</tt> - Specifies the starting point for the batch processing.
+ # This is especially useful if you want multiple workers dealing with
+ # the same processing queue. You can make worker 1 handle all the records
+ # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
+ # (by setting the +:start+ option on that worker).
+ #
+ # # Let's process for a batch of 2000 records, skiping the first 2000 rows
+ # Person.find_each(start: 2000, batch_size: 2000) do |person|
+ # person.party_all_night!
+ # end
+ #
+ # NOTE: It's not possible to set the order. That is automatically set to
+ # ascending on the primary key ("id ASC") to make the batch ordering
+ # work. This also means that this method only works with integer-based
+ # primary keys.
+ #
+ # NOTE: You can't set the limit either, that's used to control
+ # the batch sizes.
def find_each(options = {})
find_in_batches(options) do |records|
records.each { |record| yield record }
@@ -28,31 +46,33 @@ module ActiveRecord
end
# Yields each batch of records that was found by the find +options+ as
- # an array. The size of each batch is set by the +:batch_size+
- # option; the default is 1000.
- #
- # You can control the starting point for the batch processing by
- # supplying the +:start+ option. This is especially useful if you
- # want multiple workers dealing with the same processing queue. You can
- # make worker 1 handle all the records between id 0 and 10,000 and
- # worker 2 handle from 10,000 and beyond (by setting the +:start+
- # option on that worker).
- #
- # It's not possible to set the order. That is automatically set to
- # ascending on the primary key ("id ASC") to make the batch ordering
- # work. This also means that this method only works with integer-based
- # primary keys. You can't set the limit either, that's used to control
- # the batch sizes.
+ # an array.
#
# Person.where("age > 21").find_in_batches do |group|
# sleep(50) # Make sure it doesn't get too crowded in there!
# group.each { |person| person.party_all_night! }
# end
#
+ # ==== Options
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
+ # * <tt>:start</tt> - Specifies the starting point for the batch processing.
+ # This is especially useful if you want multiple workers dealing with
+ # the same processing queue. You can make worker 1 handle all the records
+ # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
+ # (by setting the +:start+ option on that worker).
+ #
# # Let's process the next 2000 records
- # Person.all.find_in_batches(start: 2000, batch_size: 2000) do |group|
+ # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
# group.each { |person| person.party_all_night! }
# end
+ #
+ # NOTE: It's not possible to set the order. That is automatically set to
+ # ascending on the primary key ("id ASC") to make the batch ordering
+ # work. This also means that this method only works with integer-based
+ # primary keys.
+ #
+ # NOTE: You can't set the limit either, that's used to control
+ # the batch sizes.
def find_in_batches(options = {})
options.assert_valid_keys(:start, :batch_size)
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 7239270c4d..4becf3980d 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -56,15 +56,7 @@ module ActiveRecord
#
# Person.sum(:age) # => 4562
def sum(*args)
- if block_given?
- ActiveSupport::Deprecation.warn(
- "Calling #sum with a block is deprecated and will be removed in Rails 4.1. " \
- "If you want to perform sum calculation over the array of elements, use `to_a.sum(&block)`."
- )
- self.to_a.sum(*args) {|*block_args| yield(*block_args)}
- else
- calculate(:sum, *args)
- end
+ calculate(:sum, *args)
end
# This calculates aggregate values in the given column. Methods for count, sum, average,
@@ -114,8 +106,6 @@ module ActiveRecord
else
relation.calculate(operation, column_name, options)
end
- rescue ThrowResult
- 0
end
# Use <tt>pluck</tt> as a shortcut to select one or more attributes without
@@ -217,15 +207,18 @@ module ActiveRecord
end
if operation == "count"
- column_name ||= (select_for_count || :all)
+ if select_values.present?
+ column_name ||= select_values.join(", ")
+ else
+ column_name ||= :all
+ end
unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
distinct = true
end
column_name = primary_key if column_name == :all && distinct
-
- distinct = nil if column_name =~ /\s*DISTINCT\s+/i
+ distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
end
if group_values.any?
@@ -386,13 +379,6 @@ module ActiveRecord
column ? column.type_cast(value) : value
end
- def select_for_count
- if select_values.present?
- select = select_values.join(", ")
- select if select !~ /[,*]/
- end
- end
-
def build_count_subquery(relation, column_name, distinct)
column_alias = Arel.sql('count_column')
subquery_alias = Arel.sql('subquery_for_count')
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 72e9272cd7..3ea3c33fcc 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -11,9 +11,11 @@ module ActiveRecord
# Person.find([1]) # returns an array for the object with ID = 1
# Person.where("administrator = 1").order("created_on DESC").find(1)
#
- # Note that returned records may not be in the same order as the ids you
- # provide since database rows are unordered. Give an explicit <tt>order</tt>
- # to ensure the results are sorted.
+ # <tt>ActiveRecord::RecordNotFound</tt> will be raised if one or more ids are not found.
+ #
+ # NOTE: The returned records may not be in the same order as the ids you
+ # provide since database rows are unordered. You'd need to provide an explicit <tt>order</tt>
+ # option if you want the results are sorted.
#
# ==== Find with lock
#
@@ -28,6 +30,34 @@ module ActiveRecord
# person.visits += 1
# person.save!
# end
+ #
+ # ==== Variations of +find+
+ #
+ # Person.where(name: 'Spartacus', rating: 4)
+ # # returns a chainable list (which can be empty).
+ #
+ # Person.find_by(name: 'Spartacus', rating: 4)
+ # # returns the first item or nil.
+ #
+ # Person.where(name: 'Spartacus', rating: 4).first_or_initialize
+ # # returns the first item or returns a new instance (requires you call .save to persist against the database).
+ #
+ # Person.where(name: 'Spartacus', rating: 4).first_or_create
+ # # returns the first item or creates it and returns it, available since Rails 3.2.1.
+ #
+ # ==== Alternatives for +find+
+ #
+ # Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none)
+ # # returns a boolean indicating if any record with the given conditions exist.
+ #
+ # Person.where(name: 'Spartacus', rating: 4).select("field1, field2, field3")
+ # # returns a chainable list of instances with only the mentioned fields.
+ #
+ # Person.where(name: 'Spartacus', rating: 4).ids
+ # # returns an Array of ids, available since Rails 3.2.1.
+ #
+ # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
+ # # returns an Array of the required fields, available since Rails 3.1.
def find(*args)
if block_given?
to_a.find { |*block_args| yield(*block_args) }
@@ -79,13 +109,22 @@ module ActiveRecord
# Person.where(["user_name = :u", { u: user_name }]).first
# Person.order("created_on DESC").offset(5).first
# Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
+ #
+ # ==== Rails 3
+ #
+ # Person.first # SELECT "people".* FROM "people" LIMIT 1
+ #
+ # NOTE: Rails 3 may not order this query by the primary key and the order
+ # will depend on the database implementation. In order to ensure that behavior,
+ # use <tt>User.order(:id).first</tt> instead.
+ #
+ # ==== Rails 4
+ #
+ # Person.first # SELECT "people".* FROM "people" ORDER BY "people"."id" ASC LIMIT 1
+ #
def first(limit = nil)
if limit
- if order_values.empty? && primary_key
- order(arel_table[primary_key].asc).limit(limit).to_a
- else
- limit(limit).to_a
- end
+ find_first_with_limit(order_values, limit)
else
find_first
end
@@ -160,8 +199,9 @@ module ActiveRecord
conditions = conditions.id if Base === conditions
return false if !conditions
- join_dependency = construct_join_dependency_for_association_find
- relation = construct_relation_for_association_find(join_dependency)
+ relation = construct_relation_for_association_find(construct_join_dependency)
+ return false if ActiveRecord::NullRelation === relation
+
relation = relation.except(:select, :order).select("1 AS one").limit(1)
case conditions
@@ -171,9 +211,8 @@ module ActiveRecord
relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
end
- connection.select_value(relation, "#{name} Exists", relation.bind_values)
- rescue ThrowResult
- false
+ relation = relation.with_default_scope
+ connection.select_value(relation.arel, "#{name} Exists", relation.bind_values)
end
# This method is called whenever no records are found with either a single
@@ -201,58 +240,53 @@ module ActiveRecord
protected
def find_with_associations
- join_dependency = construct_join_dependency_for_association_find
+ join_dependency = construct_join_dependency
relation = construct_relation_for_association_find(join_dependency)
- rows = connection.select_all(relation, 'SQL', relation.bind_values.dup)
- join_dependency.instantiate(rows)
- rescue ThrowResult
- []
+ if ActiveRecord::NullRelation === relation
+ []
+ else
+ rows = connection.select_all(relation, 'SQL', relation.bind_values.dup)
+ join_dependency.instantiate(rows)
+ end
end
- def construct_join_dependency_for_association_find
+ def construct_join_dependency(joins = [])
including = (eager_load_values + includes_values).uniq
- ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
+ ActiveRecord::Associations::JoinDependency.new(@klass, including, joins)
end
def construct_relation_for_association_calculations
- including = (eager_load_values + includes_values).uniq
- join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
- relation = except(:includes, :eager_load, :preload)
- apply_join_dependency(relation, join_dependency)
+ apply_join_dependency(self, construct_join_dependency(arel.froms.first))
end
def construct_relation_for_association_find(join_dependency)
- relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns)
+ relation = except(:select).select(join_dependency.columns)
apply_join_dependency(relation, join_dependency)
end
def apply_join_dependency(relation, join_dependency)
- join_dependency.join_associations.each do |association|
- relation = association.join_relation(relation)
- end
-
- limitable_reflections = using_limitable_reflections?(join_dependency.reflections)
+ relation = relation.except(:includes, :eager_load, :preload)
+ relation = join_dependency.join_relation(relation)
- if !limitable_reflections && relation.limit_value
- limited_id_condition = construct_limited_ids_condition(relation.except(:select))
- relation = relation.where(limited_id_condition)
+ if using_limitable_reflections?(join_dependency.reflections)
+ relation
+ else
+ if relation.limit_value
+ limited_ids = limited_ids_for(relation)
+ limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
+ end
+ relation.except(:limit, :offset)
end
-
- relation = relation.except(:limit, :offset) unless limitable_reflections
-
- relation
end
- def construct_limited_ids_condition(relation)
- orders = relation.order_values.map { |val| val.presence }.compact
- values = @klass.connection.distinct("#{quoted_table_name}.#{primary_key}", orders)
+ def limited_ids_for(relation)
+ values = @klass.connection.columns_for_distinct(
+ "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
- relation = relation.dup.select(values)
+ relation = relation.except(:select).select(values).distinct!
id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
- ids_array = id_rows.map {|row| row[primary_key]}
-
- ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
+ id_rows.map {|row| row[primary_key]}
end
def find_with_ids(*ids)
@@ -320,12 +354,15 @@ module ActiveRecord
if loaded?
@records.first
else
- @first ||=
- if with_default_scope.order_values.empty? && primary_key
- order(arel_table[primary_key].asc).limit(1).to_a.first
- else
- limit(1).to_a.first
- end
+ @first ||= find_first_with_limit(with_default_scope.order_values, 1).first
+ end
+ end
+
+ def find_first_with_limit(order_values, limit)
+ if order_values.empty? && primary_key
+ order(arel_table[primary_key].asc).limit(limit).to_a
+ else
+ limit(limit).to_a
end
end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index bda7a76330..c114ea0c0d 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -94,15 +94,20 @@ module ActiveRecord
[])
relation.joins! rest
- join_dependency.join_associations.each do |association|
- @relation = association.join_relation(relation)
- end
+ @relation = join_dependency.join_relation(relation)
end
end
def merge_multi_values
- relation.where_values = merged_wheres
- relation.bind_values = merged_binds
+ lhs_wheres = relation.where_values
+ rhs_wheres = values[:where] || []
+ lhs_binds = relation.bind_values
+ rhs_binds = values[:bind] || []
+
+ removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
+
+ relation.where_values = kept + rhs_wheres
+ relation.bind_values = filter_binds(lhs_binds, removed) + rhs_binds
if values[:reordering]
# override any order specified in the original relation
@@ -125,33 +130,27 @@ module ActiveRecord
end
end
- def merged_binds
- if values[:bind]
- (relation.bind_values + values[:bind]).uniq(&:first)
- else
- relation.bind_values
- end
- end
+ def filter_binds(lhs_binds, removed_wheres)
+ return lhs_binds if removed_wheres.empty?
- def merged_wheres
- values[:where] ||= []
-
- if values[:where].empty? || relation.where_values.empty?
- relation.where_values + values[:where]
- else
- sanitized_wheres + values[:where]
- end
+ set = Set.new removed_wheres.map { |x| x.left.name }
+ lhs_binds.dup.delete_if { |col,_| set.include? col.name }
end
# Remove equalities from the existing relation with a LHS which is
# present in the relation being merged in.
- def sanitized_wheres
- seen = Set.new
- values[:where].each do |w|
- seen << w.left if w.respond_to?(:operator) && w.operator == :==
+ # returns [things_to_remove, things_to_keep]
+ def partition_overwrites(lhs_wheres, rhs_wheres)
+ if lhs_wheres.empty? || rhs_wheres.empty?
+ return [[], lhs_wheres]
+ end
+
+ nodes = rhs_wheres.find_all do |w|
+ w.respond_to?(:operator) && w.operator == :==
end
+ seen = Set.new(nodes) { |node| node.left }
- relation.where_values.reject do |w|
+ lhs_wheres.partition do |w|
w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 9fcd2d06c5..ca1de2d4dc 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -340,6 +340,9 @@ module ActiveRecord
# User.where(name: "John", active: true).unscope(where: :name)
# == User.where(active: true)
#
+ # This method is applied before the default_scope is applied. So the conditions
+ # specified in default_scope will not be removed.
+ #
# Note that this method is more generalized than ActiveRecord::SpawnMethods#except
# because #except will only affect a particular relation's values. It won't wipe
# the order, grouping, etc. when that relation is merged. For example:
@@ -861,8 +864,6 @@ module ActiveRecord
return [] if joins.empty?
- @implicit_readonly = true
-
joins.map do |join|
case join
when Array
@@ -944,8 +945,6 @@ module ActiveRecord
join_dependency.graft(*stashed_association_joins)
- @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
-
# FIXME: refactor this to build an AST
join_dependency.join_associations.each do |association|
association.join_to(manager)
@@ -958,7 +957,6 @@ module ActiveRecord
def build_select(arel, selects)
unless selects.empty?
- @implicit_readonly = false
arel.project(*selects)
else
arel.project(@klass.arel_table[Arel.star])
@@ -1012,7 +1010,7 @@ module ActiveRecord
end
def validate_order_args(args)
- args.select { |a| Hash === a }.each do |h|
+ args.grep(Hash) do |h|
unless (h.values - [:asc, :desc]).empty?
raise ArgumentError, 'Direction should be :asc or :desc'
end
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 3259dbbd80..4bfd0167a4 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -43,7 +43,7 @@ module ActiveRecord
unless info[:version].blank?
initialize_schema_migrations_table
- assume_migrated_upto_version(info[:version], migrations_paths)
+ connection.assume_migrated_upto_version(info[:version], migrations_paths)
end
end
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index 6077144265..fee19b1096 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -4,31 +4,37 @@ require 'active_record/base'
module ActiveRecord
class SchemaMigration < ActiveRecord::Base
+ class << self
- def self.table_name
- "#{Base.table_name_prefix}schema_migrations#{Base.table_name_suffix}"
- end
+ def table_name
+ "#{table_name_prefix}schema_migrations#{table_name_suffix}"
+ end
- def self.index_name
- "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
- end
+ def index_name
+ "#{table_name_prefix}unique_schema_migrations#{table_name_suffix}"
+ end
- def self.create_table(limit=nil)
- unless connection.table_exists?(table_name)
- version_options = {null: false}
- version_options[:limit] = limit if limit
+ def table_exists?
+ connection.table_exists?(table_name)
+ end
+
+ def create_table(limit=nil)
+ unless table_exists?
+ version_options = {null: false}
+ version_options[:limit] = limit if limit
- connection.create_table(table_name, id: false) do |t|
- t.column :version, :string, version_options
+ connection.create_table(table_name, id: false) do |t|
+ t.column :version, :string, version_options
+ end
+ connection.add_index table_name, :version, unique: true, name: index_name
end
- connection.add_index table_name, :version, unique: true, name: index_name
end
- end
- def self.drop_table
- if connection.table_exists?(table_name)
- connection.remove_index table_name, name: index_name
- connection.drop_table(table_name)
+ def drop_table
+ if table_exists?
+ connection.remove_index table_name, name: index_name
+ connection.drop_table(table_name)
+ end
end
end
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index e9142481a3..1b4c473bfc 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -35,8 +35,7 @@ module ActiveRecord
def assert_queries(num = 1, options = {})
ignore_none = options.fetch(:ignore_none) { num == :any }
SQLCounter.clear_log
- yield
- ensure
+ x = yield
the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log
if num == :any
assert_operator the_log.size, :>=, 1, "1 or more queries expected, but none were executed."
@@ -44,6 +43,7 @@ module ActiveRecord
mesg = "#{the_log.size} instead of #{num} queries were executed.#{the_log.size == 0 ? '' : "\nQueries:\n#{the_log.join("\n")}"}"
assert_equal num, the_log.size, mesg
end
+ x
end
def assert_no_queries(&block)
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index a5955ccba4..77634b40bb 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -10,7 +10,9 @@ module ActiveRecord
end
included do
- define_callbacks :commit, :rollback, :terminator => "result == false", :scope => [:kind, :name]
+ define_callbacks :commit, :rollback,
+ terminator: ->(_, result) { result == false },
+ scope: [:kind, :name]
end
# = Active Record Transactions
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index 744780d069..b4785d3ba4 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -2,7 +2,7 @@ module ActiveRecord
module Validations
class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
def validate_each(record, attribute, value)
- if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?(record.validation_context) }.any?
+ if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?}.any?
record.errors.add(attribute, :invalid, options.merge(:value => value))
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index a705d8c2c4..52e46e1ffe 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -7,11 +7,7 @@ module ActiveRecord
"Pass a callable instead: `conditions: -> { where(approved: true) }`"
end
super({ case_sensitive: true }.merge!(options))
- end
-
- # Unfortunately, we have to tie Uniqueness validators to a class.
- def setup(klass)
- @klass = klass
+ @klass = options[:class]
end
def validate_each(record, attribute, value)
@@ -34,7 +30,6 @@ module ActiveRecord
end
protected
-
# The check for an existing value should be run from a class that
# isn't abstract. This means working down from the current class
# (self), to the first non-abstract class. Since classes don't know
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index b967bb6e0f..3968acba64 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -14,6 +14,10 @@ module ActiveRecord
protected
attr_reader :migration_action, :join_tables
+ # sets the default migration template that is being used for the generation of the migration
+ # depending on the arguments which would be sent out in the command line, the migration template
+ # and the table name instance variables are setup.
+
def set_local_assigns!
@migration_template = "migration.rb"
case file_name
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index 40e134e626..7e8d68ce69 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -12,6 +12,9 @@ module ActiveRecord
class_option :parent, :type => :string, :desc => "The parent class for the generated model"
class_option :indexes, :type => :boolean, :default => true, :desc => "Add indexes for references and belongs_to columns"
+
+ # creates the migration file for the model.
+
def create_migration_file
return unless options[:migration] && options[:parent].nil?
attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false
@@ -39,6 +42,7 @@ module ActiveRecord
protected
+ # Used by the migration template to determine the parent name of the model
def parent_class_name
options[:parent] || "ActiveRecord::Base"
end
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index a75883cd3a..4a23287448 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -95,14 +95,27 @@ module ActiveRecord
assert_equal @conn.default_sequence_name('ex_with_custom_index_type_pk', 'id'), seq
end
+ def test_tinyint_integer_typecasting
+ @conn.exec_query('drop table if exists ex_with_non_boolean_tinyint_column')
+ @conn.exec_query(<<-eosql)
+ CREATE TABLE `ex_with_non_boolean_tinyint_column` (
+ `status` TINYINT(4))
+ eosql
+ insert(@conn, { 'status' => 2 }, 'ex_with_non_boolean_tinyint_column')
+
+ result = @conn.exec_query('SELECT status FROM ex_with_non_boolean_tinyint_column')
+
+ assert_equal 2, result.column_types['status'].type_cast(result.last['status'])
+ end
+
private
- def insert(ctx, data)
+ def insert(ctx, data, table='ex')
binds = data.map { |name, value|
- [ctx.columns('ex').find { |x| x.name == name }, value]
+ [ctx.columns(table).find { |x| x.name == name }, value]
}
columns = binds.map(&:first).map(&:name)
- sql = "INSERT INTO ex (#{columns.join(", ")})
+ sql = "INSERT INTO #{table} (#{columns.join(", ")})
VALUES (#{(['?'] * columns.length).join(', ')})"
ctx.exec_insert(sql, 'SQL', binds)
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 16329689c0..22dd48e113 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -25,9 +25,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
def test_add_index
# add_index calls index_name_exists? which can't work since execute is stubbed
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) do |*|
- false
- end
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.stubs(:index_name_exists?).returns(false)
expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'")
@@ -51,8 +49,6 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name") WHERE state = 'active')
assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'", :using => :gist)
-
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:remove_method, :index_name_exists?)
end
private
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index d7d77f96e2..489efac932 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -15,6 +15,7 @@ class PostgresqlByteaTest < ActiveRecord::TestCase
@connection.transaction do
@connection.create_table('bytea_data_type') do |t|
t.binary 'payload'
+ t.binary 'serialized'
end
end
end
@@ -84,4 +85,20 @@ class PostgresqlByteaTest < ActiveRecord::TestCase
assert_equal(nil, record.payload)
assert_equal(nil, ByteaDataType.where(id: record.id).first.payload)
end
+
+ class Serializer
+ def load(str); str; end
+ def dump(str); str; end
+ end
+
+ def test_serialize
+ klass = Class.new(ByteaDataType) {
+ serialize :serialized, Serializer.new
+ }
+ obj = klass.new
+ obj.serialized = "hello world"
+ obj.save!
+ obj.reload
+ assert_equal "hello world", obj.serialized
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index b5d7ea603e..36d7294bc8 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -246,7 +246,7 @@ _SQL
assert_equal 2...10, @second_range.int4_range
assert_equal 2...Float::INFINITY, @third_range.int4_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int4_range)
- assert_equal nil, @empty_range.int4_range
+ assert_nil @empty_range.int4_range
end
def test_int8range_values
@@ -255,7 +255,7 @@ _SQL
assert_equal 11...100, @second_range.int8_range
assert_equal 11...Float::INFINITY, @third_range.int8_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int8_range)
- assert_equal nil, @empty_range.int8_range
+ assert_nil @empty_range.int8_range
end
def test_daterange_values
@@ -264,7 +264,7 @@ _SQL
assert_equal Date.new(2012, 1, 3)...Date.new(2012, 1, 4), @second_range.date_range
assert_equal Date.new(2012, 1, 3)...Float::INFINITY, @third_range.date_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range)
- assert_equal nil, @empty_range.date_range
+ assert_nil @empty_range.date_range
end
def test_numrange_values
@@ -273,7 +273,7 @@ _SQL
assert_equal BigDecimal.new('0.1')...BigDecimal.new('0.2'), @second_range.num_range
assert_equal BigDecimal.new('0.1')...BigDecimal.new('Infinity'), @third_range.num_range
assert_equal BigDecimal.new('-Infinity')...BigDecimal.new('Infinity'), @fourth_range.num_range
- assert_equal nil, @empty_range.num_range
+ assert_nil @empty_range.num_range
end
def test_tsrange_values
@@ -282,7 +282,7 @@ _SQL
assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range
assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range)
- assert_equal nil, @empty_range.ts_range
+ assert_nil @empty_range.ts_range
end
def test_tstzrange_values
@@ -290,7 +290,7 @@ _SQL
assert_equal Time.parse('2010-01-01 09:30:00 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), @first_range.tstz_range
assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Time.parse('2011-01-01 17:30:00 UTC'), @second_range.tstz_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range)
- assert_equal nil, @empty_range.tstz_range
+ assert_nil @empty_range.tstz_range
end
def test_money_values
@@ -314,11 +314,11 @@ _SQL
assert @first_range.tstz_range = new_tstzrange
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.tstz_range, new_tstzrange
+ assert_equal new_tstzrange, @first_range.tstz_range
assert @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000')
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.tstz_range, nil
+ assert_nil @first_range.tstz_range
end
def test_create_tsrange
@@ -338,11 +338,11 @@ _SQL
assert @first_range.ts_range = new_tsrange
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.ts_range, new_tsrange
+ assert_equal new_tsrange, @first_range.ts_range
assert @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0)
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.ts_range, nil
+ assert_nil @first_range.ts_range
end
def test_create_numrange
@@ -360,11 +360,11 @@ _SQL
assert @first_range.num_range = new_numrange
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.num_range, new_numrange
+ assert_equal new_numrange, @first_range.num_range
assert @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5')
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.num_range, nil
+ assert_nil @first_range.num_range
end
def test_create_daterange
@@ -382,11 +382,11 @@ _SQL
assert @first_range.date_range = new_daterange
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.date_range, new_daterange
+ assert_equal new_daterange, @first_range.date_range
assert @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3)
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.date_range, nil
+ assert_nil @first_range.date_range
end
def test_create_int4range
@@ -404,11 +404,11 @@ _SQL
assert @first_range.int4_range = new_int4range
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.int4_range, new_int4range
+ assert_equal new_int4range, @first_range.int4_range
assert @first_range.int4_range = 3...3
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.int4_range, nil
+ assert_nil @first_range.int4_range
end
def test_create_int8range
@@ -426,11 +426,11 @@ _SQL
assert @first_range.int8_range = new_int8range
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.int8_range, new_int8range
+ assert_equal new_int8range, @first_range.int8_range
assert @first_range.int8_range = 39999...39999
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.int8_range, nil
+ assert_nil @first_range.int8_range
end
def test_update_tsvector
@@ -441,7 +441,7 @@ _SQL
assert @first_tsvector.text_vector = new_text_vector
assert @first_tsvector.save
assert @first_tsvector.reload
- assert_equal @first_tsvector.text_vector, new_text_vector
+ assert_equal new_text_vector, @first_tsvector.text_vector
end
def test_number_values
@@ -482,11 +482,11 @@ _SQL
assert @first_array.commission_by_quarter = new_value
assert @first_array.save
assert @first_array.reload
- assert_equal @first_array.commission_by_quarter, new_value
+ assert_equal new_value, @first_array.commission_by_quarter
assert @first_array.commission_by_quarter = new_value
assert @first_array.save
assert @first_array.reload
- assert_equal @first_array.commission_by_quarter, new_value
+ assert_equal new_value, @first_array.commission_by_quarter
end
def test_update_text_array
@@ -494,11 +494,11 @@ _SQL
assert @first_array.nicknames = new_value
assert @first_array.save
assert @first_array.reload
- assert_equal @first_array.nicknames, new_value
+ assert_equal new_value, @first_array.nicknames
assert @first_array.nicknames = new_value
assert @first_array.save
assert @first_array.reload
- assert_equal @first_array.nicknames, new_value
+ assert_equal new_value, @first_array.nicknames
end
def test_update_money
@@ -516,15 +516,15 @@ _SQL
assert @first_number.double = new_double
assert @first_number.save
assert @first_number.reload
- assert_equal @first_number.single, new_single
- assert_equal @first_number.double, new_double
+ assert_equal new_single, @first_number.single
+ assert_equal new_double, @first_number.double
end
def test_update_time
assert @first_time.time_interval = '2 years 3 minutes'
assert @first_time.save
assert @first_time.reload
- assert_equal @first_time.time_interval, '2 years 00:03:00'
+ assert_equal '2 years 00:03:00', @first_time.time_interval
end
def test_update_network_address
@@ -548,10 +548,10 @@ _SQL
assert @first_bit_string.bit_string_varying = new_bit_string_varying
assert @first_bit_string.save
assert @first_bit_string.reload
- assert_equal @first_bit_string.bit_string, new_bit_string
+ assert_equal new_bit_string, @first_bit_string.bit_string
assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying
end
-
+
def test_invalid_hex_string
new_bit_string = 'FF'
@first_bit_string.bit_string = new_bit_string
@@ -563,7 +563,7 @@ _SQL
assert @first_oid.obj_id = new_value
assert @first_oid.save
assert @first_oid.reload
- assert_equal @first_oid.obj_id, new_value
+ assert_equal new_value, @first_oid.obj_id
end
def test_timestamp_with_zone_values_with_rails_time_zone_support
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index 6fc08ae4f0..f45c7afcc0 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -83,4 +83,18 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
x = JsonDataType.first
assert_equal(nil, x.payload)
end
+
+ def test_select_array_json_value
+ @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
+ x = JsonDataType.first
+ assert_equal(['v0', {'k1' => 'v1'}], x.payload)
+ end
+
+ def test_rewrite_array_json_value
+ @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
+ x = JsonDataType.first
+ x.payload = ['v1', {'k2' => 'v2'}, 'v3']
+ assert x.save!
+ end
+
end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 17d77c5454..fb88ab7c09 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -226,23 +226,51 @@ module ActiveRecord
end
def test_distinct_zero_orders
- assert_equal "DISTINCT posts.id",
- @connection.distinct("posts.id", [])
+ assert_deprecated do
+ assert_equal "DISTINCT posts.id",
+ @connection.distinct("posts.id", [])
+ end
+ end
+
+ def test_columns_for_distinct_zero_orders
+ assert_equal "posts.id",
+ @connection.columns_for_distinct("posts.id", [])
end
def test_distinct_one_order
- assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
- @connection.distinct("posts.id", ["posts.created_at desc"])
+ assert_deprecated do
+ assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
+ @connection.distinct("posts.id", ["posts.created_at desc"])
+ end
+ end
+
+ def test_columns_for_distinct_one_order
+ assert_equal "posts.id, posts.created_at AS alias_0",
+ @connection.columns_for_distinct("posts.id", ["posts.created_at desc"])
end
def test_distinct_few_orders
- assert_equal "DISTINCT posts.id, posts.created_at AS alias_0, posts.position AS alias_1",
- @connection.distinct("posts.id", ["posts.created_at desc", "posts.position asc"])
+ assert_deprecated do
+ assert_equal "DISTINCT posts.id, posts.created_at AS alias_0, posts.position AS alias_1",
+ @connection.distinct("posts.id", ["posts.created_at desc", "posts.position asc"])
+ end
+ end
+
+ def test_columns_for_distinct_few_orders
+ assert_equal "posts.id, posts.created_at AS alias_0, posts.position AS alias_1",
+ @connection.columns_for_distinct("posts.id", ["posts.created_at desc", "posts.position asc"])
end
def test_distinct_blank_not_nil_orders
- assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
- @connection.distinct("posts.id", ["posts.created_at desc", "", " "])
+ assert_deprecated do
+ assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
+ @connection.distinct("posts.id", ["posts.created_at desc", "", " "])
+ end
+ end
+
+ def test_columns_for_distinct_blank_not_nil_orders
+ assert_equal "posts.id, posts.created_at AS alias_0",
+ @connection.columns_for_distinct("posts.id", ["posts.created_at desc", "", " "])
end
def test_distinct_with_arel_order
@@ -250,13 +278,31 @@ module ActiveRecord
def order.to_sql
"posts.created_at desc"
end
- assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
- @connection.distinct("posts.id", [order])
+ assert_deprecated do
+ assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
+ @connection.distinct("posts.id", [order])
+ end
+ end
+
+ def test_columns_for_distinct_with_arel_order
+ order = Object.new
+ def order.to_sql
+ "posts.created_at desc"
+ end
+ assert_equal "posts.id, posts.created_at AS alias_0",
+ @connection.columns_for_distinct("posts.id", [order])
end
def test_distinct_with_nulls
- assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls first"])
- assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls last"])
+ assert_deprecated do
+ assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls first"])
+ assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls last"])
+ end
+ end
+
+ def test_columns_for_distinct_with_nulls
+ assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls first"])
+ assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls last"])
end
def test_raise_error_when_cannot_translate_exception
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index d51d425c3c..a8e5ab81e4 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -30,7 +30,7 @@ module ActiveRecord
assert @conn.valid_type?(column.type)
end
- # sqlite databses should be able to support any type and not
+ # sqlite databases should be able to support any type and not
# just the ones mentioned in the native_database_types.
# Therefore test_invalid column should always return true
# even if the type is not valid.
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 244e0b7179..500df52cd8 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -12,6 +12,8 @@ if ActiveRecord::Base.connection.supports_migrations?
def teardown
@connection.drop_table :fruits rescue nil
+ @connection.drop_table :nep_fruits rescue nil
+ @connection.drop_table :nep_schema_migrations rescue nil
ActiveRecord::SchemaMigration.delete_all rescue nil
end
@@ -30,6 +32,24 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal 7, ActiveRecord::Migrator::current_version
end
+ def test_schema_define_w_table_name_prefix
+ table_name = ActiveRecord::SchemaMigration.table_name
+ ActiveRecord::Base.table_name_prefix = "nep_"
+ ActiveRecord::SchemaMigration.table_name = "nep_#{table_name}"
+ ActiveRecord::Schema.define(:version => 7) do
+ create_table :fruits do |t|
+ t.column :color, :string
+ t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle
+ t.column :texture, :string
+ t.column :flavor, :string
+ end
+ end
+ assert_equal 7, ActiveRecord::Migrator::current_version
+ ensure
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::SchemaMigration.table_name = table_name
+ end
+
def test_schema_raises_an_error_for_invalid_column_type
assert_raise NoMethodError do
ActiveRecord::Schema.define(:version => 8) do
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 87af24cbe6..95896971a8 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -338,7 +338,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
topic.replies.create!(:title => "re: 37s", :content => "rails")
assert_equal 1, Topic.find(topic.id)[:replies_count]
- topic.update_columns(content: "rails is wonderfull")
+ topic.update_columns(content: "rails is wonderful")
assert_equal 1, Topic.find(topic.id)[:replies_count]
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index d85570236f..9f64ecd845 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1472,6 +1472,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal david.essays, Essay.where(writer_id: "David")
end
+ def test_has_many_assignment_with_custom_primary_key
+ david = people(:david)
+
+ assert_equal ["A Modest Proposal"], david.essays.map(&:name)
+ david.essays = [Essay.create!(name: "Remote Work" )]
+ assert_equal ["Remote Work"], david.essays.map(&:name)
+ end
+
def test_blank_custom_primary_key_on_new_record_should_not_run_queries
author = Author.new
assert !author.essays.loaded?
@@ -1739,12 +1747,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_deprecated { klass.has_many :foo, :counter_sql => 'lol' }
end
- test "sum calculation with block for array compatibility is deprecated" do
- assert_deprecated do
- posts(:welcome).comments.sum { |c| c.id }
- end
- end
-
test "has many associations on new records use null relations" do
post = Post.new
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 918783e8f1..9baf94399a 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -46,10 +46,10 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
assert_equal 2, authors.count
end
- def test_find_with_implicit_inner_joins_honors_readonly_without_select
- authors = Author.joins(:posts).to_a
- assert !authors.empty?, "expected authors to be non-empty"
- assert authors.all? {|a| a.readonly? }, "expected all authors to be readonly"
+ def test_find_with_implicit_inner_joins_without_select_does_not_imply_readonly
+ authors = Author.joins(:posts)
+ assert_not authors.empty?, "expected authors to be non-empty"
+ assert authors.none? {|a| a.readonly? }, "expected no authors to be readonly"
end
def test_find_with_implicit_inner_joins_honors_readonly_with_select
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 95c571fd03..c3b728296e 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -18,6 +18,8 @@ require 'models/ship'
require 'models/liquid'
require 'models/molecule'
require 'models/electron'
+require 'models/man'
+require 'models/interest'
class AssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :developers_projects,
@@ -215,7 +217,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert_equal post.body, "More cool stuff!"
end
- def test_reload_returns_assocition
+ def test_reload_returns_association
david = developers(:david)
assert_nothing_raised do
assert_equal david.projects, david.projects.reload.reload
@@ -242,6 +244,17 @@ class AssociationProxyTest < ActiveRecord::TestCase
david = developers(:david)
assert david.projects.equal?(david.projects)
end
+
+ test "inverses get set of subsets of the association" do
+ man = Man.create
+ man.interests.create
+
+ man = Man.find(man.id)
+
+ assert_queries(1) do
+ assert_equal man, man.interests.where("1=1").first.man
+ end
+ end
end
class OverridingAssociationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index d9c032392d..ee0150558d 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -27,6 +27,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase
ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers)
end
+ def test_attribute_for_inspect
+ t = topics(:first)
+ t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters"
+
+ assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on)
+ assert_equal '"The First Topic Now Has A Title With\nNewlines And M..."', t.attribute_for_inspect(:title)
+ end
+
def test_attribute_present
t = Topic.new
t.title = "hello there!"
@@ -130,6 +138,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal '10', keyboard.id_before_type_cast
assert_equal nil, keyboard.read_attribute_before_type_cast('id')
assert_equal '10', keyboard.read_attribute_before_type_cast('key_number')
+ assert_equal '10', keyboard.read_attribute_before_type_cast(:key_number)
end
# Syck calls respond_to? before actually calling initialize
@@ -710,6 +719,15 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } }
end
+ def test_bulk_update_raise_unknown_attribute_errro
+ error = assert_raises(ActiveRecord::UnknownAttributeError) {
+ @target.new(:hello => "world")
+ }
+ assert @target, error.record
+ assert "hello", error.attribute
+ assert "unknown attribute: hello", error.message
+ end
+
def test_read_attribute_overwrites_private_method_not_considered_implemented
# simulate a model with a db column that shares its name an inherited
# private method (e.g. Object#system)
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index d20ccaa5ca..395f28f280 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -581,12 +581,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "changed", post.body
end
- def test_attr_readonly_is_class_level_setting
- post = ReadonlyTitlePost.new
- assert_raise(NoMethodError) { post._attr_readonly = [:title] }
- assert_deprecated { post._attr_readonly }
- end
-
def test_non_valid_identifier_column_name
weird = Weird.create('a$b' => 'value')
weird.reload
@@ -1228,83 +1222,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_no_queries { assert true }
end
- def test_inspect_class
- assert_equal 'ActiveRecord::Base', ActiveRecord::Base.inspect
- assert_equal 'LoosePerson(abstract)', LoosePerson.inspect
- assert_match(/^Topic\(id: integer, title: string/, Topic.inspect)
- end
-
- def test_inspect_instance
- topic = topics(:first)
- assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", important: nil, approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect
- end
-
- def test_inspect_new_instance
- assert_match(/Topic id: nil/, Topic.new.inspect)
- end
-
- def test_inspect_limited_select_instance
- assert_equal %(#<Topic id: 1>), Topic.all.merge!(:select => 'id', :where => 'id = 1').first.inspect
- assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.all.merge!(:select => 'id, title', :where => 'id = 1').first.inspect
- end
-
- def test_inspect_class_without_table
- assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect
- end
-
- def test_attribute_for_inspect
- t = topics(:first)
- t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters"
-
- assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on)
- assert_equal '"The First Topic Now Has A Title With\nNewlines And M..."', t.attribute_for_inspect(:title)
- end
-
- def test_becomes
- assert_kind_of Reply, topics(:first).becomes(Reply)
- assert_equal "The First Topic", topics(:first).becomes(Reply).title
- end
-
- def test_becomes_includes_errors
- company = Company.new(:name => nil)
- assert !company.valid?
- original_errors = company.errors
- client = company.becomes(Client)
- assert_equal original_errors, client.errors
- end
-
- def test_silence_sets_log_level_to_error_in_block
- original_logger = ActiveRecord::Base.logger
-
- assert_deprecated do
- log = StringIO.new
- ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
- ActiveRecord::Base.logger.level = Logger::DEBUG
- ActiveRecord::Base.silence do
- ActiveRecord::Base.logger.warn "warn"
- ActiveRecord::Base.logger.error "error"
- end
- assert_equal "error\n", log.string
- end
- ensure
- ActiveRecord::Base.logger = original_logger
- end
-
- def test_silence_sets_log_level_back_to_level_before_yield
- original_logger = ActiveRecord::Base.logger
-
- assert_deprecated do
- log = StringIO.new
- ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
- ActiveRecord::Base.logger.level = Logger::WARN
- ActiveRecord::Base.silence do
- end
- assert_equal Logger::WARN, ActiveRecord::Base.logger.level
- end
- ensure
- ActiveRecord::Base.logger = original_logger
- end
-
def test_benchmark_with_log_level
original_logger = ActiveRecord::Base.logger
log = StringIO.new
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index ba6b0b1362..e09fa95756 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -12,7 +12,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_each_should_execute_one_query_per_batch
- assert_queries(Post.count + 1) do
+ assert_queries(@total + 1) do
Post.find_each(:batch_size => 1) do |post|
assert_kind_of Post, post
end
@@ -51,7 +51,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_find_in_batches_should_return_batches
- assert_queries(Post.count + 1) do
+ assert_queries(@total + 1) do
Post.find_in_batches(:batch_size => 1) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
@@ -60,7 +60,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_find_in_batches_should_start_from_the_start_option
- assert_queries(Post.count) do
+ assert_queries(@total) do
Post.find_in_batches(:batch_size => 1, :start => 2) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
@@ -69,14 +69,12 @@ class EachTest < ActiveRecord::TestCase
end
def test_find_in_batches_shouldnt_execute_query_unless_needed
- post_count = Post.count
-
assert_queries(2) do
- Post.find_in_batches(:batch_size => post_count) {|batch| assert_kind_of Array, batch }
+ Post.find_in_batches(:batch_size => @total) {|batch| assert_kind_of Array, batch }
end
assert_queries(1) do
- Post.find_in_batches(:batch_size => post_count + 1) {|batch| assert_kind_of Array, batch }
+ Post.find_in_batches(:batch_size => @total + 1) {|batch| assert_kind_of Array, batch }
end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index f49bef2346..0f3f9aecfc 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -6,6 +6,7 @@ require 'models/edge'
require 'models/organization'
require 'models/possession'
require 'models/topic'
+require 'models/reply'
require 'models/minivan'
require 'models/speedometer'
require 'models/ship_part'
@@ -166,6 +167,15 @@ class CalculationsTest < ActiveRecord::TestCase
assert_no_match(/OFFSET/, queries.first)
end
+ def test_count_on_invalid_columns_raises
+ e = assert_raises(ActiveRecord::StatementInvalid) {
+ Account.select("credit_limit, firm_name").count
+ }
+
+ assert_match "accounts", e.message
+ assert_match "credit_limit, firm_name", e.message
+ end
+
def test_should_group_by_summed_field_having_condition
c = Account.group(:firm_id).having('sum(credit_limit) > 50').sum(:credit_limit)
assert_nil c[1]
@@ -410,12 +420,6 @@ class CalculationsTest < ActiveRecord::TestCase
Account.where("credit_limit > 50").from('accounts').sum(:credit_limit)
end
- def test_sum_array_compatibility_deprecation
- assert_deprecated do
- assert_equal Account.sum(:credit_limit), Account.sum(&:credit_limit)
- end
- end
-
def test_average_with_from_option
assert_equal Account.average(:credit_limit), Account.from('accounts').average(:credit_limit)
assert_equal Account.where("credit_limit > 50").average(:credit_limit),
@@ -478,6 +482,11 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [1,2,3,4], Topic.order(:id).pluck(:id)
end
+ def test_pluck_without_column_names
+ assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, ""]],
+ Company.order(:id).limit(1).pluck
+ end
+
def test_pluck_type_cast
topic = topics(:first)
relation = Topic.where(:id => topic.id)
@@ -539,6 +548,11 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal Company.all.map(&:id).sort, Company.ids.sort
end
+ def test_pluck_with_includes_limit_and_empty_result
+ assert_equal [], Topic.includes(:replies).limit(0).pluck(:id)
+ assert_equal [], Topic.includes(:replies).limit(1).where('0 = 1').pluck(:id)
+ end
+
def test_pluck_not_auto_table_name_prefix_if_column_included
Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
ids = Company.includes(:contracts).pluck(:developer_id)
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 187cad9599..c8f56e3c73 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -43,7 +43,7 @@ class CallbackDeveloper < ActiveRecord::Base
end
class CallbackDeveloperWithFalseValidation < CallbackDeveloper
- before_validation proc { |model| model.history << [:before_validation, :returning_false]; return false }
+ before_validation proc { |model| model.history << [:before_validation, :returning_false]; false }
before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index e6af29282c..d5365b695e 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -329,7 +329,7 @@ module ActiveRecord
end
# make sure exceptions are thrown when establish_connection
- # is called with a anonymous class
+ # is called with an anonymous class
def test_anonymous_class_exception
anonymous = Class.new(ActiveRecord::Base)
handler = ActiveRecord::Base.connection_handler
diff --git a/activerecord/test/cases/core_test.rb b/activerecord/test/cases/core_test.rb
new file mode 100644
index 0000000000..2a52bf574c
--- /dev/null
+++ b/activerecord/test/cases/core_test.rb
@@ -0,0 +1,33 @@
+require 'cases/helper'
+require 'models/person'
+require 'models/topic'
+
+class NonExistentTable < ActiveRecord::Base; end
+
+class CoreTest < ActiveRecord::TestCase
+ fixtures :topics
+
+ def test_inspect_class
+ assert_equal 'ActiveRecord::Base', ActiveRecord::Base.inspect
+ assert_equal 'LoosePerson(abstract)', LoosePerson.inspect
+ assert_match(/^Topic\(id: integer, title: string/, Topic.inspect)
+ end
+
+ def test_inspect_instance
+ topic = topics(:first)
+ assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", important: nil, approved: false, replies_count: 1, unique_replies_count: 0, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect
+ end
+
+ def test_inspect_new_instance
+ assert_match(/Topic id: nil/, Topic.new.inspect)
+ end
+
+ def test_inspect_limited_select_instance
+ assert_equal %(#<Topic id: 1>), Topic.all.merge!(:select => 'id', :where => 'id = 1').first.inspect
+ assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.all.merge!(:select => 'id, title', :where => 'id = 1').first.inspect
+ end
+
+ def test_inspect_class_without_table
+ assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect
+ end
+end
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index ac093251a5..61f9d4cdae 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -51,6 +51,18 @@ class CounterCacheTest < ActiveRecord::TestCase
end
end
+ test 'reset multiple association counters' do
+ Topic.increment_counter(:replies_count, @topic.id)
+ assert_difference '@topic.reload.replies_count', -1 do
+ Topic.reset_counters(@topic.id, :replies, :unique_replies)
+ end
+
+ Topic.increment_counter(:unique_replies_count, @topic.id)
+ assert_difference '@topic.reload.unique_replies_count', -1 do
+ Topic.reset_counters(@topic.id, :replies, :unique_replies)
+ end
+ end
+
test "reset counters with string argument" do
Topic.increment_counter('replies_count', @topic.id)
diff --git a/activerecord/test/cases/disconnected_test.rb b/activerecord/test/cases/disconnected_test.rb
new file mode 100644
index 0000000000..cc2c1f6489
--- /dev/null
+++ b/activerecord/test/cases/disconnected_test.rb
@@ -0,0 +1,26 @@
+require "cases/helper"
+
+class TestRecord < ActiveRecord::Base
+end
+
+class TestDisconnectedAdapter < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ spec = ActiveRecord::Base.connection_config
+ ActiveRecord::Base.establish_connection(spec)
+ @connection = nil
+ end
+
+ test "can't execute statements while disconnected" do
+ @connection.execute "SELECT count(*) from products"
+ @connection.disconnect!
+ assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.execute "SELECT count(*) from products"
+ end
+ end
+end
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index fb53a92c89..b00e2744b9 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -43,6 +43,11 @@ if ActiveRecord::Base.connection.supports_explain?
assert queries.empty?
end
+ def test_collects_nothing_if_the_statement_is_only_partially_matched
+ SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'select_db yo_mama')
+ assert queries.empty?
+ end
+
def teardown
ActiveRecord::ExplainRegistry.reset
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 7db9aef218..6f0de42aef 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -98,6 +98,18 @@ class FinderTest < ActiveRecord::TestCase
assert !Topic.includes(:replies).limit(1).where('0 = 1').exists?
end
+ def test_exists_with_distinct_association_includes_and_limit
+ author = Author.first
+ assert !author.unique_categorized_posts.includes(:special_comments).limit(0).exists?
+ assert author.unique_categorized_posts.includes(:special_comments).limit(1).exists?
+ end
+
+ def test_exists_with_distinct_association_includes_limit_and_order
+ author = Author.first
+ assert !author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(0).exists?
+ assert author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(1).exists?
+ end
+
def test_exists_with_empty_table_and_no_args_given
Topic.delete_all
assert !Topic.exists?
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index f6cfee0cb8..df6edc4057 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -245,6 +245,22 @@ class FixturesTest < ActiveRecord::TestCase
def test_serialized_fixtures
assert_equal ["Green", "Red", "Orange"], traffic_lights(:uk).state
end
+
+ def test_fixtures_are_set_up_with_database_env_variable
+ ENV.stubs(:[]).with("DATABASE_URL").returns("sqlite3:///:memory:")
+ ActiveRecord::Base.stubs(:configurations).returns({})
+ test_case = Class.new(ActiveRecord::TestCase) do
+ fixtures :accounts
+
+ def test_fixtures
+ assert accounts(:signals37)
+ end
+ end
+
+ result = test_case.new(:test_fixtures).run
+
+ assert result.passed?, "Expected #{result.name} to pass:\n#{result}"
+ end
end
if Account.connection.respond_to?(:reset_pk_sequence!)
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 77891b9156..0030f1b464 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -242,7 +242,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
car = Car.create!
assert_difference 'car.wheels.count' do
- car.wheels << Wheel.create!
+ car.wheels << Wheel.create!
end
assert_difference 'car.wheels.count', -1 do
car.destroy
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index cad759bba9..e37dca856d 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -74,6 +74,35 @@ module ActiveRecord
assert_equal "hello", five.default unless mysql
end
+ def test_add_column_with_array
+ if current_adapter?(:PostgreSQLAdapter)
+ connection.create_table :testings
+ connection.add_column :testings, :foo, :string, :array => true
+
+ columns = connection.columns(:testings)
+ array_column = columns.detect { |c| c.name == "foo" }
+
+ assert array_column.array
+ else
+ skip "array option only supported in PostgreSQLAdapter"
+ end
+ end
+
+ def test_create_table_with_array_column
+ if current_adapter?(:PostgreSQLAdapter)
+ connection.create_table :testings do |t|
+ t.string :foo, :array => true
+ end
+
+ columns = connection.columns(:testings)
+ array_column = columns.detect { |c| c.name == "foo" }
+
+ assert array_column.array
+ else
+ skip "array option only supported in PostgreSQLAdapter"
+ end
+ end
+
def test_create_table_with_limits
connection.create_table :testings do |t|
t.column :foo, :string, :limit => 255
@@ -235,7 +264,7 @@ module ActiveRecord
end
end
- def test_keeping_default_and_notnull_constaint_on_change
+ def test_keeping_default_and_notnull_constraints_on_change
connection.create_table :testings do |t|
t.column :title, :string
end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index 0e375af6e8..04521a5f5a 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -109,16 +109,6 @@ module ActiveRecord
end
end
- def test_deprecated_type_argument
- message = "Passing a string as third argument of `add_index` is deprecated and will" +
- " be removed in Rails 4.1." +
- " Use add_index(:testings, [:foo, :bar], unique: true) instead"
-
- assert_deprecated message do
- connection.add_index :testings, [:foo, :bar], "UNIQUE"
- end
- end
-
def test_unique_index_exists
connection.add_index :testings, :foo, :unique => true
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 193ffb26e3..e99312c245 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -849,4 +849,13 @@ class CopyMigrationsTest < ActiveRecord::TestCase
ensure
clear
end
+
+ def test_check_pending_with_stdlib_logger
+ old, ActiveRecord::Base.logger = ActiveRecord::Base.logger, ::Logger.new($stdout)
+ quietly do
+ assert_nothing_raised { ActiveRecord::Migration::CheckPending.new(Proc.new {}).call({}) }
+ end
+ ensure
+ ActiveRecord::Base.logger = old
+ end
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 6fe81e0d96..2f89699df7 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -797,26 +797,6 @@ module NestedAttributesOnACollectionAssociationTests
end
end
- def test_validate_presence_of_parent_fails_without_inverse_of
- Man.accepts_nested_attributes_for(:interests)
- Man.reflect_on_association(:interests).options.delete(:inverse_of)
- Man.reflect_on_association(:interests).clear_inverse_of_cache!
- Interest.reflect_on_association(:man).options.delete(:inverse_of)
- Interest.reflect_on_association(:man).clear_inverse_of_cache!
-
- repair_validations(Interest) do
- Interest.validates_presence_of(:man)
- assert_no_difference ['Man.count', 'Interest.count'] do
- man = Man.create(:name => 'John',
- :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}])
- assert !man.errors[:"interests.man"].empty?
- end
- end
- ensure
- Man.reflect_on_association(:interests).options[:inverse_of] = :man
- Interest.reflect_on_association(:man).options[:inverse_of] = :interests
- end
-
def test_can_use_symbols_as_object_identifier
@pirate.attributes = { :parrots_attributes => { :foo => { :name => 'Lovely Day' }, :bar => { :name => 'Blown Away' } } }
assert_nothing_raised(NoMethodError) { @pirate.save! }
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index db3bb56f1f..30dc2a34c6 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -18,7 +18,7 @@ require 'models/pet'
require 'models/toy'
require 'rexml/document'
-class PersistencesTest < ActiveRecord::TestCase
+class PersistenceTest < ActiveRecord::TestCase
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans, :pets, :toys
# Oracle UPDATE does not support ORDER BY
@@ -139,6 +139,19 @@ class PersistencesTest < ActiveRecord::TestCase
end
end
+ def test_becomes
+ assert_kind_of Reply, topics(:first).becomes(Reply)
+ assert_equal "The First Topic", topics(:first).becomes(Reply).title
+ end
+
+ def test_becomes_includes_errors
+ company = Company.new(:name => nil)
+ assert !company.valid?
+ original_errors = company.errors
+ client = company.becomes(Client)
+ assert_equal original_errors, client.errors
+ end
+
def test_delete_many
original_count = Topic.count
Topic.delete(deleting = [1, 2])
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 8e5379cb1f..aa125c70c5 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -205,7 +205,7 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
- def test_primaery_key_method_with_ansi_quotes
+ def test_primary_key_method_with_ansi_quotes
con = ActiveRecord::Base.connection
con.execute("SET SESSION sql_mode='ANSI_QUOTES'")
assert_equal "id", con.primary_key("topics")
diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb
index df076c97b4..2afd25c989 100644
--- a/activerecord/test/cases/readonly_test.rb
+++ b/activerecord/test/cases/readonly_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'models/author'
require 'models/post'
require 'models/comment'
require 'models/developer'
@@ -7,7 +8,7 @@ require 'models/reader'
require 'models/person'
class ReadOnlyTest < ActiveRecord::TestCase
- fixtures :posts, :comments, :developers, :projects, :developers_projects, :people, :readers
+ fixtures :authors, :posts, :comments, :developers, :projects, :developers_projects, :people, :readers
def test_cant_save_readonly_record
dev = Developer.find(1)
@@ -34,15 +35,12 @@ class ReadOnlyTest < ActiveRecord::TestCase
Developer.readonly.each { |d| assert d.readonly? }
end
+ def test_find_with_joins_option_does_not_imply_readonly
+ Developer.joins(' ').each { |d| assert_not d.readonly? }
+ Developer.joins(' ').readonly(true).each { |d| assert d.readonly? }
- def test_find_with_joins_option_implies_readonly
- # Blank joins don't count.
- Developer.joins(' ').each { |d| assert !d.readonly? }
- Developer.joins(' ').readonly(false).each { |d| assert !d.readonly? }
-
- # Others do.
- Developer.joins(', projects').each { |d| assert d.readonly? }
- Developer.joins(', projects').readonly(false).each { |d| assert !d.readonly? }
+ Developer.joins(', projects').each { |d| assert_not d.readonly? }
+ Developer.joins(', projects').readonly(true).each { |d| assert d.readonly? }
end
def test_has_many_find_readonly
@@ -87,7 +85,7 @@ class ReadOnlyTest < ActiveRecord::TestCase
# conflicting column names
unless current_adapter?(:OracleAdapter)
Post.joins(', developers').scoping do
- assert Post.find(1).readonly?
+ assert_not Post.find(1).readonly?
assert Post.readonly.find(1).readonly?
assert !Post.readonly(false).find(1).readonly?
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index a9d46f4fba..b5314bc9be 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -35,18 +35,18 @@ class ReflectionTest < ActiveRecord::TestCase
def test_read_attribute_names
assert_equal(
- %w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count parent_id parent_title type created_at updated_at ).sort,
+ %w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count unique_replies_count parent_id parent_title type created_at updated_at ).sort,
@first.attribute_names.sort
)
end
def test_columns
- assert_equal 17, Topic.columns.length
+ assert_equal 18, Topic.columns.length
end
def test_columns_are_returned_in_the_order_they_were_declared
column_names = Topic.columns.map { |column| column.name }
- assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count parent_id parent_title type group created_at updated_at), column_names
+ assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count unique_replies_count parent_id parent_title type group created_at updated_at), column_names
end
def test_content_columns
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 482c1b3d48..55fd068d37 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -12,10 +12,7 @@ module ActiveRecord
end
def test_construction
- relation = nil
- assert_nothing_raised do
- relation = Relation.new FakeKlass, :b
- end
+ relation = Relation.new FakeKlass, :b
assert_equal FakeKlass, relation.klass
assert_equal :b, relation.table
assert !relation.loaded, 'relation is not loaded'
@@ -189,7 +186,7 @@ module ActiveRecord
post = Post.select(:title).first
assert_equal false, post.respond_to?(:body), "post should not respond_to?(:body) since invoking it raises exception"
- post = Post.select("'title' as post_title").first
+ silence_warnings { post = Post.select("'title' as post_title").first }
assert_equal false, post.respond_to?(:title), "post should not respond_to?(:body) since invoking it raises exception"
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index cf6af4e8f4..e746ca2805 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -278,8 +278,9 @@ class RelationTest < ActiveRecord::TestCase
def test_null_relation_calculations_methods
assert_no_queries do
- assert_equal 0, Developer.none.count
- assert_equal nil, Developer.none.calculate(:average, 'salary')
+ assert_equal 0, Developer.none.count
+ assert_equal 0, Developer.none.calculate(:count, nil, {})
+ assert_equal nil, Developer.none.calculate(:average, 'salary')
end
end
@@ -1546,4 +1547,26 @@ class RelationTest < ActiveRecord::TestCase
assert merged.to_sql.include?("wtf")
assert merged.to_sql.include?("bbq")
end
+
+ def test_merging_removes_rhs_bind_parameters
+ left = Post.where(id: Arel::Nodes::BindParam.new('?'))
+ column = Post.columns_hash['id']
+ left.bind_values += [[column, 20]]
+ right = Post.where(id: 10)
+
+ merged = left.merge(right)
+ assert_equal [], merged.bind_values
+ end
+
+ def test_merging_keeps_lhs_bind_parameters
+ column = Post.columns_hash['id']
+ binds = [[column, 20]]
+
+ right = Post.where(id: Arel::Nodes::BindParam.new('?'))
+ right.bind_values += binds
+ left = Post.where(id: 10)
+
+ merged = left.merge(right)
+ assert_equal binds, merged.bind_values
+ end
end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 765e92ccca..b49c27bc78 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -19,12 +19,6 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal %w(content), Topic.serialized_attributes.keys
end
- def test_serialized_attributes_are_class_level_settings
- topic = Topic.new
- assert_raise(NoMethodError) { topic.serialized_attributes = [] }
- assert_deprecated { topic.serialized_attributes }
- end
-
def test_serialized_attribute
Topic.serialize("content", MyObject)
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 3e32d866ee..c2c56abacd 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -150,9 +150,4 @@ class StoreTest < ActiveRecord::TestCase
test "all stored attributes are returned" do
assert_equal [:color, :homepage, :favorite_food], Admin::User.stored_attributes[:settings]
end
-
- test "stores_attributes are class level settings" do
- assert_raise(NoMethodError) { @john.stored_attributes = Hash.new }
- assert_raise(NoMethodError) { @john.stored_attributes }
- end
end
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index 7ac34bc71e..7e92a2b127 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -118,21 +118,4 @@ class AssociationValidationTest < ActiveRecord::TestCase
end
end
- def test_validates_associated_models_in_the_same_context
- Topic.validates_presence_of :title, :on => :custom_context
- Topic.validates_associated :replies
- Reply.validates_presence_of :title, :on => :custom_context
-
- t = Topic.new('title' => '')
- r = t.replies.new('title' => '')
-
- assert t.valid?
- assert !t.valid?(:custom_context)
-
- t.title = "Longer"
- assert !t.valid?(:custom_context), "Should NOT be valid if the associated object is not valid in the same context."
-
- r.title = "Longer"
- assert t.valid?(:custom_context), "Should be valid if the associated object is not valid in the same context."
- end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 57457359b1..2b33f01783 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -268,7 +268,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
end
def test_validate_case_sensitive_uniqueness_with_attribute_passed_as_integer
- Topic.validates_uniqueness_of(:title, :case_sensitve => true)
+ Topic.validates_uniqueness_of(:title, :case_sensitive => true)
Topic.create!('title' => 101)
t2 = Topic.new('title' => 101)
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index a96899ae10..af80b1ba42 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -85,7 +85,7 @@ class Author < ActiveRecord::Base
has_many :author_favorites
has_many :favorite_authors, -> { order('name') }, :through => :author_favorites
- has_many :taggings, :through => :posts
+ has_many :taggings, :through => :posts, :source => :taggings
has_many :taggings_2, :through => :posts, :source => :tagging
has_many :tags, :through => :posts
has_many :post_categories, :through => :posts, :source => :categories
diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb
index 7d7c205041..816c5e6937 100644
--- a/activerecord/test/models/club.rb
+++ b/activerecord/test/models/club.rb
@@ -1,6 +1,6 @@
class Club < ActiveRecord::Base
has_one :membership
- has_many :memberships, :automatic_inverse_of => false
+ has_many :memberships, :inverse_of => false
has_many :members, :through => :memberships
has_many :current_memberships
has_one :sponsor
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index dcda62e71d..b988184f34 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -141,7 +141,7 @@ class Client < Company
belongs_to :firm_with_primary_key_symbols, :class_name => "Firm", :primary_key => :name, :foreign_key => :firm_name
belongs_to :readonly_firm, -> { readonly }, :class_name => "Firm", :foreign_key => "firm_id"
belongs_to :bob_firm, -> { where :name => "Bob" }, :class_name => "Firm", :foreign_key => "client_of"
- has_many :accounts, :through => :firm
+ has_many :accounts, :through => :firm, :source => :accounts
belongs_to :account
class RaisedOnSave < RuntimeError; end
diff --git a/activerecord/test/models/interest.rb b/activerecord/test/models/interest.rb
index f772bb1c7f..d5d9226204 100644
--- a/activerecord/test/models/interest.rb
+++ b/activerecord/test/models/interest.rb
@@ -1,5 +1,5 @@
class Interest < ActiveRecord::Base
- belongs_to :man, :inverse_of => :interests, :automatic_inverse_of => false
+ belongs_to :man, :inverse_of => :interests
belongs_to :polymorphic_man, :polymorphic => true, :inverse_of => :polymorphic_interests
belongs_to :zine, :inverse_of => :interests
end
diff --git a/activerecord/test/models/man.rb b/activerecord/test/models/man.rb
index 49f002aa9a..4bff92dc98 100644
--- a/activerecord/test/models/man.rb
+++ b/activerecord/test/models/man.rb
@@ -1,7 +1,7 @@
class Man < ActiveRecord::Base
has_one :face, :inverse_of => :man
has_one :polymorphic_face, :class_name => 'Face', :as => :polymorphic_man, :inverse_of => :polymorphic_man
- has_many :interests, :inverse_of => :man, :automatic_inverse_of => false
+ has_many :interests, :inverse_of => :man
has_many :polymorphic_interests, :class_name => 'Interest', :as => :polymorphic_man, :inverse_of => :polymorphic_man
# These are "broken" inverse_of associations for the purposes of testing
has_one :dirty_face, :class_name => 'Face', :inverse_of => :dirty_man
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index b81304b8e0..cc47c7bc18 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -9,7 +9,7 @@ class Member < ActiveRecord::Base
has_one :hairy_club, -> { where :clubs => {:name => "Moustache and Eyebrow Fancier Club"} }, :through => :membership, :source => :club
has_one :sponsor, :as => :sponsorable
has_one :sponsor_club, :through => :sponsor
- has_one :member_detail, :automatic_inverse_of => false
+ has_one :member_detail, :inverse_of => false
has_one :organization, :through => :member_detail
belongs_to :member_type
diff --git a/activerecord/test/models/member_detail.rb b/activerecord/test/models/member_detail.rb
index a256c73c7e..9d253aa126 100644
--- a/activerecord/test/models/member_detail.rb
+++ b/activerecord/test/models/member_detail.rb
@@ -1,5 +1,5 @@
class MemberDetail < ActiveRecord::Base
- belongs_to :member, :automatic_inverse_of => false
+ belongs_to :member, :inverse_of => false
belongs_to :organization
has_one :member_type, :through => :member
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 2985160d28..1a282dbce4 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -32,6 +32,7 @@ class Person < ActiveRecord::Base
has_many :agents_posts, :through => :agents, :source => :posts
has_many :agents_posts_authors, :through => :agents_posts, :source => :author
+ has_many :essays, primary_key: "first_name", foreign_key: "writer_id"
scope :males, -> { where(:gender => 'M') }
scope :females, -> { where(:gender => 'F') }
diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb
index c88262580e..3e82e55d89 100644
--- a/activerecord/test/models/reply.rb
+++ b/activerecord/test/models/reply.rb
@@ -7,6 +7,7 @@ class Reply < Topic
end
class UniqueReply < Reply
+ belongs_to :topic, :foreign_key => 'parent_id', :counter_cache => true
validates_uniqueness_of :content, :scope => 'parent_id'
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 8beb58f3fc..188a3f0164 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -675,6 +675,7 @@ ActiveRecord::Schema.define do
end
t.boolean :approved, :default => true
t.integer :replies_count, :default => 0
+ t.integer :unique_replies_count, :default => 0
t.integer :parent_id
t.string :parent_title
t.string :type