diff options
Diffstat (limited to 'activerecord')
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 |