aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md350
-rw-r--r--activerecord/lib/active_record/associations/association.rb15
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb19
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb18
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb20
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb9
-rw-r--r--activerecord/lib/active_record/base.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb10
-rw-r--r--activerecord/lib/active_record/explain.rb13
-rw-r--r--activerecord/lib/active_record/integration.rb13
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb6
-rw-r--r--activerecord/lib/active_record/migration.rb4
-rw-r--r--activerecord/lib/active_record/model_schema.rb4
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb7
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/query_cache.rb12
-rw-r--r--activerecord/lib/active_record/railties/databases.rake33
-rw-r--r--activerecord/lib/active_record/relation.rb2
-rw-r--r--activerecord/lib/active_record/relation/batches.rb4
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb14
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb8
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb6
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb9
-rw-r--r--activerecord/lib/active_record/result.rb12
-rw-r--r--activerecord/lib/active_record/scoping/named.rb4
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
-rw-r--r--activerecord/lib/active_record/version.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/sql_types_test.rb18
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb8
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb6
-rw-r--r--activerecord/test/cases/ar_schema_test.rb37
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb22
-rw-r--r--activerecord/test/cases/associations/eager_test.rb20
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb16
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb6
-rw-r--r--activerecord/test/cases/base_test.rb76
-rw-r--r--activerecord/test/cases/batches_test.rb23
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb19
-rw-r--r--activerecord/test/cases/calculations_test.rb31
-rw-r--r--activerecord/test/cases/column_test.rb26
-rw-r--r--activerecord/test/cases/dirty_test.rb29
-rw-r--r--activerecord/test/cases/dup_test.rb9
-rw-r--r--activerecord/test/cases/explain_test.rb10
-rw-r--r--activerecord/test/cases/finder_test.rb5
-rw-r--r--activerecord/test/cases/helper.rb2
-rw-r--r--activerecord/test/cases/locking_test.rb12
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb34
-rw-r--r--activerecord/test/cases/method_scoping_test.rb10
-rw-r--r--activerecord/test/cases/migration_test.rb44
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb11
-rw-r--r--activerecord/test/cases/persistence_test.rb11
-rw-r--r--activerecord/test/cases/query_cache_test.rb11
-rw-r--r--activerecord/test/cases/quoting_test.rb14
-rw-r--r--activerecord/test/cases/relation/where_test.rb25
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb6
-rw-r--r--activerecord/test/cases/relation_test.rb6
-rw-r--r--activerecord/test/cases/relations_test.rb21
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb16
-rw-r--r--activerecord/test/fixtures/peoples_treasures.yml3
-rw-r--r--activerecord/test/migrations/10_urban/9_add_expressions.rb11
-rw-r--r--activerecord/test/models/bulb.rb2
-rw-r--r--activerecord/test/models/developer.rb14
-rw-r--r--activerecord/test/models/person.rb27
-rw-r--r--activerecord/test/models/possession.rb3
-rw-r--r--activerecord/test/models/post.rb3
-rw-r--r--activerecord/test/models/topic.rb6
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb5
-rw-r--r--activerecord/test/schema/schema.rb14
84 files changed, 1148 insertions, 236 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 93c5aba851..5e5e1490c4 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,4 +1,343 @@
-## Rails 3.2.12 (unreleased) ##
+## unreleased ##
+
+* Removed warning when `auto_explain_threshold_in_seconds` is set and the
+ connection adapter doesn't support explain.
+ This is causing a regression since the Active Record Railtie is trying to
+ connect to the development database in the application boot.
+
+ *Rafael Mendonça França*
+
+* Do not reset `inheritance_column` when it's set explicitly.
+ Backport of #5327.
+
+ *kennyj + Fred Wu*
+
+* Fix a problem wrong exception is occured
+ when raising no translatable exception in PostgreSQL.
+
+ *kennyj*
+
+* Resets the postgres search path in the structure.sql after the structure
+ is dumped in order to find schema_migrations table when multiples schemas
+ are used.
+ Fixes #9796.
+
+ *Juan M. Cuello + Dembskiy Alexander*
+
+* Reload the association target if it's stale. `@stale_state` should be nil
+ when a model isn't saved.
+ Fixes #7526.
+
+ *Larry Lv*
+
+* Don't read CSV files during execution of `db:fixtures:load`. CSV support for
+ fixtures was removed some time ago but the task was still loading them, even
+ though later the code was looking for the related yaml file instead.
+
+ *kennyj*
+
+
+## Rails 3.2.13 (Mar 18, 2013) ##
+
+* Chaining multiple preloaded scopes will correctly preload all the scopes
+ at the same time.
+
+ *Chris Geihsler*
+
+* Reverted 921a296a3390192a71abeec6d9a035cc6d1865c8, 'Quote numeric values
+ compared to string columns.' This caused several regressions.
+
+ *Steve Klabnik*
+
+* Fix overriding of attributes by `default_scope` on `ActiveRecord::Base#dup`.
+
+ *Hiroshige UMINO*
+
+* Fix issue with overriding Active Record reader methods with a composed object
+ and using that attribute as the scope of a `uniqueness_of` validation.
+ Backport #7072.
+
+ *Peter Brown*
+
+* Sqlite now preserves custom primary keys when copying or altering tables.
+ Fixes #9367.
+ Backport #2312.
+
+ *Sean Scally + Yves Senn*
+
+* Preloading `has_many :through` associations with conditions won't
+ cache the `:through` association. This will prevent invalid
+ subsets to be cached.
+ Fixes #8423.
+ Backport #9252.
+
+ Example:
+
+ class User
+ has_many :posts
+ has_many :recent_comments, -> { where('created_at > ?', 1.week.ago) }, :through => :posts
+ end
+
+ a_user = User.includes(:recent_comments).first
+
+ # this is preloaded
+ a_user.recent_comments
+
+ # fetching the recent_comments through the posts association won't preload it.
+ a_user.posts
+
+ *Yves Senn*
+
+* Fix handling of dirty time zone aware attributes
+
+ Previously, when `time_zone_aware_attributes` were enabled, after
+ changing a datetime or timestamp attribute and then changing it back
+ to the original value, `changed_attributes` still tracked the
+ attribute as changed. This caused `[attribute]_changed?` and
+ `changed?` methods to return true incorrectly.
+
+ Example:
+
+ in_time_zone 'Paris' do
+ order = Order.new
+ original_time = Time.local(2012, 10, 10)
+ order.shipped_at = original_time
+ order.save
+ order.changed? # => false
+
+ # changing value
+ order.shipped_at = Time.local(2013, 1, 1)
+ order.changed? # => true
+
+ # reverting to original value
+ order.shipped_at = original_time
+ order.changed? # => false, used to return true
+ end
+
+ Backport of #9073
+ Fixes #8898
+
+ *Lilibeth De La Cruz*
+
+* Fix counter cache columns not updated when replacing `has_many :through`
+ associations.
+ Backport #8400.
+ Fix #7630.
+
+ *Matthew Robertson*
+
+* Don't update `column_defaults` when calling destructive methods on column with default value.
+ Backport c517602.
+ Fix #6115.
+
+ *Piotr Sarnacki + Aleksey Magusev + Alan Daud*
+
+* When `#count` is used in conjunction with `#uniq` we perform `count(:distinct => true)`.
+ Fix #6865.
+
+ Example:
+
+ relation.uniq.count # => SELECT COUNT(DISTINCT *)
+
+ *Yves Senn + Kaspar Schiess*
+
+* Fix `ActiveRecord::Relation#pluck` when columns or tables are reserved words.
+ Backport #7536.
+ Fix #8968.
+
+ *Ian Lesperance + Yves Senn + Kaspar Schiess*
+
+* Don't run explain on slow queries for database adapters that don't support it.
+ Backport #6197.
+
+ *Blake Smith*
+
+* Revert round usec when comparing timestamp attributes in the dirty tracking.
+ Fixes #8460.
+
+ *Andrew White*
+
+* Revert creation of through association models when using `collection=[]`
+ on a `has_many :through` association from an unsaved model.
+ Fix #7661, #8269.
+
+ *Ernie Miller*
+
+* Fix undefined method `to_i` when calling `new` on a scope that uses an
+ Array; Fix FloatDomainError when setting integer column to NaN.
+ Fixes #8718, #8734, #8757.
+
+ *Jason Stirk + Tristan Harward*
+
+* Serialized attributes can be serialized in integer columns.
+ Fix #8575.
+
+ *Rafael Mendonça França*
+
+* Keep index names when using `alter_table` with sqlite3.
+ Fix #3489.
+ Backport #8522.
+
+ *Yves Senn*
+
+* Recognize migrations placed in directories containing numbers and 'rb'.
+ Fix #8492.
+ Backport of #8500.
+
+ *Yves Senn*
+
+* Add `ActiveRecord::Base.cache_timestamp_format` class attribute to control
+ the format of the timestamp value in the cache key.
+ This allows users to improve the precision of the cache key.
+ Fixes #8195.
+
+ *Rafael Mendonça França*
+
+* Add `:nsec` date format. This can be used to improve the precision of cache key.
+ Please note that this format only works with Ruby 1.9, Ruby 1.8 will ignore it completely.
+
+ *Jamie Gaskins*
+
+* Unscope `update_column(s)` query to ignore default scope.
+
+ When applying `default_scope` to a class with a where clause, using
+ `update_column(s)` could generate a query that would not properly update
+ the record due to the where clause from the `default_scope` being applied
+ to the update query.
+
+ class User < ActiveRecord::Base
+ default_scope where(active: true)
+ end
+
+ user = User.first
+ user.active = false
+ user.save!
+
+ user.update_column(:active, true) # => false
+
+ In this situation we want to skip the default_scope clause and just
+ update the record based on the primary key. With this change:
+
+ user.update_column(:active, true) # => true
+
+ Backport of #8436 fix.
+
+ *Carlos Antonio da Silva*
+
+* Fix performance problem with primary_key method in PostgreSQL adapter when having many schemas.
+ Uses pg_constraint table instead of pg_depend table which has many records in general.
+ Fix #8414
+
+ *kennyj*
+
+* Do not instantiate intermediate Active Record objects when eager loading.
+ These records caused `after_find` to run more than expected.
+ Fix #3313
+ Backport of #8403
+
+ *Yves Senn*
+
+* Fix `pluck` to work with joins. Backport of #4942.
+
+ *Carlos Antonio da Silva*
+
+* Fix a problem with `translate_exception` method in a non English environment.
+ Backport of #6397.
+
+ *kennyj*
+
+* Fix dirty attribute checks for TimeZoneConversion with nil and blank
+ datetime attributes. Setting a nil datetime to a blank string should not
+ result in a change being flagged.
+ Fixes #8310.
+ Backport of #8311.
+
+ *Alisdair McDiarmid*
+
+* Prevent mass assignment to the type column of polymorphic associations when using `build`.
+ Fixes #8265.
+ Backport of #8291.
+
+ *Yves Senn*
+
+* When running migrations on Postgresql, the `:limit` option for `binary` and `text` columns is
+ silently dropped.
+ Previously, these migrations caused sql exceptions, because Postgresql doesn't support limits
+ on these types.
+
+ *Victor Costan*
+
+* `#pluck` can be used on a relation with `select` clause.
+ Fixes #7551.
+ Backport of #8176.
+
+ Example:
+
+ Topic.select([:approved, :id]).order(:id).pluck(:id)
+
+ *Yves Senn*
+
+* Use `nil?` instead of `blank?` to check whether dynamic finder with a bang
+ should raise RecordNotFound.
+ Fixes #7238.
+
+ *Nikita Afanasenko*
+
+* Fix deleting from a HABTM join table upon destroying an object of a model
+ with optimistic locking enabled.
+ Fixes #5332.
+
+ *Nick Rogers*
+
+* Use query cache/uncache when using ENV["DATABASE_URL"].
+ Fixes #6951.
+ Backport of #8074.
+
+ *kennyj*
+
+* Do not create useless database transaction when building `has_one` association.
+
+ Example:
+
+ User.has_one :profile
+ User.new.build_profile
+
+ Backport of #8154.
+
+ *Bogdan Gusiev*
+
+* `AR::Base#attributes_before_type_cast` now returns unserialized values for serialized attributes.
+
+ *Nikita Afanasenko*
+
+* Fix issue that raises `NameError` when overriding the `accepts_nested_attributes` in child classes.
+
+ Before:
+
+ class Shared::Person < ActiveRecord::Base
+ has_one :address
+
+ accepts_nested_attributes :address, :reject_if => :all_blank
+ end
+
+ class Person < Shared::Person
+ accepts_nested_attributes :address
+ end
+
+ Person
+ #=> NameError: method `address_attributes=' not defined in Person
+
+ After:
+
+ Person
+ #=> Person(id: integer, ...)
+
+ Fixes #8131.
+
+ *Gabriel Sobrinho, Ricardo Henrique*
+
+
+## Rails 3.2.12 (Feb 11, 2013) ##
* Quote numeric values being compared to non-numeric columns. Otherwise,
in some database, the string column values will be coerced to a numeric
@@ -10,17 +349,24 @@
*Dylan Smith*
+
## Rails 3.2.11 (Jan 8, 2013) ##
* Fix querying with an empty hash *Damien Mathieu* [CVE-2013-0155]
-## Rails 3.2.10 ##
+
+## Rails 3.2.10 (Jan 2, 2013) ##
* CVE-2012-5664 options hashes should only be extracted if there are extra
parameters
+
## Rails 3.2.9 (Nov 12, 2012) ##
+* Fix `find_in_batches` crashing when IDs are strings and start option is not specified.
+
+ *Alexis Bernard*
+
* Fix issue with collection associations calling first(n)/last(n) and attempting
to set the inverse association when `:inverse_of` was used. Fixes #8087.
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 59c1bad559..99f307922e 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -46,6 +46,7 @@ module ActiveRecord
@loaded = false
IdentityMap.remove(target) if IdentityMap.enabled? && target
@target = nil
+ @stale_state = nil
end
# Reloads the \target and returns +self+ on success.
@@ -128,16 +129,21 @@ module ActiveRecord
# This method is abstract in the sense that it relies on +find_target+,
# which is expected to be provided by descendants.
#
- # If the \target is already \loaded it is just returned. Thus, you can call
- # +load_target+ unconditionally to get the \target.
+ # If the \target is stale(the target no longer points to the record(s) that the
+ # relevant foreign_key(s) refers to.), force reload the \target.
+ #
+ # Otherwise if the \target is already \loaded it is just returned. Thus, you can
+ # call +load_target+ unconditionally to get the \target.
#
# ActiveRecord::RecordNotFound is rescued within the method, and it is
# not reraised. The proxy is \reset and +nil+ is the return value.
def load_target
- if find_target?
+ if (@stale_state && stale_target?) || find_target?
begin
if IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class)
@target = IdentityMap.get(association_class, owner[reflection.foreign_key])
+ elsif @stale_state && stale_target?
+ @target = find_target
end
rescue NameError
nil
@@ -231,7 +237,8 @@ module ActiveRecord
def build_record(attributes, options)
reflection.build_association(attributes, options) do |record|
- attributes = create_scope.except(*(record.changed - [reflection.foreign_key]))
+ skip_assign = [reflection.foreign_key, reflection.type].compact
+ attributes = create_scope.except(*(record.changed - skip_assign))
record.assign_attributes(attributes, :without_protection => true)
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 97f531d064..52c67df646 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -72,7 +72,7 @@ module ActiveRecord
end
def stale_state
- owner[reflection.foreign_key].to_s
+ owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
end
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index 2ee5dbbd70..88ce03a3cd 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -27,7 +27,8 @@ module ActiveRecord
end
def stale_state
- [super, owner[reflection.foreign_type].to_s]
+ foreign_key = super
+ foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index ce81333aa9..2b9e7ea55d 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -38,20 +38,6 @@ module ActiveRecord
super
end
- def concat_records(records)
- ensure_not_nested
-
- records = super
-
- if owner.new_record? && records
- records.flatten.each do |record|
- build_through_record(record)
- end
- end
-
- records
- end
-
def insert_record(record, validate = true, raise = false)
ensure_not_nested
@@ -153,6 +139,11 @@ module ActiveRecord
delete_through_records(records)
+ if source_reflection.options[:counter_cache]
+ counter = source_reflection.counter_cache_column
+ klass.decrement_counter counter, records.map(&:id)
+ end
+
if through_reflection.macro == :has_many && update_through_counter?(method)
update_counter(-count, through_reflection)
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 501ebe7c5b..56f9013d61 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -11,7 +11,7 @@ module ActiveRecord
# If target and record are nil, or target is equal to record,
# we don't need to have transaction.
if (target || record) && target != record
- reflection.klass.transaction do
+ transaction_if(save) do
remove_target!(options[:dependent]) if target && !target.destroyed?
if record
@@ -70,6 +70,14 @@ module ActiveRecord
def nullify_owner_attributes(record)
record[reflection.foreign_key] = nil
end
+
+ def transaction_if(value)
+ if value
+ reflection.klass.transaction { yield }
+ else
+ yield
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index ad6374d09a..e052b00403 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -37,7 +37,8 @@ module ActiveRecord
through_records = Array.wrap(owner.send(through_reflection.name))
# Dont cache the association - we would only be caching a subset
- if reflection.options[:source_type] && through_reflection.collection?
+ if (preload_options != through_options) ||
+ (reflection.options[:source_type] && through_reflection.collection?)
owner.association(through_reflection.name).reset
end
@@ -55,8 +56,7 @@ module ActiveRecord
through_options[:include] = options[:include] || options[:source]
through_options[:conditions] = options[:conditions]
end
-
- through_options[:order] = options[:order]
+ through_options[:order] = options[:order] if options.has_key?(:order)
end
through_options
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index fd0e90aaf0..be890e5767 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -62,7 +62,7 @@ module ActiveRecord
# properly support stale-checking for nested associations.
def stale_state
if through_reflection.macro == :belongs_to
- owner[through_reflection.foreign_key].to_s
+ owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 00023b0b6c..deb89f3ac1 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -90,6 +90,14 @@ module ActiveRecord
end
end
+ def _field_changed?(attr, old, value)
+ if self.class.serialized_attributes.include?(attr)
+ old != value
+ else
+ super
+ end
+ end
+
def read_attribute_before_type_cast(attr_name)
if serialized_attributes.include?(attr_name)
super.unserialized_value
@@ -97,6 +105,16 @@ module ActiveRecord
super
end
end
+
+ def attributes_before_type_cast
+ super.dup.tap do |attributes|
+ self.class.serialized_attributes.each_key do |key|
+ if attributes.key?(key)
+ attributes[key] = attributes[key].unserialized_value
+ end
+ end
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 4c3d5eed4c..6f3112e654 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -37,18 +37,16 @@ module ActiveRecord
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
method_body, line = <<-EOV, __LINE__ + 1
def #{attr_name}=(original_time)
+ original_time = nil if original_time.blank?
time = original_time
unless time.acts_like?(:time)
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
end
- zoned_time = time && time.in_time_zone rescue nil
- rounded_time = round_usec(zoned_time)
- rounded_value = round_usec(read_attribute("#{attr_name}"))
- if (rounded_value != rounded_time) || (!rounded_value && original_time)
- write_attribute("#{attr_name}", original_time)
- #{attr_name}_will_change!
- @attributes_cache["#{attr_name}"] = zoned_time
- end
+ time = time.in_time_zone rescue nil if time
+ previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
+ write_attribute(:#{attr_name}, original_time)
+ #{attr_name}_will_change! if previous_time != time
+ @attributes_cache["#{attr_name}"] = time
end
EOV
generated_attribute_methods.module_eval(method_body, __FILE__, line)
@@ -62,12 +60,6 @@ module ActiveRecord
time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && column.type.in?([:datetime, :timestamp])
end
end
-
- private
- def round_usec(value)
- return unless value
- value.change(:usec => 0)
- end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 8c6fa90a28..16aed7348f 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -54,12 +54,13 @@ module ActiveRecord
end
def convert_number_column_value(value)
- if value == false
+ case value
+ when FalseClass
0
- elsif value == true
+ when TrueClass
1
- elsif value.is_a?(String) && value.blank?
- nil
+ when String
+ value.presence
else
value
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 62c8110274..5bd1721439 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -479,7 +479,8 @@ module ActiveRecord #:nodoc:
# # Instantiates a single new object bypassing mass-assignment security
# User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
def initialize(attributes = nil, options = {})
- @attributes = self.class.initialize_attributes(self.class.column_defaults.dup)
+ defaults = Hash[self.class.column_defaults.map { |k, v| [k, v.duplicable? ? v.dup : v] }]
+ @attributes = self.class.initialize_attributes(defaults)
@association_cache = {}
@aggregation_cache = {}
@attributes_cache = {}
@@ -552,12 +553,11 @@ module ActiveRecord #:nodoc:
@new_record = true
ensure_proper_type
- populate_with_current_scope_attributes
super
end
# Backport dup from 1.9 so that initialize_dup() gets called
- unless Object.respond_to?(:initialize_dup)
+ unless Object.respond_to?(:initialize_dup, true)
def dup # :nodoc:
copy = super
copy.initialize_dup(self)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index db99c3fbef..2703c2ed70 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -266,7 +266,7 @@ module ActiveRecord
# Inserts the given fixture into the table. Overridden in adapters that require
# something beyond a simple insert (eg. Oracle).
def insert_fixture(fixture, table_name)
- columns = Hash[columns(table_name).map { |c| [c.name, c] }]
+ columns = schema_cache.columns_hash(table_name)
key_list = []
value_list = fixture.map do |name, value|
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index fe0b1959f6..f93c7cd74a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -25,19 +25,13 @@ module ActiveRecord
when true, false
if column && column.type == :integer
value ? '1' : '0'
- elsif column && [:text, :string, :binary].include?(column.type)
- value ? "'1'" : "'0'"
else
value ? quoted_true : quoted_false
end
# BigDecimals need to be put in a non-normalized form and quoted.
when nil then "NULL"
- when Numeric, ActiveSupport::Duration
- value = BigDecimal === value ? value.to_s('F') : value.to_s
- if column && ![:integer, :float, :decimal].include?(column.type)
- value = "'#{value}'"
- end
- value
+ when BigDecimal then value.to_s('F')
+ when Numeric then value.to_s
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
else
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 ddb6896257..6a5cff6acd 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -42,8 +42,8 @@ module ActiveRecord
# Represents the schema of an SQL table in an abstract way. This class
# provides methods for manipulating the schema representation.
#
- # Inside migration files, the +t+ object in +create_table+ and
- # +change_table+ is actually of this type:
+ # Inside migration files, the +t+ object in +create_table+
+ # is actually of this type:
#
# class SomeMigration < ActiveRecord::Migration
# def up
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 61c5e8040e..abccc3af24 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -199,6 +199,8 @@ module ActiveRecord
if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
s = column.class.string_to_binary(value).unpack("H*")[0]
"x'#{s}'"
+ elsif value.kind_of?(BigDecimal)
+ value.to_s("F")
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index e6269c78cf..e22d9f7222 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -175,7 +175,7 @@ module ActiveRecord
when TrueClass, FalseClass
value ? 1 : 0
else
- value.to_i
+ value.to_i rescue nil
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index c132692249..4850b68051 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -2,7 +2,7 @@ require 'active_record/connection_adapters/abstract_mysql_adapter'
require 'active_record/connection_adapters/statement_pool'
require 'active_support/core_ext/hash/keys'
-gem 'mysql', '~> 2.8.1'
+gem 'mysql', '~> 2.8'
require 'mysql'
class Mysql
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 80a0c6d13c..cbbb195458 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -315,8 +315,6 @@ module ActiveRecord
@visitor = BindSubstitution.new self
end
- connection_parameters.delete :prepared_statements
-
@connection_parameters, @config = connection_parameters, config
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@@ -988,12 +986,11 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
row = exec_query(<<-end_sql, 'SCHEMA').rows.first
- SELECT DISTINCT(attr.attname)
+ SELECT attr.attname
FROM pg_attribute attr
- INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
WHERE cons.contype = 'p'
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
+ AND cons.conrelid = '#{quote_table_name(table)}'::regclass
end_sql
row && row.first
@@ -1078,6 +1075,13 @@ module ActiveRecord
when nil, 0..0x3fffffff; super(type)
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
end
+ when 'text'
+ # PostgreSQL doesn't support limits on text columns.
+ # The hard limit is 1Gb, according to section 8.3 in the manual.
+ case limit
+ when nil, 0..0x3fffffff; super(type)
+ else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
+ end
when 'integer'
return 'integer' unless limit
@@ -1135,11 +1139,17 @@ module ActiveRecord
@connection.server_version
end
+ # See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
+ FOREIGN_KEY_VIOLATION = "23503"
+ UNIQUE_VIOLATION = "23505"
+
def translate_exception(exception, message)
- case exception.message
- when /duplicate key value violates unique constraint/
+ return exception unless exception.respond_to?(:result)
+
+ case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
+ when UNIQUE_VIOLATION
RecordNotUnique.new(message, exception)
- when /violates foreign key constraint/
+ when FOREIGN_KEY_VIOLATION
InvalidForeignKey.new(message, exception)
else
super
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 4e8932a695..bc8d24a03f 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module ConnectionAdapters
class SchemaCache
- attr_reader :columns, :columns_hash, :primary_keys, :tables
+ attr_reader :primary_keys, :tables
attr_reader :connection
def initialize(conn)
@@ -30,6 +30,25 @@ module ActiveRecord
@tables[name] = connection.table_exists?(name)
end
+ # Get the columns for a table
+ def columns(table = nil)
+ if table
+ @columns[table]
+ else
+ @columns
+ end
+ end
+
+ # Get the columns for a table as a hash, key is the column name
+ # value is the column object.
+ def columns_hash(table = nil)
+ if table
+ @columns_hash[table]
+ else
+ @columns_hash
+ end
+ end
+
# Clears out internal caches
def clear!
@columns.clear
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index e80b465bab..455560aa55 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -490,7 +490,11 @@ module ActiveRecord
end
def copy_table(from, to, options = {}) #:nodoc:
- options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
+ from_primary_key = primary_key(from)
+ options[:primary_key] = from_primary_key if from_primary_key != 'id'
+ unless options[:primary_key]
+ options[:id] = !columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == from_primary_key
+ end
create_table(to, options) do |definition|
@definition = definition
columns(from).each do |column|
@@ -504,7 +508,7 @@ module ActiveRecord
:precision => column.precision, :scale => column.scale,
:null => column.null)
end
- @definition.primary_key(primary_key(from)) if primary_key(from)
+ @definition.primary_key(from_primary_key) if from_primary_key
yield @definition if block_given?
end
@@ -530,7 +534,7 @@ module ActiveRecord
unless columns.empty?
# index name can't be the same
- opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
+ opts = { :name => name.gsub(/(^|_)(#{from})_/, "\\1#{to}_") }
opts[:unique] = true if index.unique
add_index(to, columns, opts)
end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index bdccf535ea..236834fd84 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -11,11 +11,12 @@ module ActiveRecord
end
end
- # If auto explain is enabled, this method triggers EXPLAIN logging for the
- # queries triggered by the block if it takes more than the threshold as a
- # whole. That is, the threshold is not checked against each individual
- # query, but against the duration of the entire block. This approach is
- # convenient for relations.
+ # If the database adapter supports explain and auto explain is enabled,
+ # this method triggers EXPLAIN logging for the queries triggered by the
+ # block if it takes more than the threshold as a whole. That is, the
+ # threshold is not checked against each individual query, but against the
+ # duration of the entire block. This approach is convenient for relations.
+
#
# The available_queries_for_explain thread variable collects the queries
# to be explained. If the value is nil, it means queries are not being
@@ -26,7 +27,7 @@ module ActiveRecord
threshold = auto_explain_threshold_in_seconds
current = Thread.current
- if threshold && current[:available_queries_for_explain].nil?
+ if connection.supports_explain? && threshold && current[:available_queries_for_explain].nil?
begin
queries = current[:available_queries_for_explain] = []
start = Time.now
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 2c42f4cca5..f2ace18d44 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -1,5 +1,16 @@
module ActiveRecord
module Integration
+ extend ActiveSupport::Concern
+
+ included do
+ ##
+ # :singleton-method:
+ # Indicates the format used to generate the timestamp format in the cache key.
+ # This is +:number+, by default.
+ class_attribute :cache_timestamp_format, :instance_writer => false
+ self.cache_timestamp_format = :number
+ end
+
# Returns a String, which Action Pack uses for constructing an URL to this
# object. The default implementation returns this record's id as a String,
# or nil if this record's unsaved.
@@ -39,7 +50,7 @@ module ActiveRecord
when new_record?
"#{self.class.model_name.cache_key}/new"
when timestamp = self[:updated_at]
- timestamp = timestamp.utc.to_s(:number)
+ timestamp = timestamp.utc.to_s(cache_timestamp_format)
"#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
else
"#{self.class.model_name.cache_key}/#{id}"
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 7deac2588a..b2881991d5 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -102,6 +102,8 @@ module ActiveRecord
def destroy #:nodoc:
return super unless locking_enabled?
+ destroy_associations
+
if persisted?
table = self.class.arel_table
lock_col = self.class.locking_column
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index a25f2c7bca..2b6488f183 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -32,7 +32,11 @@ module ActiveRecord
unless (payload[:binds] || []).empty?
binds = " " + payload[:binds].map { |col,v|
- [col.name, v]
+ if col
+ [col.name, v]
+ else
+ [nil, v]
+ end
}.inspect
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index e6686597b7..e8ec93470d 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -458,7 +458,7 @@ module ActiveRecord
say_with_time "#{method}(#{arg_list})" do
unless reverting?
unless arguments.empty? || method == :execute
- arguments[0] = Migrator.proper_table_name(arguments.first)
+ arguments[0] = Migrator.proper_table_name(arguments.first) unless method == :assume_migrated_upto_version
arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table
end
end
@@ -627,7 +627,7 @@ module ActiveRecord
seen = Hash.new false
migrations = files.map do |file|
- version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first
+ version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/).first
raise IllegalMigrationNameError.new(file) unless version
version = version.to_i
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 1517e5ec17..e10e6b4aa8 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -173,6 +173,7 @@ module ActiveRecord
def inheritance_column=(value)
@original_inheritance_column = inheritance_column
@inheritance_column = value.to_s
+ @explicit_inheritance_column = true
end
def set_inheritance_column(value = nil, &block) #:nodoc:
@@ -300,7 +301,8 @@ module ActiveRecord
connection.schema_cache.clear_table_cache!(table_name) if table_exists?
@column_names = @content_columns = @column_defaults = @columns = @columns_hash = nil
- @dynamic_methods_hash = @inheritance_column = nil
+ @dynamic_methods_hash = nil
+ @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
@arel_engine = @relation = nil
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index d2065d701f..05091654c0 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -277,13 +277,14 @@ module ActiveRecord
type = (reflection.collection? ? :collection : :one_to_one)
+ # remove_possible_method :pirate_attributes=
+ #
# def pirate_attributes=(attributes)
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options)
# end
class_eval <<-eoruby, __FILE__, __LINE__ + 1
- if method_defined?(:#{association_name}_attributes=)
- remove_method(:#{association_name}_attributes=)
- end
+ remove_possible_method(:#{association_name}_attributes=)
+
def #{association_name}_attributes=(attributes)
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options)
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index fd3380a53c..36b6b5fce2 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -194,7 +194,7 @@ module ActiveRecord
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
raise ActiveRecordError, "can not update on a new record object" unless persisted?
- updated_count = self.class.update_all({ name => value }, self.class.primary_key => id)
+ updated_count = self.class.unscoped.update_all({ name => value }, self.class.primary_key => id)
raw_write_attribute(name, value)
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 466d148901..2156889a0f 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -6,19 +6,19 @@ module ActiveRecord
module ClassMethods
# Enable the query cache within the block if Active Record is configured.
def cache(&block)
- if ActiveRecord::Base.configurations.blank?
- yield
- else
+ if ActiveRecord::Base.connected?
connection.cache(&block)
+ else
+ yield
end
end
# Disable the query cache within the block if Active Record is configured.
def uncached(&block)
- if ActiveRecord::Base.configurations.blank?
- yield
- else
+ if ActiveRecord::Base.connected?
connection.uncached(&block)
+ else
+ yield
end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index ae6d9c8653..b8aed49854 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -64,10 +64,21 @@ db_namespace = namespace :db do
end
end
+ # If neither encoding nor collation is specified, use the utf-8 defaults.
def mysql_creation_options(config)
- @charset = ENV['CHARSET'] || 'utf8'
- @collation = ENV['COLLATION'] || 'utf8_unicode_ci'
- {:charset => (config['encoding'] || @charset), :collation => (config['collation'] || @collation)}
+ default_charset = ENV['CHARSET'] || 'utf8'
+ default_collation = ENV['COLLATION'] || 'utf8_unicode_ci'
+
+ Hash.new.tap do |options|
+ options[:charset] = config['encoding'] if config.include? 'encoding'
+ options[:collation] = config['collation'] if config.include? 'collation'
+
+ # Set default charset only when collation isn't set.
+ options[:charset] ||= default_charset unless options[:collation]
+
+ # Set default collation only when charset is also default.
+ options[:collation] ||= default_collation if options[:charset] == default_charset
+ end
end
def create_database(config)
@@ -101,9 +112,12 @@ db_namespace = namespace :db do
error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error
end
access_denied_error = 1045
+
+ create_options = mysql_creation_options(config)
+
begin
ActiveRecord::Base.establish_connection(config.merge('database' => nil))
- ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
+ ActiveRecord::Base.connection.create_database(config['database'], create_options)
ActiveRecord::Base.establish_connection(config)
rescue error_class => sqlerr
if sqlerr.errno == access_denied_error
@@ -119,7 +133,7 @@ db_namespace = namespace :db do
ActiveRecord::Base.establish_connection(config)
else
$stderr.puts sqlerr.error
- $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['encoding'] || @charset}, collation: #{config['collation'] || @collation}"
+ $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{create_options[:charset]}, collation: #{create_options[:collation]}"
$stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['encoding']
end
end
@@ -341,7 +355,7 @@ db_namespace = namespace :db do
base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
- (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
ActiveRecord::Fixtures.create_fixtures(fixtures_dir, fixture_file)
end
end
@@ -413,17 +427,18 @@ db_namespace = namespace :db do
end
`pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(config['database'])}`
raise 'Error dumping database' if $?.exitstatus == 1
+ File.open(filename, "a") { |f| f << "SET search_path TO #{ActiveRecord::Base.connection.schema_search_path};\n\n" }
when /sqlite/
dbfile = config['database']
`sqlite3 #{dbfile} .schema > #{filename}`
when 'sqlserver'
`smoscript -s #{config['host']} -d #{config['database']} -u #{config['username']} -p #{config['password']} -f #{filename} -A -U`
when "firebird"
- set_firebird_env(abcs[Rails.env])
- db_string = firebird_db_string(abcs[Rails.env])
+ set_firebird_env(config)
+ db_string = firebird_db_string(config)
sh "isql -a #{db_string} > #{filename}"
else
- raise "Task not supported by '#{abcs[Rails.env]["adapter"]}'"
+ raise "Task not supported by '#{config['adapter']}'"
end
if ActiveRecord::Base.connection.supports_migrations?
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 4b3b30d6ed..ae1a57545e 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -464,7 +464,7 @@ module ActiveRecord
node.left.relation.name == table_name
}
- Hash[equalities.map { |where| [where.left.name, where.right] }]
+ Hash[equalities.map { |where| [where.left.name, where.right] }].with_indifferent_access
end
def scope_for_create
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 2fd89882ff..14701f668c 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -59,11 +59,11 @@ module ActiveRecord
relation = apply_finder_options(finder_options)
end
- start = options.delete(:start).to_i
+ start = options.delete(:start)
batch_size = options.delete(:batch_size) || 1000
relation = relation.reorder(batch_order).limit(batch_size)
- records = relation.where(table[primary_key].gteq(start)).all
+ records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
while records.any?
records_size = records.size
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 802059db21..1f9dbdc3d4 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -177,8 +177,15 @@ module ActiveRecord
# Person.where(:confirmed => true).limit(5).pluck(:id)
#
def pluck(column_name)
- column_name = column_name.to_s
- klass.connection.select_all(select(column_name).arel).map! do |attributes|
+ if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
+ column_name = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
+ else
+ column_name = column_name.to_s
+ end
+
+ relation = clone
+ relation.select_values = [column_name]
+ klass.connection.select_all(relation.arel).map! do |attributes|
klass.type_cast_attribute(attributes.keys.first, klass.initialize_attributes(attributes))
end
end
@@ -188,7 +195,8 @@ module ActiveRecord
def perform_calculation(operation, column_name, options = {})
operation = operation.to_s.downcase
- distinct = options[:distinct]
+ # If #count is used in conjuction with #uniq it is considered distinct. (eg. relation.uniq.count)
+ distinct = options[:distinct] || self.uniq_value
if operation == "count"
column_name ||= (select_for_count || :all)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index abc67d9c15..08cfe4f70d 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -253,9 +253,11 @@ module ActiveRecord
orders = relation.order_values.map { |val| val.presence }.compact
values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
- relation = relation.dup
+ relation = relation.dup.select(values)
+
+ id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
+ ids_array = id_rows.map {|row| row[primary_key]}
- ids_array = relation.select(values).collect {|row| row[primary_key]}
ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
end
@@ -263,7 +265,7 @@ module ActiveRecord
conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
result = where(conditions).send(match.finder)
- if match.bang? && result.blank?
+ if match.bang? && result.nil?
raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
else
yield(result) if block_given?
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 236fd5cdfd..413b81cc69 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -20,7 +20,7 @@ module ActiveRecord
table = Arel::Table.new(table_name, engine)
end
- attribute = table[column.to_sym]
+ attribute = table[column]
case value
when ActiveRecord::Relation
@@ -51,10 +51,6 @@ module ActiveRecord
when Class
# FIXME: I think we need to deprecate this behavior
attribute.eq(value.name)
- when Integer, ActiveSupport::Duration
- # Arel treats integers as literals, but they should be quoted when compared with strings
- column = engine.connection.schema_cache.columns_hash[table.name][attribute.name.to_s]
- attribute.eq(Arel::Nodes::SqlLiteral.new(engine.connection.quote(value, column)))
else
attribute.eq(value)
end
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index c25570d758..b8e384c2f3 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -17,14 +17,14 @@ module ActiveRecord
if method == :includes
merged_relation = merged_relation.includes(value)
else
- merged_relation.send(:"#{method}_values=", value)
+ merge_relation_method(merged_relation, method, value)
end
end
end
(Relation::MULTI_VALUE_METHODS - [:joins, :where, :order]).each do |method|
value = r.send(:"#{method}_values")
- merged_relation.send(:"#{method}_values=", merged_relation.send(:"#{method}_values") + value) if value.present?
+ merge_relation_method(merged_relation, method, value) if value.present?
end
merged_relation.joins_values += r.joins_values
@@ -144,5 +144,10 @@ module ActiveRecord
relation
end
+ private
+
+ def merge_relation_method(relation, method, value)
+ relation.send(:"#{method}_values=", relation.send(:"#{method}_values") + value)
+ end
end
end
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 9ceab2eabc..b8d2cd2866 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -26,9 +26,15 @@ module ActiveRecord
private
def hash_rows
- @hash_rows ||= @rows.map { |row|
- Hash[@columns.zip(row)]
- }
+ @hash_rows ||=
+ begin
+ # We freeze the strings to prevent them getting duped when
+ # used as keys in ActiveRecord::Model's @attributes hash
+ columns = @columns.map { |c| c.dup.freeze }
+ @rows.map { |row|
+ Hash[columns.zip(row)]
+ }
+ end
end
end
end
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index d6b0265fb3..767b30f51f 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -161,16 +161,14 @@ module ActiveRecord
# end
#
# def self.titles
- # map(&:title)
+ # pluck(:title)
# end
- #
# end
#
# We are able to call the methods like this:
#
# Article.published.featured.latest_article
# Article.featured.titles
-
def scope(name, scope_options = {})
name = name.to_sym
valid_scope_name?(name)
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 154aacef9f..795088530e 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -26,7 +26,7 @@ module ActiveRecord
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.send(:id))) if record.persisted?
Array.wrap(options[:scope]).each do |scope_item|
- scope_value = record.send(scope_item)
+ scope_value = record.read_attribute(scope_item)
relation = relation.and(table[scope_item].eq(scope_value))
end
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index ff9fa279f4..9069d9964f 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -2,7 +2,7 @@ module ActiveRecord
module VERSION #:nodoc:
MAJOR = 3
MINOR = 2
- TINY = 11
+ TINY = 13
PRE = nil
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 0de3786eb8..6c345cd8cc 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -188,6 +188,12 @@ module ActiveRecord
assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls last"])
end
+ def test_raise_error_when_cannot_translate_exception
+ assert_raise TypeError do
+ @connection.send(:log, nil) { @connection.execute(nil) }
+ end
+ end
+
private
def insert(ctx, data)
binds = data.map { |name, value|
diff --git a/activerecord/test/cases/adapters/postgresql/sql_types_test.rb b/activerecord/test/cases/adapters/postgresql/sql_types_test.rb
new file mode 100644
index 0000000000..d7d40f6385
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/sql_types_test.rb
@@ -0,0 +1,18 @@
+require "cases/helper"
+
+class SqlTypesTest < ActiveRecord::TestCase
+ def test_binary_types
+ assert_equal 'bytea', type_to_sql(:binary, 100_000)
+ assert_raise ActiveRecord::ActiveRecordError do
+ type_to_sql :binary, 4294967295
+ end
+ assert_equal 'text', type_to_sql(:text, 100_000)
+ assert_raise ActiveRecord::ActiveRecordError do
+ type_to_sql :text, 4294967295
+ end
+ end
+
+ def type_to_sql(*args)
+ ActiveRecord::Base.connection.type_to_sql(*args)
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index 575b4806c1..74288a98d1 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -57,6 +57,14 @@ class CopyTableTest < ActiveRecord::TestCase
end
end
+ def test_copy_table_with_unconventional_primary_key
+ test_copy_table('owners', 'owners_unconventional') do |from, to, options|
+ original_pk = @connection.primary_key('owners')
+ copied_pk = @connection.primary_key('owners_unconventional')
+ assert_equal original_pk, copied_pk
+ end
+ end
+
protected
def copy_table(from, to, options = {})
@connection.copy_table(from, to, {:temporary => true}.merge(options))
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index ec69a36174..614be8760a 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -157,7 +157,6 @@ module ActiveRecord
binary = DualEncoding.new :name => 'いただきます!', :data => str
binary.save!
assert_equal str, binary.data
-
ensure
if "<3".respond_to?(:encode)
DualEncoding.connection.drop_table('dual_encodings')
@@ -167,8 +166,9 @@ module ActiveRecord
def test_type_cast_should_not_mutate_encoding
return skip('only test encoding on 1.9') unless "<3".encoding_aware?
- name = 'hello'.force_encoding(Encoding::ASCII_8BIT)
- owner = Owner.create(:name => name)
+ name = 'hello'.force_encoding(Encoding::ASCII_8BIT)
+ Owner.create(:name => name)
+
assert_equal Encoding::ASCII_8BIT, name.encoding
end
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 588adc38e3..6556219205 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -11,23 +11,31 @@ if ActiveRecord::Base.connection.supports_migrations?
def teardown
@connection.drop_table :fruits rescue nil
+ @connection.drop_table :"_p_fruits_s_" rescue nil
+ @connection.drop_table :"_p_schema_migrations_s_" rescue nil
end
def test_schema_define
- 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
+ perform_schema_define!
assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" }
assert_equal 7, ActiveRecord::Migrator::current_version
end
+ def test_schema_define_with_table_prefix_and_suffix
+ # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters
+ ActiveRecord::Base.table_name_prefix = '_p_'
+ ActiveRecord::Base.table_name_suffix = '_s_'
+
+ perform_schema_define!
+
+ assert_equal 7, ActiveRecord::Migrator::current_version
+ ensure
+ ActiveRecord::Base.table_name_prefix = ''
+ ActiveRecord::Base.table_name_suffix = ''
+ end
+
def test_schema_raises_an_error_for_invalid_column_type
assert_raise NoMethodError do
ActiveRecord::Schema.define(:version => 8) do
@@ -39,4 +47,17 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
+ private
+
+ def perform_schema_define!
+ 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
+ end
+
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index e7d1893ff3..c9b26895ae 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -14,6 +14,8 @@ require 'models/sponsor'
require 'models/member'
require 'models/essay'
require 'models/toy'
+require 'models/person'
+require 'models/reader'
class BelongsToAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :topics,
@@ -66,7 +68,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_id_assignment
apple = Firm.create("name" => "Apple")
citibank = Account.create("credit_limit" => 10)
- assert_raise(NoMethodError) { citibank.firm_id = apple }
+ citibank.firm_id = apple
+ assert_nil citibank.firm_id
end
def test_natural_assignment_with_primary_key
@@ -544,6 +547,11 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal new_firm.name, "Apple"
end
+ def test_attributes_are_set_without_error_when_initialized_from_belongs_to_association_with_array_in_where_clause
+ new_account = Account.where(:credit_limit => [ 50, 60 ]).new
+ assert_nil new_account.credit_limit
+ end
+
def test_reassigning_the_parent_id_updates_the_object
client = companies(:second_client)
@@ -710,4 +718,16 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal toy, sponsor.reload.sponsorable
end
+
+ def test_saving_nested_association
+ post1, post2 = Post.limit(2)
+ person = Person.new(:first_name => 'foo')
+ reader = Reader.new(:post => post1)
+
+ reader.post_id = post2.id
+ person.readers = [reader]
+
+ assert person.save
+ assert_equal reader.post_id, post2.id
+ end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index bf01b46852..42061d3d73 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -592,7 +592,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
# gets raw row hashes from the database and then instantiates them, this test ensures that
# it only instantiates one actual object per record from the database.
def test_has_and_belongs_to_many_should_not_instantiate_same_records_multiple_times
- welcome = posts(:welcome)
categories = Category.includes(:posts)
general = categories.find { |c| c == categories(:general) }
@@ -916,6 +915,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal 3, Developer.find(:all, :include => 'projects', :conditions => 'developers_projects.access_level = 1', :limit => 5).size
end
+ def test_dont_create_temporary_active_record_instances
+ Developer.instance_count = 0
+ developers = Developer.find(:all, :include => 'projects', :conditions => 'developers_projects.access_level = 1', :limit => 5).to_a
+ assert_equal developers.count, Developer.instance_count
+ end
+
def test_order_on_join_table_with_include_and_limit
assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size
end
@@ -1107,4 +1112,17 @@ class EagerAssociationTest < ActiveRecord::TestCase
Post.includes(:comments).order(nil).where(:comments => {:body => "Thank you for the welcome"}).first
end
end
+
+ test "preloading does not cache has many association subset when preloaded with a through association" do
+ author = Author.order(:id).includes(:comments_with_order_and_conditions, :posts).first
+ assert_no_queries { assert_equal 2, author.comments_with_order_and_conditions.size }
+ assert_no_queries { assert_equal 5, author.posts.size, "should not cache a subset of the association" }
+ end
+
+ test "preloading a through association twice does not reset it" do
+ members = Member.includes(:current_membership => :club).includes(:club).to_a
+ assert_no_queries {
+ assert_equal 3, members.map(&:current_membership).map(&:club).size
+ }
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index ed4475770b..869ec1e4b8 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1697,6 +1697,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [tagging], post.taggings
end
+ def test_build_with_polymotphic_has_many_does_not_allow_to_override_type_and_id
+ welcome = posts(:welcome)
+ tagging = welcome.taggings.build(:taggable_id => 99, :taggable_type => 'ShouldNotChange')
+
+ assert_equal welcome.id, tagging.taggable_id
+ assert_equal 'Post', tagging.taggable_type
+ end
+
def test_dont_call_save_callbacks_twice_on_has_many
firm = companies(:first_firm)
contract = firm.contracts.create!
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index ffd7f4efa2..277a3bddaa 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -346,6 +346,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_update_counter_caches_on_replace_association
+ post = posts(:welcome)
+ tag = post.tags.create!(:name => 'doomed')
+ tag.tagged_posts << posts(:thinking)
+
+ tag.tagged_posts = []
+ post.reload
+
+ assert_equal(post.taggings.count, post.taggings_count)
+ end
+
def test_replace_association
assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people(true)}
@@ -851,11 +862,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- def test_assign_array_to_new_record_builds_join_records
- c = Category.new(:name => 'Fishing', :authors => [Author.first])
- assert_equal 1, c.categorizations.size
- end
-
def test_create_bang_should_raise_exception_when_join_record_has_errors
repair_validations(Categorization) do
Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' }
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 08831a42ba..31aa3788c7 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -173,6 +173,12 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal account, firm.account
end
+ def test_build_association_dont_create_transaction
+ assert_no_queries {
+ Firm.new.build_account
+ }
+ end
+
def test_build_and_create_should_not_happen_within_scope
pirate = pirates(:blackbeard)
scoped_count = pirate.association(:foo_bulb).scoped.where_values.count
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 073e856e5e..67b5d174aa 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -23,6 +23,8 @@ require 'models/edge'
require 'models/joke'
require 'models/bulb'
require 'models/bird'
+require 'models/car'
+require 'models/bulb'
require 'rexml/document'
require 'active_support/core_ext/exception'
require 'bcrypt'
@@ -323,6 +325,12 @@ class BasicsTest < ActiveRecord::TestCase
assert parrot.valid?
end
+ def test_default_values_are_deeply_dupped
+ company = Company.new
+ company.description << "foo"
+ assert_equal "", Company.new.description
+ end
+
def test_load
topics = Topic.find(:all, :order => 'id')
assert_equal(4, topics.size)
@@ -1293,6 +1301,16 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal({ :foo => :bar }, t.content_before_type_cast)
end
+ def test_serialized_attributes_before_type_cast_returns_unserialized_value
+ Topic.serialize :content, Hash
+
+ t = Topic.new(:content => { :foo => :bar })
+ assert_equal({ :foo => :bar }, t.attributes_before_type_cast["content"])
+ t.save!
+ t.reload
+ assert_equal({ :foo => :bar }, t.attributes_before_type_cast["content"])
+ end
+
def test_serialized_attribute_calling_dup_method
klass = Class.new(ActiveRecord::Base)
klass.table_name = "topics"
@@ -1430,6 +1448,29 @@ class BasicsTest < ActiveRecord::TestCase
Topic.serialize(:content)
end
+ def test_serialize_attribute_via_select_method_when_time_zone_available
+ ActiveRecord::Base.time_zone_aware_attributes = true
+ Topic.serialize(:content, MyObject)
+
+ myobj = MyObject.new('value1', 'value2')
+ topic = Topic.create(:content => myobj)
+
+ ActiveRecord::IdentityMap.without do
+ assert_equal(myobj, Topic.select(:content).find(topic.id).content)
+ assert_raise(ActiveModel::MissingAttributeError) { Topic.select(:id).find(topic.id).content }
+ end
+ ensure
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ end
+
+ def test_serialize_attribute_can_be_serialized_in_an_integer_column
+ insures = ['life']
+ person = SerializedPerson.new(:first_name => 'David', :insures => insures)
+ assert person.save
+ person = person.reload
+ assert_equal(insures, person.insures)
+ end
+
def test_quote
author_name = "\\ \001 ' \n \\n \""
topic = Topic.create('author_name' => author_name)
@@ -1499,6 +1540,16 @@ class BasicsTest < ActiveRecord::TestCase
end
end
+ def test_dont_clear_inheritnce_column_when_setting_explicitly
+ Joke.inheritance_column = "my_type"
+ before_inherit = Joke.inheritance_column
+
+ Joke.reset_column_information
+ after_inherit = Joke.inheritance_column
+
+ assert_equal before_inherit, after_inherit unless before_inherit.blank? && after_inherit.blank?
+ end
+
def test_set_table_name_symbol_converted_to_string
Joke.table_name = :cold_jokes
assert_equal 'cold_jokes', Joke.table_name
@@ -2113,7 +2164,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_attribute_names
- assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id"],
+ assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id", "description"],
Company.attribute_names
end
@@ -2144,6 +2195,29 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key
end
+ def test_cache_key_format_for_existing_record_with_updated_at_and_custom_cache_timestamp_format
+ return skip "Only in Ruby 1.9" if RUBY_VERSION < '1.9'
+
+ dev = CachedDeveloper.first
+ assert_equal "cached_developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key
+ end
+
+ def test_cache_key_changes_when_child_touched
+ return skip "Only in Ruby 1.9" if RUBY_VERSION < '1.9'
+
+ old_timestamp_format = Car.cache_timestamp_format
+ Car.cache_timestamp_format = :nsec
+ car = Car.create
+ Bulb.create(:car => car)
+
+ key = car.cache_key
+ car.bulb.touch
+ car.reload
+ assert_not_equal key, car.cache_key
+ ensure
+ Car.cache_timestamp_format = old_timestamp_format
+ end
+
def test_cache_key_format_for_existing_record_with_nil_updated_at
dev = Developer.first
dev.update_attribute(:updated_at, nil)
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 660098b9ad..ad2a749ab4 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -1,8 +1,9 @@
require 'cases/helper'
require 'models/post'
+require 'models/subscriber'
class EachTest < ActiveRecord::TestCase
- fixtures :posts
+ fixtures :posts, :subscribers
def setup
@posts = Post.order("id asc")
@@ -136,4 +137,24 @@ class EachTest < ActiveRecord::TestCase
assert_equal special_posts_ids, posts.map(&:id)
end
+ def test_find_in_batches_should_use_any_column_as_primary_key
+ nick_order_subscribers = Subscriber.order('nick asc')
+ start_nick = nick_order_subscribers.second.nick
+
+ subscribers = []
+ Subscriber.find_in_batches(:batch_size => 1, :start => start_nick) do |batch|
+ subscribers.concat(batch)
+ end
+
+ assert_equal nick_order_subscribers[1..-1].map(&:id), subscribers.map(&:id)
+ end
+
+ def test_find_in_batches_should_use_any_column_as_primary_key_when_start_is_not_specified
+ Subscriber.count('nick') # preheat arel's table cache
+ assert_queries(Subscriber.count + 1) do
+ Subscriber.find_each(:batch_size => 1) do |subscriber|
+ assert_kind_of Subscriber, subscriber
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 3652255c38..32726e8d8b 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -30,8 +30,7 @@ module ActiveRecord
end
def test_binds_are_logged
- # FIXME: use skip with minitest
- return unless @connection.supports_statement_cache?
+ return skip_bind_parameter_test unless supports_statement_cache?
sub = @connection.substitute_at(@pk, 0)
binds = [[@pk, 1]]
@@ -44,8 +43,7 @@ module ActiveRecord
end
def test_find_one_uses_binds
- # FIXME: use skip with minitest
- return unless @connection.supports_statement_cache?
+ return skip_bind_parameter_test unless supports_statement_cache?
Topic.find(1)
binds = [[@pk, 1]]
@@ -54,8 +52,7 @@ module ActiveRecord
end
def test_logs_bind_vars
- # FIXME: use skip with minitest
- return unless @connection.supports_statement_cache?
+ return skip_bind_parameter_test unless supports_statement_cache?
pk = Topic.columns.find { |x| x.primary }
@@ -86,5 +83,15 @@ module ActiveRecord
logger.sql event
assert_match([[pk.name, 10]].inspect, logger.debugs.first)
end
+
+ private
+
+ def skip_bind_parameter_test
+ skip('prepared statement caching is not supported')
+ end
+
+ def supports_statement_cache?
+ @connection.supports_statement_cache?
+ end
end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index cf1181e829..a1dc1de38d 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -1,10 +1,11 @@
require "cases/helper"
+require 'models/club'
require 'models/company'
require "models/contract"
-require 'models/topic'
require 'models/edge'
-require 'models/club'
require 'models/organization'
+require 'models/possession'
+require 'models/topic'
Company.has_many :accounts
@@ -368,6 +369,10 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 5, Account.count(:firm_id)
end
+ def test_count_with_uniq
+ assert_equal 4, Account.select(:credit_limit).uniq.count
+ end
+
def test_count_with_column_and_options_parameter
assert_equal 2, Account.count(:firm_id, :conditions => "credit_limit = 50 AND firm_id IS NOT NULL")
end
@@ -487,4 +492,26 @@ class CalculationsTest < ActiveRecord::TestCase
def test_pluck_with_qualified_column_name
assert_equal [1,2,3,4], Topic.order(:id).pluck("topics.id")
end
+
+ def test_pluck_replaces_select_clause
+ taks_relation = Topic.select([:approved, :id]).order(:id)
+ assert_equal [1,2,3,4], taks_relation.pluck(:id)
+ assert_equal [false, true, true, true], taks_relation.pluck(:approved)
+ end
+
+ def test_pluck_auto_table_name_prefix
+ c = Company.create!(:name => "test", :contracts => [Contract.new])
+ assert_equal [c.id], Company.joins(:contracts).pluck(:id)
+ end
+
+ def test_pluck_not_auto_table_name_prefix_if_column_joined
+ Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+ assert_equal [7], Company.joins(:contracts).pluck(:developer_id).map(&:to_i)
+ end
+
+ def test_pluck_with_reserved_words
+ Possession.create!(:where => "Over There")
+
+ assert_equal ["Over There"], Possession.pluck(:where)
+ end
end
diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb
index b1c1165bd9..800cfbdef9 100644
--- a/activerecord/test/cases/column_test.rb
+++ b/activerecord/test/cases/column_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'models/company'
module ActiveRecord
module ConnectionAdapters
@@ -40,12 +41,27 @@ module ActiveRecord
def test_type_cast_non_integer_to_integer
column = Column.new("field", nil, "integer")
- assert_raises(NoMethodError) do
- column.type_cast([])
- end
+ assert_nil column.type_cast([1,2])
+ assert_nil column.type_cast({1 => 2})
+ assert_nil column.type_cast((1..2))
+ end
+
+ def test_type_cast_activerecord_to_integer
+ column = Column.new("field", nil, "integer")
+ firm = Firm.create(:name => 'Apple')
+ assert_nil column.type_cast(firm)
+ end
+
+ def test_type_cast_object_without_to_i_to_integer
+ column = Column.new("field", nil, "integer")
+ assert_nil column.type_cast(Object.new)
+ end
- assert_raises(NoMethodError) do
- column.type_cast(Object.new)
+ if RUBY_VERSION > '1.9'
+ def test_type_cast_nan_and_infinity_to_integer
+ column = Column.new("field", nil, "integer")
+ assert_nil column.type_cast(Float::NAN)
+ assert_nil column.type_cast(1.0/0.0)
end
end
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 4fc7e77786..9756c182b9 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -76,6 +76,8 @@ class DirtyTest < ActiveRecord::TestCase
assert pirate.created_on_changed?
assert_kind_of ActiveSupport::TimeWithZone, pirate.created_on_was
assert_equal old_created_on, pirate.created_on_was
+ pirate.created_on = old_created_on
+ assert !pirate.created_on_changed?
end
end
@@ -201,6 +203,20 @@ class DirtyTest < ActiveRecord::TestCase
end
end
+ def test_nullable_datetime_not_marked_as_changed_if_new_value_is_blank
+ in_time_zone 'Edinburgh' do
+ target = Class.new(ActiveRecord::Base)
+ target.table_name = 'topics'
+
+ topic = target.create
+ assert_equal nil, topic.written_on
+
+ topic.written_on = ""
+ assert_equal nil, topic.written_on
+ assert !topic.written_on_changed?
+ end
+ end
+
def test_integer_zero_to_string_zero_not_marked_as_changed
pirate = Pirate.new
pirate.parrot_id = 0
@@ -535,18 +551,17 @@ class DirtyTest < ActiveRecord::TestCase
end
end
- def test_setting_time_attributes_with_time_zone_field_to_same_time_should_not_be_marked_as_a_change
+ def test_datetime_attribute_can_be_updated_with_fractional_seconds
in_time_zone 'Paris' do
target = Class.new(ActiveRecord::Base)
- target.table_name = 'pirates'
+ target.table_name = 'topics'
- created_on = Time.now
+ written_on = Time.utc(2012, 12, 1, 12, 0, 0).in_time_zone('Paris')
- pirate = target.create(:created_on => created_on)
- pirate.reload # Here mysql truncate the usec value to 0
+ topic = target.create(:written_on => written_on)
+ topic.written_on += 0.3
- pirate.created_on = created_on
- assert !pirate.created_on_changed?
+ assert topic.written_on_changed?, 'Fractional second update not detected'
end
end
diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb
index b2a3cb5733..17a02f139a 100644
--- a/activerecord/test/cases/dup_test.rb
+++ b/activerecord/test/cases/dup_test.rb
@@ -113,5 +113,14 @@ module ActiveRecord
assert topic.invalid?
assert duped.valid?
end
+
+ def test_dup_with_default_scope
+ prev_default_scopes = Topic.default_scopes
+ Topic.default_scopes = [Topic.where(:approved => true)]
+ topic = Topic.new(:approved => false)
+ assert !topic.dup.approved?, "should not be overriden by default scopes"
+ ensure
+ Topic.default_scopes = prev_default_scopes
+ end
end
end
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index cb7781f8e7..bdeb0a033c 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -98,6 +98,16 @@ if ActiveRecord::Base.connection.supports_explain?
assert_equal expected, base.exec_explain(queries)
end
+ def test_unsupported_connection_adapter
+ connection.stubs(:supports_explain?).returns(false)
+
+ base.logger.expects(:warn).never
+
+ with_threshold(0) do
+ Car.where(:name => 'honda').to_a
+ end
+ end
+
def test_silence_auto_explain
base.expects(:collecting_sqls_for_explain).never
base.logger.expects(:warn).never
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 77ca3b574d..7d63d76c34 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -664,6 +664,11 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
end
+ def test_find_by_one_attribute_bang_with_blank_defined
+ blank_topic = BlankTopic.create(:title => "The Blank One")
+ assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One")
+ end
+
def test_find_by_one_attribute_with_order_option
assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC')
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index e352a55104..cf49b125d7 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -4,7 +4,7 @@ require 'config'
require 'test/unit'
require 'stringio'
-require 'mocha'
+require 'mocha/setup'
require 'active_record'
require 'active_support/dependencies'
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 015a3ccefd..066a60f81a 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -7,6 +7,7 @@ require 'models/ship'
require 'models/legacy_thing'
require 'models/reference'
require 'models/string_key_object'
+require 'models/treasure'
class LockWithoutDefault < ActiveRecord::Base; end
@@ -20,7 +21,7 @@ class ReadonlyNameShip < Ship
end
class OptimisticLockingTest < ActiveRecord::TestCase
- fixtures :people, :legacy_things, :references, :string_key_objects
+ fixtures :people, :legacy_things, :references, :string_key_objects, :peoples_treasures
def test_non_integer_lock_existing
s1 = StringKeyObject.find("record1")
@@ -267,6 +268,15 @@ class SetLockingColumnTest < ActiveRecord::TestCase
assert_equal "omg", k.original_locking_column
end
end
+
+ def test_removing_has_and_belongs_to_many_associations_upon_destroy
+ p = RichPerson.create! :first_name => 'Jon'
+ p.treasures.create!
+ assert !p.treasures.empty?
+ p.destroy
+ assert p.treasures.empty?
+ assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty?
+ end
end
class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index e24a5ca5aa..10ee86de4a 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -7,6 +7,19 @@ class LogSubscriberTest < ActiveRecord::TestCase
include ActiveSupport::LogSubscriber::TestHelper
include ActiveSupport::BufferedLogger::Severity
+ class TestDebugLogSubscriber < ActiveRecord::LogSubscriber
+ attr_reader :debugs
+
+ def initialize
+ @debugs = []
+ super
+ end
+
+ def debug message
+ @debugs << message
+ end
+ end
+
fixtures :posts
def setup
@@ -32,18 +45,7 @@ class LogSubscriberTest < ActiveRecord::TestCase
def test_schema_statements_are_ignored
event = Struct.new(:duration, :payload)
- logger = Class.new(ActiveRecord::LogSubscriber) {
- attr_accessor :debugs
-
- def initialize
- @debugs = []
- super
- end
-
- def debug message
- @debugs << message
- end
- }.new
+ logger = TestDebugLogSubscriber.new
assert_equal 0, logger.debugs.length
logger.sql(event.new(0, { :sql => 'hi mom!' }))
@@ -56,6 +58,14 @@ class LogSubscriberTest < ActiveRecord::TestCase
assert_equal 2, logger.debugs.length
end
+ def test_ignore_binds_payload_with_nil_column
+ event = Struct.new(:duration, :payload)
+
+ logger = TestDebugLogSubscriber.new
+ logger.sql(event.new(0, :sql => 'hi mom!', :binds => [[nil, 1]]))
+ assert_equal 1, logger.debugs.length
+ end
+
def test_basic_query_logging
Developer.all
wait
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 0ab4f30363..ac84306eae 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -212,14 +212,14 @@ class MethodScopingTest < ActiveRecord::TestCase
table = VerySpecialComment.arel_table
relation = VerySpecialComment.scoped
relation.where_values << table[:id].not_eq(1)
- assert_equal({:type => "VerySpecialComment"}, relation.send(:scope_for_create))
+ assert_equal({'type' => "VerySpecialComment"}, relation.send(:scope_for_create))
end
def test_scoped_create
new_comment = nil
VerySpecialComment.send(:with_scope, :create => { :post_id => 1 }) do
- assert_equal({:post_id => 1, :type => 'VerySpecialComment' }, VerySpecialComment.scoped.send(:scope_for_create))
+ assert_equal({'post_id' => 1, 'type' => 'VerySpecialComment' }, VerySpecialComment.scoped.send(:scope_for_create))
new_comment = VerySpecialComment.create :body => "Wonderful world"
end
@@ -228,7 +228,7 @@ class MethodScopingTest < ActiveRecord::TestCase
def test_scoped_create_with_join_and_merge
Comment.where(:body => "but Who's Buying?").joins(:post).merge(Post.where(:body => 'Peace Sells...')).with_scope do
- assert_equal({:body => "but Who's Buying?"}, Comment.scoped.scope_for_create)
+ assert_equal({'body' => "but Who's Buying?"}, Comment.scoped.scope_for_create)
end
end
@@ -441,7 +441,7 @@ class NestedScopingTest < ActiveRecord::TestCase
comment = nil
Comment.send(:with_scope, :create => { :post_id => 1}) do
Comment.send(:with_scope, :create => { :post_id => 2}) do
- assert_equal({:post_id => 2}, Comment.scoped.send(:scope_for_create))
+ assert_equal({'post_id' => 2}, Comment.scoped.send(:scope_for_create))
comment = Comment.create :body => "Hey guys, nested scopes are broken. Please fix!"
end
end
@@ -453,7 +453,7 @@ class NestedScopingTest < ActiveRecord::TestCase
Comment.send(:with_scope, :create => { :body => "Hey guys, nested scopes are broken. Please fix!" }) do
Comment.send(:with_exclusive_scope, :create => { :post_id => 1 }) do
- assert_equal({:post_id => 1}, Comment.scoped.send(:scope_for_create))
+ assert_equal({'post_id' => 1}, Comment.scoped.send(:scope_for_create))
assert_blank Comment.new.body
comment = Comment.create :body => "Hey guys"
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index f0c55b1535..7f0d921545 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -61,7 +61,7 @@ if ActiveRecord::Base.connection.supports_migrations?
ActiveRecord::Base.connection.initialize_schema_migrations_table
ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}"
- %w(things awesome_things prefix_things_suffix prefix_awesome_things_suffix).each do |table|
+ %w(things awesome_things prefix_things_suffix p_awesome_things_s).each do |table|
Thing.connection.drop_table(table) rescue nil
end
Thing.reset_column_information
@@ -874,8 +874,6 @@ if ActiveRecord::Base.connection.supports_migrations?
end
def test_remove_column_with_array_as_an_argument_is_deprecated
- return skip "remove_column with array as argument is not supported with OracleAdapter" if current_adapter? :OracleAdapter
-
ActiveRecord::Base.connection.create_table(:hats) do |table|
table.column :hat_name, :string, :limit => 100
table.column :hat_size, :integer
@@ -886,7 +884,21 @@ if ActiveRecord::Base.connection.supports_migrations?
Person.connection.remove_column("hats", ["hat_name", "hat_size"])
end
ensure
- ActiveRecord::Base.connection.drop_table(:hats) rescue nil
+ ActiveRecord::Base.connection.drop_table(:hats)
+ end
+
+ def test_removing_and_renaming_column_preserves_custom_primary_key
+ ActiveRecord::Base.connection.create_table "my_table", :primary_key => "my_table_id", :force => true do |t|
+ t.integer "col_one"
+ t.string "col_two", :limit => 128, :null => false
+ end
+
+ ActiveRecord::Base.connection.remove_column("my_table", "col_two")
+ ActiveRecord::Base.connection.rename_column("my_table", "col_one", "col_three")
+
+ assert_equal 'my_table_id', ActiveRecord::Base.connection.primary_key('my_table')
+ ensure
+ ActiveRecord::Base.connection.drop_table(:my_table) rescue nil
end
def test_change_type_of_not_null_column
@@ -1051,6 +1063,18 @@ if ActiveRecord::Base.connection.supports_migrations?
Person.connection.remove_column("people", "administrator") rescue nil
end
+ def test_change_column_with_custom_index_name
+ Person.connection.add_column "people", "category", :string, :default => 'human'
+ Person.connection.add_index :people, :category, :name => 'people_categories_idx'
+
+ assert_equal ['people_categories_idx'], Person.connection.indexes('people').map(&:name)
+ Person.connection.change_column "people", "category", :string, :null => false, :default => 'article'
+
+ assert_equal ['people_categories_idx'], Person.connection.indexes('people').map(&:name)
+ ensure
+ Person.connection.remove_column("people", "category") rescue nil
+ end
+
def test_change_column_default
Person.connection.change_column_default "people", "first_name", "Tester"
Person.reset_column_information
@@ -1430,6 +1454,12 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
+ def test_finds_migrations_in_numbered_directory
+ migrations = ActiveRecord::Migrator.migrations [MIGRATIONS_ROOT + '/10_urban']
+ assert_equal 9, migrations[0].version
+ assert_equal 'AddExpressions', migrations[0].name
+ end
+
def test_dump_schema_information_outputs_lexically_ordered_versions
migration_path = MIGRATIONS_ROOT + '/valid_with_timestamps'
ActiveRecord::Migrator.run(:up, migration_path, 20100301010101)
@@ -1615,8 +1645,8 @@ if ActiveRecord::Base.connection.supports_migrations?
def test_rename_table_with_prefix_and_suffix
assert !Thing.table_exists?
- ActiveRecord::Base.table_name_prefix = 'prefix_'
- ActiveRecord::Base.table_name_suffix = '_suffix'
+ ActiveRecord::Base.table_name_prefix = 'p_'
+ ActiveRecord::Base.table_name_suffix = '_s'
Thing.reset_table_name
Thing.reset_sequence_name
WeNeedThings.up
@@ -1625,7 +1655,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal "hello world", Thing.find(:first).content
RenameThings.up
- Thing.table_name = "prefix_awesome_things_suffix"
+ Thing.table_name = "p_awesome_things_s"
assert_equal "hello world", Thing.find(:first).content
ensure
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 16b1eb040e..85b9d3c1a1 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -172,6 +172,17 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
man.interests_attributes = [{:id => interest.id, :topic => 'gardening'}]
assert_equal man.interests.first.topic, man.interests[0].topic
end
+
+ def test_accepts_nested_attributes_for_can_be_overridden_in_subclasses
+ Pirate.accepts_nested_attributes_for(:parrot)
+
+ mean_pirate_class = Class.new(Pirate) do
+ accepts_nested_attributes_for :parrot
+ end
+ mean_pirate = mean_pirate_class.new
+ mean_pirate.parrot_attributes = { :name => "James" }
+ assert_equal "James", mean_pirate.parrot.name
+ end
end
class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index a35cb3c4a5..db1ba3549f 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -394,7 +394,6 @@ class PersistencesTest < ActiveRecord::TestCase
def test_update_attribute_with_one_updated
t = Topic.first
- title = t.title
t.update_attribute(:title, 'super_title')
assert_equal 'super_title', t.title
assert !t.changed?, "topic should not have changed"
@@ -492,7 +491,7 @@ class PersistencesTest < ActiveRecord::TestCase
def test_update_column_with_one_changed_and_one_updated
t = Topic.order('id').limit(1).first
- title, author_name = t.title, t.author_name
+ author_name = t.author_name
t.author_name = 'John'
t.update_column(:title, 'super_title')
assert_equal 'John', t.author_name
@@ -519,6 +518,14 @@ class PersistencesTest < ActiveRecord::TestCase
assert return_value
end
+ def test_update_column_with_default_scope
+ developer = DeveloperCalledDavid.first
+ developer.name = 'John'
+ developer.save!
+
+ assert developer.update_column(:name, 'Will'), 'did not update record due to default scope'
+ end
+
def test_update_attributes
topic = Topic.find(1)
assert !topic.approved?
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index dd881f8230..dfc6ea2457 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -173,6 +173,17 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_queries(2) { task.lock!; task.lock! }
end
end
+
+ def test_cache_is_available_when_connection_is_connected
+ conf = ActiveRecord::Base.configurations
+
+ ActiveRecord::Base.configurations = {}
+ Task.cache do
+ assert_queries(1) { Task.find(1); Task.find(1) }
+ end
+ ensure
+ ActiveRecord::Base.configurations = conf
+ end
end
class QueryCacheExpiryTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index babeaec5f6..80ee74e41e 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -122,35 +122,35 @@ module ActiveRecord
def test_quote_float
float = 1.2
assert_equal float.to_s, @quoter.quote(float, nil)
- assert_equal float.to_s, @quoter.quote(float, FakeColumn.new(:float))
+ assert_equal float.to_s, @quoter.quote(float, Object.new)
end
def test_quote_fixnum
fixnum = 1
assert_equal fixnum.to_s, @quoter.quote(fixnum, nil)
- assert_equal fixnum.to_s, @quoter.quote(fixnum, FakeColumn.new(:integer))
+ assert_equal fixnum.to_s, @quoter.quote(fixnum, Object.new)
end
def test_quote_bignum
bignum = 1 << 100
assert_equal bignum.to_s, @quoter.quote(bignum, nil)
- assert_equal bignum.to_s, @quoter.quote(bignum, FakeColumn.new(:integer))
+ assert_equal bignum.to_s, @quoter.quote(bignum, Object.new)
end
def test_quote_bigdecimal
bigdec = BigDecimal.new((1 << 100).to_s)
assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, nil)
- assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, FakeColumn.new(:decimal))
+ assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, Object.new)
end
def test_dates_and_times
@quoter.extend(Module.new { def quoted_date(value) 'lol' end })
assert_equal "'lol'", @quoter.quote(Date.today, nil)
- assert_equal "'lol'", @quoter.quote(Date.today, FakeColumn.new(:date))
+ assert_equal "'lol'", @quoter.quote(Date.today, Object.new)
assert_equal "'lol'", @quoter.quote(Time.now, nil)
- assert_equal "'lol'", @quoter.quote(Time.now, FakeColumn.new(:time))
+ assert_equal "'lol'", @quoter.quote(Time.now, Object.new)
assert_equal "'lol'", @quoter.quote(DateTime.now, nil)
- assert_equal "'lol'", @quoter.quote(DateTime.now, FakeColumn.new(:datetime))
+ assert_equal "'lol'", @quoter.quote(DateTime.now, Object.new)
end
def test_crazy_object
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index ba9df1651b..80158332f9 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -35,30 +35,5 @@ module ActiveRecord
def test_where_with_empty_hash_and_no_foreign_key
assert_equal 0, Edge.where(:sink => {}).count
end
-
- def test_where_with_integer_for_string_column
- count = Post.where(:title => 0).count
- assert_equal 0, count
- end
-
- def test_where_with_float_for_string_column
- count = Post.where(:title => 0.0).count
- assert_equal 0, count
- end
-
- def test_where_with_boolean_for_string_column
- count = Post.where(:title => false).count
- assert_equal 0, count
- end
-
- def test_where_with_decimal_for_string_column
- count = Post.where(:title => BigDecimal.new('0')).count
- assert_equal 0, count
- end
-
- def test_where_with_duration_for_string_column
- count = Post.where(:title => 0.seconds).count
- assert_equal 0, count
- end
end
end
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index aec2487aee..f33e765c59 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -380,19 +380,19 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_default_scope_with_inheritance
wheres = InheritedPoorDeveloperCalledJamis.scoped.where_values_hash
assert_equal "Jamis", wheres[:name]
- assert_equal Arel.sql("50000"), wheres[:salary]
+ assert_equal 50000, wheres[:salary]
end
def test_default_scope_with_module_includes
wheres = ModuleIncludedPoorDeveloperCalledJamis.scoped.where_values_hash
assert_equal "Jamis", wheres[:name]
- assert_equal Arel.sql("50000"), wheres[:salary]
+ assert_equal 50000, wheres[:salary]
end
def test_default_scope_with_multiple_calls
wheres = MultiplePoorDeveloperCalledJamis.scoped.where_values_hash
assert_equal "Jamis", wheres[:name]
- assert_equal Arel.sql("50000"), wheres[:salary]
+ assert_equal 50000, wheres[:salary]
end
def test_method_scope
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 7a75a8436b..6efdeac3d9 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -71,7 +71,7 @@ module ActiveRecord
def test_has_values
relation = Relation.new Post, Post.arel_table
relation.where_values << relation.table[:id].eq(10)
- assert_equal({:id => 10}, relation.where_values_hash)
+ assert_equal({'id' => 10}, relation.where_values_hash)
end
def test_values_wrong_table
@@ -101,7 +101,7 @@ module ActiveRecord
def test_create_with_value
relation = Relation.new Post, Post.arel_table
- hash = { :hello => 'world' }
+ hash = { 'hello' => 'world' }
relation.create_with_value = hash
assert_equal hash, relation.scope_for_create
end
@@ -110,7 +110,7 @@ module ActiveRecord
relation = Relation.new Post, Post.arel_table
relation.where_values << relation.table[:id].eq(10)
relation.create_with_value = {:hello => 'world'}
- assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create)
+ assert_equal({'hello' => 'world', 'id' => 10}, relation.scope_for_create)
end
# FIXME: is this really wanted or expected behavior?
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index b24df47efd..98fe6b7611 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -332,6 +332,13 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_preload_applies_to_all_chained_preloaded_scopes
+ assert_queries(3) do
+ post = Post.with_tags.with_comments.first
+ assert post
+ end
+ end
+
def test_find_with_included_associations
assert_queries(2) do
posts = Post.includes(:comments).order('posts.id')
@@ -1185,4 +1192,18 @@ class RelationTest < ActiveRecord::TestCase
end
assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name)
end
+
+ test 'group with select and includes' do
+ authors_count = Post.select('author_id, COUNT(author_id) AS num_posts').
+ group('author_id').order('author_id').includes(:author).to_a
+
+ assert_no_queries do
+ result = authors_count.map do |post|
+ [post.num_posts.to_i, post.author.try(:name)]
+ end
+
+ expected = [[1, nil], [5, "David"], [3, "Mary"], [2, "Bob"]]
+ assert_equal expected, result
+ end
+ end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 8708117129..0f9cb10f0f 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -22,6 +22,14 @@ end
class Thaumaturgist < IneptWizard
end
+class ReplyTitle; end
+
+class ReplyWithTitleObject < Reply
+ validates_uniqueness_of :content, :scope => :title
+
+ def title; ReplyTitle.new; end
+end
+
class UniquenessValidationTest < ActiveRecord::TestCase
fixtures :topics, 'warehouse-things', :developers
@@ -80,6 +88,14 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert r3.valid?, "Saving r3"
end
+ def test_validate_uniqueness_with_composed_attribute_scope
+ r1 = ReplyWithTitleObject.create "title" => "r1", "content" => "hello world"
+ assert r1.valid?, "Saving r1"
+
+ r2 = ReplyWithTitleObject.create "title" => "r1", "content" => "hello world"
+ assert !r2.valid?, "Saving r2 first time"
+ end
+
def test_validate_uniqueness_scoped_to_defining_class
t = Topic.create("title" => "What, me worry?")
diff --git a/activerecord/test/fixtures/peoples_treasures.yml b/activerecord/test/fixtures/peoples_treasures.yml
new file mode 100644
index 0000000000..a72b190d0c
--- /dev/null
+++ b/activerecord/test/fixtures/peoples_treasures.yml
@@ -0,0 +1,3 @@
+michael_diamond:
+ rich_person_id: <%= ActiveRecord::Fixtures.identify(:michael) %>
+ treasure_id: <%= ActiveRecord::Fixtures.identify(:diamond) %>
diff --git a/activerecord/test/migrations/10_urban/9_add_expressions.rb b/activerecord/test/migrations/10_urban/9_add_expressions.rb
new file mode 100644
index 0000000000..79a342e574
--- /dev/null
+++ b/activerecord/test/migrations/10_urban/9_add_expressions.rb
@@ -0,0 +1,11 @@
+class AddExpressions < ActiveRecord::Migration
+ def self.up
+ create_table("expressions") do |t|
+ t.column :expression, :string
+ end
+ end
+
+ def self.down
+ drop_table "expressions"
+ end
+end
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index 888afc7604..ab3caa3f79 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -1,6 +1,6 @@
class Bulb < ActiveRecord::Base
default_scope where(:name => 'defaulty')
- belongs_to :car
+ belongs_to :car, :touch => true
attr_protected :car_id, :frickinawesome
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 4dc9fff9fd..f8b758b3c2 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -63,6 +63,15 @@ class Developer < ActiveRecord::Base
self.all
end
end
+
+ after_find :track_instance_count
+ cattr_accessor :instance_count
+
+ def track_instance_count
+ self.class.instance_count ||= 0
+ self.class.instance_count += 1
+ end
+ private :track_instance_count
end
class AuditLog < ActiveRecord::Base
@@ -236,3 +245,8 @@ class ThreadsafeDeveloper < ActiveRecord::Base
limit(1)
end
end
+
+class CachedDeveloper < ActiveRecord::Base
+ self.table_name = "developers"
+ self.cache_timestamp_format = :nsec
+end
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 5b92227f4a..07c529d685 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -86,3 +86,30 @@ class TightPerson < ActiveRecord::Base
end
class TightDescendant < TightPerson; end
+
+class RichPerson < ActiveRecord::Base
+ self.table_name = 'people'
+
+ has_and_belongs_to_many :treasures, :join_table => 'peoples_treasures'
+end
+
+class Insure
+ INSURES = %W{life annuality}
+
+ def self.load mask
+ INSURES.select do |insure|
+ (1 << INSURES.index(insure)) & mask.to_i > 0
+ end
+ end
+
+ def self.dump insures
+ numbers = insures.map { |insure| INSURES.index(insure) }
+ numbers.inject(0) { |sum, n| sum + (1 << n) }
+ end
+end
+
+class SerializedPerson < ActiveRecord::Base
+ self.table_name = 'people'
+
+ serialize :insures, Insure
+end
diff --git a/activerecord/test/models/possession.rb b/activerecord/test/models/possession.rb
new file mode 100644
index 0000000000..ddf759113b
--- /dev/null
+++ b/activerecord/test/models/possession.rb
@@ -0,0 +1,3 @@
+class Possession < ActiveRecord::Base
+ self.table_name = 'having'
+end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 9aa02fa18f..3cfd1a0f76 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -76,6 +76,9 @@ class Post < ActiveRecord::Base
end
end
+ scope :with_comments, preload(:comments)
+ scope :with_tags, preload(:taggings)
+
has_many :interpolated_taggings, :class_name => 'Tagging', :as => :taggable, :conditions => proc { "1 = #{1}" }
has_many :interpolated_tags, :through => :taggings
has_many :interpolated_tags_2, :through => :interpolated_taggings, :source => :tag
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 1a1a18166a..5166fefe81 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -112,6 +112,12 @@ class ImportantTopic < Topic
serialize :important, Hash
end
+class BlankTopic < Topic
+ def blank?
+ true
+ end
+end
+
module Web
class Topic < ActiveRecord::Base
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id", :class_name => 'Web::Reply'
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 34e24b5a2e..b2c655ddcd 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -132,5 +132,10 @@ _SQL
_SQL
rescue #This version of PostgreSQL either has no XML support or is was not compiled with XML support: skipping table
end
+
+ create_table :limitless_fields, :force => true do |t|
+ t.binary :binary, :limit => 100_000
+ t.text :text, :limit => 100_000
+ end
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index f04dc72cdd..ab62fb82b6 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -112,6 +112,7 @@ ActiveRecord::Schema.define do
t.string :name
t.integer :engines_count
t.integer :wheels_count
+ t.timestamps
end
create_table :categories, :force => true do |t|
@@ -175,6 +176,7 @@ ActiveRecord::Schema.define do
t.integer :client_of
t.integer :rating, :default => 1
t.integer :account_id
+ t.string :description, :default => ""
end
add_index :companies, [:firm_id, :type, :rating, :ruby_type], :name => "company_index"
@@ -280,6 +282,10 @@ ActiveRecord::Schema.define do
t.string :info
end
+ create_table :having, :force => true do |t|
+ t.string :where
+ end
+
create_table :guids, :force => true do |t|
t.column :key, :string
end
@@ -478,9 +484,15 @@ ActiveRecord::Schema.define do
t.integer :followers_count, :default => 0
t.references :best_friend
t.references :best_friend_of
+ t.integer :insures, :null => false, :default => 0
t.timestamps
end
+ create_table :peoples_treasures, :id => false, :force => true do |t|
+ t.column :rich_person_id, :integer
+ t.column :treasure_id, :integer
+ end
+
create_table :pets, :primary_key => :pet_id ,:force => true do |t|
t.string :name
t.integer :owner_id, :integer
@@ -518,8 +530,6 @@ ActiveRecord::Schema.define do
create_table :price_estimates, :force => true do |t|
t.string :estimate_of_type
t.integer :estimate_of_id
- t.string :thing_type
- t.integer :thing_id
t.integer :price
end