aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorNeeraj Singh <neerajdotname@gmail.com>2010-11-22 17:32:27 -0500
committerNeeraj Singh <neerajdotname@gmail.com>2010-11-22 17:32:27 -0500
commit5debc65c356c619bce20dcc10c0befc7c4204ef5 (patch)
treedc32bab1eccd11bacdaff70f25696689a2b9dc66 /activerecord
parent1c68e55ae5e90b7c072a1f6030ea3dd4becd8a07 (diff)
parent818b366c3e3645b3375ee0b1d1023621dbeaede2 (diff)
downloadrails-5debc65c356c619bce20dcc10c0befc7c4204ef5.tar.gz
rails-5debc65c356c619bce20dcc10c0befc7c4204ef5.tar.bz2
rails-5debc65c356c619bce20dcc10c0befc7c4204ef5.zip
Merge branch 'master' of github.com:lifo/docrails
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG27
-rw-r--r--activerecord/lib/active_record/association_preload.rb26
-rw-r--r--activerecord/lib/active_record/associations.rb13
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb31
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb6
-rw-r--r--activerecord/lib/active_record/base.rb43
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb5
-rw-r--r--activerecord/lib/active_record/migration.rb293
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb91
-rw-r--r--activerecord/lib/active_record/named_scope.rb10
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb13
-rw-r--r--activerecord/lib/active_record/reflection.rb20
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb39
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb5
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb28
-rw-r--r--activerecord/lib/active_record/schema.rb9
-rw-r--r--activerecord/lib/active_record/timestamp.rb8
-rw-r--r--activerecord/lib/active_record/version.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/migration.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record/session_migration/templates/migration.rb4
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb3
-rw-r--r--activerecord/test/cases/associations/eager_test.rb52
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb21
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations_test.rb10
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb2
-rw-r--r--activerecord/test/cases/autosave_association_test.rb6
-rw-r--r--activerecord/test/cases/base_test.rb12
-rw-r--r--activerecord/test/cases/calculations_test.rb13
-rw-r--r--activerecord/test/cases/finder_test.rb2
-rw-r--r--activerecord/test/cases/fixtures_test.rb6
-rw-r--r--activerecord/test/cases/helper.rb5
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb57
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb108
-rw-r--r--activerecord/test/cases/migration_test.rb78
-rw-r--r--activerecord/test/cases/named_scope_test.rb2
-rw-r--r--activerecord/test/cases/persistence_test.rb3
-rw-r--r--activerecord/test/cases/query_cache_test.rb1
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb2
-rw-r--r--activerecord/test/cases/relations_test.rb46
-rw-r--r--activerecord/test/cases/timestamp_test.rb2
-rw-r--r--activerecord/test/cases/transactions_test.rb2
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb4
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb4
47 files changed, 862 insertions, 273 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 11eb47917d..a3e3051b96 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,32 @@
*Rails 3.1.0 (unreleased)*
+* Migrations can be defined as reversible, meaning that the migration system
+will figure out how to reverse your migration. To use reversible migrations,
+just define the "change" method. For example:
+
+ class MyMigration < ActiveRecord::Migration
+ def change
+ create_table(:horses) do
+ t.column :content, :text
+ t.column :remind_at, :datetime
+ end
+ end
+ end
+
+Some things cannot be automatically reversed for you. If you know how to
+reverse those things, you should define 'up' and 'down' in your migration. If
+you define something in `change` that cannot be reversed, an
+IrreversibleMigration exception will be raised when going down.
+
+* Migrations should use instance methods rather than class methods:
+ class FooMigration < ActiveRecord::Migration
+ def up
+ ...
+ end
+ end
+
+ [Aaron Patterson]
+
* has_one maintains the association with separate after_create/after_update instead
of a single after_save. [fxn]
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index 911a5155fd..9743b1b4a7 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -193,13 +193,17 @@ module ActiveRecord
conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}"
conditions << append_conditions(reflection, preload_options)
- associated_records = reflection.klass.unscoped.where([conditions, ids]).
+ associated_records_proxy = reflection.klass.unscoped.
includes(options[:include]).
joins("INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}").
select("#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id").
- order(options[:order]).to_a
+ order(options[:order])
- set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
+ all_associated_records = associated_records(ids) do |some_ids|
+ associated_records_proxy.where([conditions, ids]).to_a
+ end
+
+ set_association_collection_records(id_to_record_map, reflection.name, all_associated_records, 'the_parent_record_id')
end
def preload_has_one_association(records, reflection, preload_options={})
@@ -358,13 +362,14 @@ module ActiveRecord
find_options = {
:select => preload_options[:select] || options[:select] || Arel::SqlLiteral.new("#{table_name}.*"),
:include => preload_options[:include] || options[:include],
- :conditions => [conditions, ids],
:joins => options[:joins],
:group => preload_options[:group] || options[:group],
:order => preload_options[:order] || options[:order]
}
- reflection.klass.scoped.apply_finder_options(find_options).to_a
+ associated_records(ids) do |some_ids|
+ reflection.klass.scoped.apply_finder_options(find_options.merge(:conditions => [conditions, some_ids])).to_a
+ end
end
@@ -382,6 +387,17 @@ module ActiveRecord
def in_or_equals_for_ids(ids)
ids.size > 1 ? "IN (?)" : "= ?"
end
+
+ # Some databases impose a limit on the number of ids in a list (in Oracle its 1000)
+ # Make several smaller queries if necessary or make one query if the adapter supports it
+ def associated_records(ids)
+ max_ids_in_a_list = connection.ids_in_list_limit || ids.size
+ records = []
+ ids.each_slice(max_ids_in_a_list) do |some_ids|
+ records += yield(some_ids)
+ end
+ records
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 08a4ebfd7e..7da38cd03f 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/conversions'
require 'active_support/core_ext/module/remove_method'
+require 'active_support/core_ext/class/attribute'
module ActiveRecord
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
@@ -1810,12 +1811,12 @@ module ActiveRecord
callbacks.each do |callback_name|
full_callback_name = "#{callback_name}_for_#{association_name}"
defined_callbacks = options[callback_name.to_sym]
- if options.has_key?(callback_name.to_sym)
- class_inheritable_reader full_callback_name.to_sym
- write_inheritable_attribute(full_callback_name.to_sym, [defined_callbacks].flatten)
- else
- write_inheritable_attribute(full_callback_name.to_sym, [])
- end
+
+ full_callback_value = options.has_key?(callback_name.to_sym) ? [defined_callbacks].flatten : []
+
+ # TODO : why do i need method_defined? I think its because of the inheritance chain
+ class_attribute full_callback_name.to_sym unless method_defined?(full_callback_name)
+ self.send("#{full_callback_name}=", full_callback_value)
end
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 5b34ac907c..774103342a 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -182,7 +182,7 @@ module ActiveRecord
unless options.blank?
raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
end
-
+
@reflection.klass.count_by_sql(custom_counter_sql)
else
@@ -332,13 +332,10 @@ module ActiveRecord
end
def uniq(collection = self)
- seen = Set.new
- collection.map do |record|
- unless seen.include?(record.id)
- seen << record.id
- record
- end
- end.compact
+ seen = {}
+ collection.find_all do |record|
+ seen[record.id] = true unless seen.key?(record.id)
+ end
end
# Replace this collection with +other_array+
@@ -375,14 +372,16 @@ module ActiveRecord
def load_target
if @owner.persisted? || foreign_key_present
begin
- if !loaded?
+ unless loaded?
if @target.is_a?(Array) && @target.any?
@target = find_target.map do |f|
i = @target.index(f)
if i
@target.delete_at(i).tap do |t|
keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
- t.attributes = f.attributes.except(*keys)
+ f.attributes.except(*keys).each do |k,v|
+ t.send("#{k}=", v)
+ end
end
else
f
@@ -409,11 +408,7 @@ module ActiveRecord
end
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
- if block_given?
- super { |*block_args| yield(*block_args) }
- else
- super
- end
+ super
elsif @reflection.klass.scopes[method]
@_named_scopes_cache ||= {}
@_named_scopes_cache[method] ||= {}
@@ -436,10 +431,10 @@ module ActiveRecord
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
counter_sql = @reflection.options[:finder_sql].sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
end
-
+
interpolate_sql(counter_sql)
end
-
+
def custom_finder_sql
interpolate_sql(@reflection.options[:finder_sql])
end
@@ -535,7 +530,7 @@ module ActiveRecord
def callbacks_for(callback_name)
full_callback_name = "#{callback_name}_for_#{@reflection.name}"
- @owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
+ @owner.class.send(full_callback_name.to_sym) || []
end
def ensure_owner_is_not_new
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index 09bba213ce..7cd04a1ad5 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -211,12 +211,12 @@ module ActiveRecord
:create => construct_create_scope
}
end
-
+
# Implemented by subclasses
def construct_find_scope
raise NotImplementedError
end
-
+
# Implemented by (some) subclasses
def construct_create_scope
{}
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 439880c1fa..c19a33faa8 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/object/blank'
module ActiveRecord
@@ -88,7 +89,7 @@ module ActiveRecord
end
def clone_with_time_zone_conversion_attribute?(attr, old)
- old.class.name == "Time" && time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
+ old.class.name == "Time" && time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
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 d640b26b74..dc2785b6bf 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/class/attribute'
+
module ActiveRecord
module AttributeMethods
module TimeZoneConversion
@@ -7,7 +9,7 @@ module ActiveRecord
cattr_accessor :time_zone_aware_attributes, :instance_writer => false
self.time_zone_aware_attributes = false
- class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
+ class_attribute :skip_time_zone_conversion_for_attributes, :instance_writer => false
self.skip_time_zone_conversion_for_attributes = []
end
@@ -54,7 +56,7 @@ module ActiveRecord
private
def create_time_zone_conversion_attribute?(name, column)
- time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
+ time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index f588475bbf..aee6f3a7eb 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -7,7 +7,7 @@ require 'active_support/time'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/delegating_attributes'
-require 'active_support/core_ext/class/inheritable_attributes'
+require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/deep_merge'
require 'active_support/core_ext/hash/indifferent_access'
@@ -412,7 +412,7 @@ module ActiveRecord #:nodoc:
self.store_full_sti_class = true
# Stores the default scope for the class
- class_inheritable_accessor :default_scoping, :instance_writer => false
+ class_attribute :default_scoping, :instance_writer => false
self.default_scoping = []
# Returns a hash of all the attributes that have been specified for serialization as
@@ -420,6 +420,9 @@ module ActiveRecord #:nodoc:
class_attribute :serialized_attributes
self.serialized_attributes = {}
+ class_attribute :_attr_readonly, :instance_writer => false
+ self._attr_readonly = []
+
class << self # Class methods
delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
@@ -504,12 +507,12 @@ module ActiveRecord #:nodoc:
# Attributes listed as readonly will be used to create a new record but update operations will
# ignore these fields.
def attr_readonly(*attributes)
- write_inheritable_attribute(:attr_readonly, Set.new(attributes.map { |a| a.to_s }) + (readonly_attributes || []))
+ self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
end
# Returns an array of all the attributes that have been specified as readonly.
def readonly_attributes
- read_inheritable_attribute(:attr_readonly) || []
+ self._attr_readonly
end
# If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
@@ -724,10 +727,6 @@ module ActiveRecord #:nodoc:
@arel_engine = @relation = @arel_table = nil
end
- def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
- descendants.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
- end
-
def attribute_method?(attribute)
super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
end
@@ -736,15 +735,12 @@ module ActiveRecord #:nodoc:
def lookup_ancestors #:nodoc:
klass = self
classes = [klass]
+ return classes if klass == ActiveRecord::Base
+
while klass != klass.base_class
classes << klass = klass.superclass
end
classes
- rescue
- # OPTIMIZE this rescue is to fix this test: ./test/cases/reflection_test.rb:56:in `test_human_name_for_column'
- # Apparently the method base_class causes some trouble.
- # It now works for sure.
- [self]
end
# Set the i18n scope to overwrite ActiveModel.
@@ -1129,7 +1125,8 @@ MSG
# Article.create.published # => true
def default_scope(options = {})
reset_scoped_methods
- self.default_scoping << construct_finder_arel(options, default_scoping.pop)
+ default_scoping = self.default_scoping.dup
+ self.default_scoping = default_scoping << construct_finder_arel(options, default_scoping.pop)
end
def current_scoped_methods #:nodoc:
@@ -1286,7 +1283,7 @@ MSG
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
def sanitize_sql_array(ary)
statement, *values = ary
- if values.first.is_a?(Hash) and statement =~ /:\w+/
+ if values.first.is_a?(Hash) && statement =~ /:\w+/
replace_named_bind_variables(statement, values.first)
elsif statement.include?('?')
replace_bind_variables(statement, values)
@@ -1582,12 +1579,20 @@ MSG
self.class.columns_hash[name.to_s]
end
- # Returns true if the +comparison_object+ is the same object, or is of the same type and has the same id.
+ # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
+ # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
+ #
+ # Note that new records are different from any other record by definition, unless the
+ # other record is the receiver itself. Besides, if you fetch existing records with
+ # +select+ and leave the ID out, you're on your own, this predicate will return false.
+ #
+ # Note also that destroying a record preserves its ID in the model instance, so deleted
+ # models are still comparable.
def ==(comparison_object)
comparison_object.equal?(self) ||
- persisted? &&
- (comparison_object.instance_of?(self.class) &&
- comparison_object.id == id)
+ comparison_object.instance_of?(self.class) &&
+ id.present? &&
+ comparison_object.id == id
end
# Delegates to ==
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index f3fba9a3a9..ada1560ce2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -91,6 +91,11 @@ module ActiveRecord
false
end
+ # Does this adapter restrict the number of ids you can use in a list. Oracle has a limit of 1000.
+ def ids_in_list_limit
+ nil
+ end
+
# QUOTING ==================================================
# Override to return the quoted table name. Defaults to column quoting.
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index a4c09b654a..f6321f1499 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,6 +1,3 @@
-require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/core_ext/module/aliasing'
-
module ActiveRecord
# Exception that can be raised to stop migrations from going backwards.
class IrreversibleMigration < ActiveRecordError
@@ -43,11 +40,11 @@ module ActiveRecord
# Example of a simple migration:
#
# class AddSsl < ActiveRecord::Migration
- # def self.up
+ # def up
# add_column :accounts, :ssl_enabled, :boolean, :default => 1
# end
#
- # def self.down
+ # def down
# remove_column :accounts, :ssl_enabled
# end
# end
@@ -63,7 +60,7 @@ module ActiveRecord
# Example of a more complex migration that also needs to initialize data:
#
# class AddSystemSettings < ActiveRecord::Migration
- # def self.up
+ # def up
# create_table :system_settings do |t|
# t.string :name
# t.string :label
@@ -77,7 +74,7 @@ module ActiveRecord
# :value => 1
# end
#
- # def self.down
+ # def down
# drop_table :system_settings
# end
# end
@@ -138,7 +135,7 @@ module ActiveRecord
# in the <tt>db/migrate/</tt> directory where <tt>timestamp</tt> is the
# UTC formatted date and time that the migration was generated.
#
- # You may then edit the <tt>self.up</tt> and <tt>self.down</tt> methods of
+ # You may then edit the <tt>up</tt> and <tt>down</tt> methods of
# MyNewMigration.
#
# There is a special syntactic shortcut to generate migrations that add fields to a table.
@@ -147,11 +144,11 @@ module ActiveRecord
#
# This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
# class AddFieldnameToTablename < ActiveRecord::Migration
- # def self.up
+ # def up
# add_column :tablenames, :fieldname, :string
# end
#
- # def self.down
+ # def down
# remove_column :tablenames, :fieldname
# end
# end
@@ -179,11 +176,11 @@ module ActiveRecord
# Not all migrations change the schema. Some just fix the data:
#
# class RemoveEmptyTags < ActiveRecord::Migration
- # def self.up
+ # def up
# Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? }
# end
#
- # def self.down
+ # def down
# # not much we can do to restore deleted data
# raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags"
# end
@@ -192,12 +189,12 @@ module ActiveRecord
# Others remove columns when they migrate up instead of down:
#
# class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration
- # def self.up
+ # def up
# remove_column :items, :incomplete_items_count
# remove_column :items, :completed_items_count
# end
#
- # def self.down
+ # def down
# add_column :items, :incomplete_items_count
# add_column :items, :completed_items_count
# end
@@ -206,11 +203,11 @@ module ActiveRecord
# And sometimes you need to do something in SQL not abstracted directly by migrations:
#
# class MakeJoinUnique < ActiveRecord::Migration
- # def self.up
+ # def up
# execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
# end
#
- # def self.down
+ # def down
# execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`"
# end
# end
@@ -223,7 +220,7 @@ module ActiveRecord
# latest column data from after the new column was added. Example:
#
# class AddPeopleSalary < ActiveRecord::Migration
- # def self.up
+ # def up
# add_column :people, :salary, :integer
# Person.reset_column_information
# Person.find(:all).each do |p|
@@ -243,7 +240,7 @@ module ActiveRecord
# You can also insert your own messages and benchmarks by using the +say_with_time+
# method:
#
- # def self.up
+ # def up
# ...
# say_with_time "Updating salaries..." do
# Person.find(:all).each do |p|
@@ -286,143 +283,201 @@ module ActiveRecord
#
# In application.rb.
#
+ # == Reversible Migrations
+ #
+ # Starting with Rails 3.1, you will be able to define reversible migrations.
+ # Reversible migrations are migrations that know how to go +down+ for you.
+ # You simply supply the +up+ logic, and the Migration system will figure out
+ # how to execute the down commands for you.
+ #
+ # To define a reversible migration, define the +change+ method in your
+ # migration like this:
+ #
+ # class TenderloveMigration < ActiveRecord::Migration
+ # def change
+ # create_table(:horses) do
+ # t.column :content, :text
+ # t.column :remind_at, :datetime
+ # end
+ # end
+ # end
+ #
+ # This migration will create the horses table for you on the way up, and
+ # automatically figure out how to drop the table on the way down.
+ #
+ # Some commands like +remove_column+ cannot be reversed. If you care to
+ # define how to move up and down in these cases, you should define the +up+
+ # and +down+ methods as before.
+ #
+ # If a command cannot be reversed, an
+ # <tt>ActiveRecord::IrreversibleMigration</tt> exception will be raised when
+ # the migration is moving down.
+ #
+ # For a list of commands that are reversible, please see
+ # <tt>ActiveRecord::Migration::CommandRecorder</tt>.
class Migration
- @@verbose = true
- cattr_accessor :verbose
+ autoload :CommandRecorder, 'active_record/migration/command_recorder'
class << self
- def up_with_benchmarks #:nodoc:
- migrate(:up)
- end
+ attr_accessor :delegate # :nodoc:
+ end
- def down_with_benchmarks #:nodoc:
- migrate(:down)
- end
+ def self.method_missing(name, *args, &block) # :nodoc:
+ (delegate || superclass.delegate).send(name, *args, &block)
+ end
- # Execute this migration in the named direction
- def migrate(direction)
- return unless respond_to?(direction)
+ cattr_accessor :verbose
- case direction
- when :up then announce "migrating"
- when :down then announce "reverting"
- end
+ attr_accessor :name, :version
- result = nil
- time = Benchmark.measure { result = send("#{direction}_without_benchmarks") }
+ def initialize
+ @name = self.class.name
+ @version = nil
+ @connection = nil
+ end
- case direction
- when :up then announce "migrated (%.4fs)" % time.real; write
- when :down then announce "reverted (%.4fs)" % time.real; write
- end
+ # instantiate the delegate object after initialize is defined
+ self.verbose = true
+ self.delegate = new
- result
- end
+ def up
+ self.class.delegate = self
+ return unless self.class.respond_to?(:up)
+ self.class.up
+ end
+
+ def down
+ self.class.delegate = self
+ return unless self.class.respond_to?(:down)
+ self.class.down
+ end
- # Because the method added may do an alias_method, it can be invoked
- # recursively. We use @ignore_new_methods as a guard to indicate whether
- # it is safe for the call to proceed.
- def singleton_method_added(sym) #:nodoc:
- return if defined?(@ignore_new_methods) && @ignore_new_methods
+ # Execute this migration in the named direction
+ def migrate(direction)
+ return unless respond_to?(direction)
- begin
- @ignore_new_methods = true
+ case direction
+ when :up then announce "migrating"
+ when :down then announce "reverting"
+ end
- case sym
- when :up, :down
- singleton_class.send(:alias_method_chain, sym, "benchmarks")
+ time = nil
+ ActiveRecord::Base.connection_pool.with_connection do |conn|
+ @connection = conn
+ if respond_to?(:change)
+ if direction == :down
+ recorder = CommandRecorder.new(@connection)
+ suppress_messages do
+ @connection = recorder
+ change
+ end
+ @connection = conn
+ time = Benchmark.measure {
+ recorder.inverse.each do |cmd, args|
+ send(cmd, *args)
+ end
+ }
+ else
+ time = Benchmark.measure { change }
end
- ensure
- @ignore_new_methods = false
+ else
+ time = Benchmark.measure { send(direction) }
end
+ @connection = nil
end
- def write(text="")
- puts(text) if verbose
+ case direction
+ when :up then announce "migrated (%.4fs)" % time.real; write
+ when :down then announce "reverted (%.4fs)" % time.real; write
end
+ end
- def announce(message)
- version = defined?(@version) ? @version : nil
+ def write(text="")
+ puts(text) if verbose
+ end
- text = "#{version} #{name}: #{message}"
- length = [0, 75 - text.length].max
- write "== %s %s" % [text, "=" * length]
- end
+ def announce(message)
+ text = "#{version} #{name}: #{message}"
+ length = [0, 75 - text.length].max
+ write "== %s %s" % [text, "=" * length]
+ end
- def say(message, subitem=false)
- write "#{subitem ? " ->" : "--"} #{message}"
- end
+ def say(message, subitem=false)
+ write "#{subitem ? " ->" : "--"} #{message}"
+ end
- def say_with_time(message)
- say(message)
- result = nil
- time = Benchmark.measure { result = yield }
- say "%.4fs" % time.real, :subitem
- say("#{result} rows", :subitem) if result.is_a?(Integer)
- result
- end
+ def say_with_time(message)
+ say(message)
+ result = nil
+ time = Benchmark.measure { result = yield }
+ say "%.4fs" % time.real, :subitem
+ say("#{result} rows", :subitem) if result.is_a?(Integer)
+ result
+ end
- def suppress_messages
- save, self.verbose = verbose, false
- yield
- ensure
- self.verbose = save
- end
+ def suppress_messages
+ save, self.verbose = verbose, false
+ yield
+ ensure
+ self.verbose = save
+ end
- def connection
- ActiveRecord::Base.connection
- end
+ def connection
+ @connection || ActiveRecord::Base.connection
+ end
- def method_missing(method, *arguments, &block)
- arg_list = arguments.map{ |a| a.inspect } * ', '
+ def method_missing(method, *arguments, &block)
+ arg_list = arguments.map{ |a| a.inspect } * ', '
- say_with_time "#{method}(#{arg_list})" do
- unless arguments.empty? || method == :execute
- arguments[0] = Migrator.proper_table_name(arguments.first)
- end
- connection.send(method, *arguments, &block)
+ say_with_time "#{method}(#{arg_list})" do
+ unless arguments.empty? || method == :execute
+ arguments[0] = Migrator.proper_table_name(arguments.first)
end
+ return super unless connection.respond_to?(method)
+ connection.send(method, *arguments, &block)
end
+ end
- def copy(destination, sources, options = {})
- copied = []
-
- destination_migrations = ActiveRecord::Migrator.migrations(destination)
- last = destination_migrations.last
- sources.each do |name, path|
- source_migrations = ActiveRecord::Migrator.migrations(path)
+ def copy(destination, sources, options = {})
+ copied = []
- source_migrations.each do |migration|
- source = File.read(migration.filename)
- source = "# This migration comes from #{name} (originally #{migration.version})\n#{source}"
+ FileUtils.mkdir_p(destination) unless File.exists?(destination)
- if duplicate = destination_migrations.detect { |m| m.name == migration.name }
- options[:on_skip].call(name, migration) if File.read(duplicate.filename) != source && options[:on_skip]
- next
- end
+ destination_migrations = ActiveRecord::Migrator.migrations(destination)
+ last = destination_migrations.last
+ sources.each do |name, path|
+ source_migrations = ActiveRecord::Migrator.migrations(path)
- migration.version = next_migration_number(last ? last.version + 1 : 0).to_i
- new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.rb")
- old_path, migration.filename = migration.filename, new_path
- last = migration
+ source_migrations.each do |migration|
+ source = File.read(migration.filename)
+ source = "# This migration comes from #{name} (originally #{migration.version})\n#{source}"
- FileUtils.cp(old_path, migration.filename)
- copied << migration
- options[:on_copy].call(name, migration, old_path) if options[:on_copy]
- destination_migrations << migration
+ if duplicate = destination_migrations.detect { |m| m.name == migration.name }
+ options[:on_skip].call(name, migration) if File.read(duplicate.filename) != source && options[:on_skip]
+ next
end
- end
- copied
- end
+ migration.version = next_migration_number(last ? last.version + 1 : 0).to_i
+ new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.rb")
+ old_path, migration.filename = migration.filename, new_path
+ last = migration
- def next_migration_number(number)
- if ActiveRecord::Base.timestamped_migrations
- [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
- else
- "%.3d" % number
+ FileUtils.cp(old_path, migration.filename)
+ copied << migration
+ options[:on_copy].call(name, migration, old_path) if options[:on_copy]
+ destination_migrations << migration
end
end
+
+ copied
+ end
+
+ def next_migration_number(number)
+ if ActiveRecord::Base.timestamped_migrations
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
+ else
+ "%.3d" % number
+ end
end
end
@@ -449,7 +504,7 @@ module ActiveRecord
def load_migration
require(File.expand_path(filename))
- name.constantize
+ name.constantize.new
end
end
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
new file mode 100644
index 0000000000..d7e481905a
--- /dev/null
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -0,0 +1,91 @@
+module ActiveRecord
+ class Migration
+ # ActiveRecord::Migration::CommandRecorder records commands done during
+ # a migration and knows how to reverse those commands. The CommandRecorder
+ # knows how to invert the following commands:
+ #
+ # * add_column
+ # * add_index
+ # * add_timestamp
+ # * create_table
+ # * remove_timestamps
+ # * rename_column
+ # * rename_index
+ # * rename_table
+ class CommandRecorder
+ attr_accessor :commands, :delegate
+
+ def initialize(delegate = nil)
+ @commands = []
+ @delegate = delegate
+ end
+
+ # record +command+. +command+ should be a method name and arguments.
+ # For example:
+ #
+ # recorder.record(:method_name, [:arg1, arg2])
+ def record(*command)
+ @commands << command
+ end
+
+ # Returns a list that represents commands that are the inverse of the
+ # commands stored in +commands+. For example:
+ #
+ # recorder.record(:rename_table, [:old, :new])
+ # recorder.inverse # => [:rename_table, [:new, :old]]
+ #
+ # This method will raise an IrreversibleMigration exception if it cannot
+ # invert the +commands+.
+ def inverse
+ @commands.reverse.map { |name, args|
+ method = :"invert_#{name}"
+ raise IrreversibleMigration unless respond_to?(method, true)
+ __send__(method, args)
+ }
+ end
+
+ def respond_to?(*args) # :nodoc:
+ super || delegate.respond_to?(*args)
+ end
+
+ def send(method, *args) # :nodoc:
+ return super unless respond_to?(method)
+ record(method, args)
+ end
+
+ private
+ def invert_create_table(args)
+ [:drop_table, args]
+ end
+
+ def invert_rename_table(args)
+ [:rename_table, args.reverse]
+ end
+
+ def invert_add_column(args)
+ [:remove_column, args.first(2)]
+ end
+
+ def invert_rename_index(args)
+ [:rename_index, args.reverse]
+ end
+
+ def invert_rename_column(args)
+ [:rename_column, [args.first] + args.last(2).reverse]
+ end
+
+ def invert_add_index(args)
+ table, columns, _ = *args
+ [:remove_index, [table, {:column => columns}]]
+ end
+
+ def invert_remove_timestamps(args)
+ [:add_timestamps, args]
+ end
+
+ def invert_add_timestamps(args)
+ [:remove_timestamps, args]
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index 0b92ba5caa..0f421560f0 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -2,12 +2,18 @@ require 'active_support/core_ext/array'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/class/attribute'
module ActiveRecord
# = Active Record Named \Scopes
module NamedScope
extend ActiveSupport::Concern
+ included do
+ class_attribute :scopes
+ self.scopes = {}
+ end
+
module ClassMethods
# Returns an anonymous \scope.
#
@@ -33,10 +39,6 @@ module ActiveRecord
end
end
- def scopes
- read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
- end
-
# Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query,
# such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>.
#
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 0c3392263a..f1d3eaed38 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -2,6 +2,7 @@ require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/class/attribute'
module ActiveRecord
module NestedAttributes #:nodoc:
@@ -11,7 +12,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_inheritable_accessor :nested_attributes_options, :instance_writer => false
+ class_attribute :nested_attributes_options, :instance_writer => false
self.nested_attributes_options = {}
end
@@ -268,7 +269,11 @@ module ActiveRecord
if reflection = reflect_on_association(association_name)
reflection.options[:autosave] = true
add_autosave_association_callbacks(reflection)
+
+ nested_attributes_options = self.nested_attributes_options.dup
nested_attributes_options[association_name.to_sym] = options
+ self.nested_attributes_options = nested_attributes_options
+
type = (reflection.collection? ? :collection : :one_to_one)
# def pirate_attributes=(attributes)
@@ -315,7 +320,7 @@ module ActiveRecord
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
# then the existing record will be marked for destruction.
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
- options = nested_attributes_options[association_name]
+ options = self.nested_attributes_options[association_name]
attributes = attributes.with_indifferent_access
check_existing_record = (options[:update_only] || !attributes['id'].blank?)
@@ -364,7 +369,7 @@ module ActiveRecord
# { :id => '2', :_destroy => true }
# ])
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
- options = nested_attributes_options[association_name]
+ options = self.nested_attributes_options[association_name]
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
@@ -433,7 +438,7 @@ module ActiveRecord
end
def call_reject_if(association_name, attributes)
- case callback = nested_attributes_options[association_name][:reject_if]
+ case callback = self.nested_attributes_options[association_name][:reject_if]
when Symbol
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
when Proc
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index a2260e9a19..a07c321960 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,8 +1,15 @@
+require 'active_support/core_ext/class/attribute'
+
module ActiveRecord
# = Active Record Reflection
module Reflection # :nodoc:
extend ActiveSupport::Concern
+ included do
+ class_attribute :reflections
+ self.reflections = {}
+ end
+
# Reflection enables to interrogate Active Record classes and objects
# about their associations and aggregations. This information can,
# for example, be used in a form builder that takes an Active Record object
@@ -20,18 +27,9 @@ module ActiveRecord
when :composed_of
reflection = AggregateReflection.new(macro, name, options, active_record)
end
- write_inheritable_hash :reflections, name => reflection
- reflection
- end
- # Returns a hash containing all AssociationReflection objects for the current class.
- # Example:
- #
- # Invoice.reflections
- # Account.reflections
- #
- def reflections
- read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
+ self.reflections = self.reflections.merge(name => reflection)
+ reflection
end
# Returns an array of AggregateReflection objects for all the aggregations in the class.
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 6bf698fe97..c8adaddfca 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -208,14 +208,16 @@ module ActiveRecord
end
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
- group_attr = @group_values.first
- association = @klass.reflect_on_association(group_attr.to_sym)
- associated = association && association.macro == :belongs_to # only count belongs_to associations
- group_field = associated ? association.primary_key_name : group_attr
- group_alias = column_alias_for(group_field)
- group_column = column_for(group_field)
+ group_attr = @group_values
+ association = @klass.reflect_on_association(group_attr.first.to_sym)
+ associated = group_attr.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
+ group_fields = Array(associated ? association.primary_key_name : group_attr)
+ group_aliases = group_fields.map { |field| column_alias_for(field) }
+ group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
+ [aliaz, column_for(field)]
+ }
- group = @klass.connection.adapter_name == 'FrontBase' ? group_alias : group_field
+ group = @klass.connection.adapter_name == 'FrontBase' ? group_aliases : group_fields
if operation == 'count' && column_name == :all
aggregate_alias = 'count_all'
@@ -223,22 +225,33 @@ module ActiveRecord
aggregate_alias = column_alias_for(operation, column_name)
end
- relation = except(:group).group(group)
- relation.select_values = [
- operation_over_aggregate_column(aggregate_column(column_name), operation, distinct).as(aggregate_alias),
- "#{group_field} AS #{group_alias}"
+ select_values = [
+ operation_over_aggregate_column(
+ aggregate_column(column_name),
+ operation,
+ distinct).as(aggregate_alias)
]
+ select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
+ "#{field} AS #{aliaz}"
+ }
+
+ relation = except(:group).group(group.join(','))
+ relation.select_values = select_values
+
calculated_data = @klass.connection.select_all(relation.to_sql)
if association
- key_ids = calculated_data.collect { |row| row[group_alias] }
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
key_records = association.klass.base_class.find(key_ids)
key_records = Hash[key_records.map { |r| [r.id, r] }]
end
ActiveSupport::OrderedHash[calculated_data.map do |row|
- key = type_cast_calculated_value(row[group_alias], group_column)
+ key = group_columns.map { |aliaz, column|
+ type_cast_calculated_value(row[aliaz], column)
+ }
+ key = key.first if key.size == 1
key = key_records[key] if associated
[key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)]
end]
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index c5428dccd6..32c7d08daa 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -25,6 +25,11 @@ module ActiveRecord
attribute.in(values)
when Range, Arel::Relation
attribute.in(value)
+ when ActiveRecord::Base
+ attribute.eq(value.quoted_id)
+ when Class
+ # FIXME: I think we need to deprecate this behavior
+ attribute.eq(value.name)
else
attribute.eq(value)
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 9c399d3333..9e7503a60d 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -141,7 +141,7 @@ module ActiveRecord
"#{@klass.table_name}.#{@klass.primary_key} DESC" :
reverse_sql_order(order_clause).join(', ')
- except(:order).order(Arel::SqlLiteral.new(order))
+ except(:order).order(Arel.sql(order))
end
def arel
@@ -174,10 +174,7 @@ module ActiveRecord
arel = build_joins(arel, @joins_values) unless @joins_values.empty?
- (@where_values - ['']).uniq.each do |where|
- where = Arel.sql(where) if String === where
- arel = arel.where(Arel::Nodes::Grouping.new(where))
- end
+ arel = collapse_wheres(arel, (@where_values - ['']).uniq)
arel = arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
@@ -198,6 +195,27 @@ module ActiveRecord
private
+ def collapse_wheres(arel, wheres)
+ equalities = wheres.grep(Arel::Nodes::Equality)
+
+ groups = equalities.group_by do |equality|
+ equality.left
+ end
+
+ groups.each do |_, eqls|
+ test = eqls.inject(eqls.shift) do |memo, expr|
+ memo.or(expr)
+ end
+ arel = arel.where(test)
+ end
+
+ (wheres - equalities).each do |where|
+ where = Arel.sql(where) if String === where
+ arel = arel.where(Arel::Nodes::Grouping.new(where))
+ end
+ arel
+ end
+
def build_where(opts, other = [])
case opts
when String, Array
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index c1bc3214ea..c6bb5c1961 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -30,9 +30,7 @@ module ActiveRecord
# ActiveRecord::Schema is only supported by database adapters that also
# support migrations, the two features being very similar.
class Schema < Migration
- private_class_method :new
-
- def self.migrations_path
+ def migrations_path
ActiveRecord::Migrator.migrations_path
end
@@ -48,11 +46,12 @@ module ActiveRecord
# ...
# end
def self.define(info={}, &block)
- instance_eval(&block)
+ schema = new
+ schema.instance_eval(&block)
unless info[:version].blank?
initialize_schema_migrations_table
- assume_migrated_upto_version(info[:version], migrations_path)
+ assume_migrated_upto_version(info[:version], schema.migrations_path)
end
end
end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 230adf6b2b..2ecbd906bd 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/class/attribute'
+
module ActiveRecord
# = Active Record Timestamp
#
@@ -29,14 +31,14 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_inheritable_accessor :record_timestamps, :instance_writer => false
+ class_attribute :record_timestamps, :instance_writer => false
self.record_timestamps = true
end
private
def create #:nodoc:
- if record_timestamps
+ if self.record_timestamps
current_time = current_time_from_proper_timezone
all_timestamp_attributes.each do |column|
@@ -61,7 +63,7 @@ module ActiveRecord
end
def should_record_timestamps?
- record_timestamps && (!partial_updates? || changed? || (attributes.keys & self.class.serialized_attributes.keys).present?)
+ self.record_timestamps && (!partial_updates? || changed? || (attributes.keys & self.class.serialized_attributes.keys).present?)
end
def timestamp_attributes_for_update_in_model
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index 89eba15be1..0667be7d23 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -3,8 +3,8 @@ module ActiveRecord
MAJOR = 3
MINOR = 1
TINY = 0
- BUILD = "beta"
+ PRE = "beta"
- STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
end
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
index 8ac21c1410..126b6f434b 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -1,5 +1,5 @@
class <%= migration_class_name %> < ActiveRecord::Migration
- def self.up
+ def up
<% attributes.each do |attribute| -%>
<%- if migration_action -%>
<%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end %>
@@ -7,7 +7,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration
<%- end -%>
end
- def self.down
+ def down
<% attributes.reverse.each do |attribute| -%>
<%- if migration_action -%>
<%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end %>
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb b/activerecord/lib/rails/generators/active_record/model/templates/migration.rb
index 1f68487304..70e064be21 100644
--- a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/model/templates/migration.rb
@@ -1,5 +1,5 @@
class <%= migration_class_name %> < ActiveRecord::Migration
- def self.up
+ def up
create_table :<%= table_name %> do |t|
<% for attribute in attributes -%>
t.<%= attribute.type %> :<%= attribute.name %>
@@ -10,7 +10,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration
end
end
- def self.down
+ def down
drop_table :<%= table_name %>
end
end
diff --git a/activerecord/lib/rails/generators/active_record/session_migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/session_migration/templates/migration.rb
index 919822af7b..8f0bf1ef0d 100644
--- a/activerecord/lib/rails/generators/active_record/session_migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/session_migration/templates/migration.rb
@@ -1,5 +1,5 @@
class <%= migration_class_name %> < ActiveRecord::Migration
- def self.up
+ def up
create_table :<%= session_table_name %> do |t|
t.string :session_id, :null => false
t.text :data
@@ -10,7 +10,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration
add_index :<%= session_table_name %>, :updated_at
end
- def self.down
+ def down
drop_table :<%= session_table_name %>
end
end
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 37c6f354a8..0742e311d9 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -10,7 +10,8 @@ require 'models/reply'
require 'models/person'
class CascadedEagerLoadingTest < ActiveRecord::TestCase
- fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments, :categorizations, :people
+ fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments,
+ :categorizations, :people, :categories
def test_eager_association_loading_with_cascaded_two_levels
authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 66fb5ac1e1..c00b8a1cde 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -79,6 +79,58 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ def test_preloading_has_many_in_multiple_queries_with_more_ids_than_database_can_handle
+ Post.connection.expects(:ids_in_list_limit).at_least_once.returns(5)
+ posts = Post.find(:all, :include=>:comments)
+ assert_equal 7, posts.size
+ end
+
+ def test_preloading_has_many_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle
+ Post.connection.expects(:ids_in_list_limit).at_least_once.returns(nil)
+ posts = Post.find(:all, :include=>:comments)
+ assert_equal 7, posts.size
+ end
+
+ def test_preloading_habtm_in_multiple_queries_with_more_ids_than_database_can_handle
+ Post.connection.expects(:ids_in_list_limit).at_least_once.returns(5)
+ posts = Post.find(:all, :include=>:categories)
+ assert_equal 7, posts.size
+ end
+
+ def test_preloading_habtm_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle
+ Post.connection.expects(:ids_in_list_limit).at_least_once.returns(nil)
+ posts = Post.find(:all, :include=>:categories)
+ assert_equal 7, posts.size
+ end
+
+ def test_load_associated_records_in_one_query_when_adapter_has_no_limit
+ Post.connection.expects(:ids_in_list_limit).at_least_once.returns(nil)
+ Post.expects(:i_was_called).with([1,2,3,4,5,6,7]).returns([1])
+ associated_records = Post.send(:associated_records, [1,2,3,4,5,6,7]) do |some_ids|
+ Post.i_was_called(some_ids)
+ end
+ assert_equal [1], associated_records
+ end
+
+ def test_load_associated_records_in_several_queries_when_many_ids_passed
+ Post.connection.expects(:ids_in_list_limit).at_least_once.returns(5)
+ Post.expects(:i_was_called).with([1,2,3,4,5]).returns([1])
+ Post.expects(:i_was_called).with([6,7]).returns([6])
+ associated_records = Post.send(:associated_records, [1,2,3,4,5,6,7]) do |some_ids|
+ Post.i_was_called(some_ids)
+ end
+ assert_equal [1,6], associated_records
+ end
+
+ def test_load_associated_records_in_one_query_when_a_few_ids_passed
+ Post.connection.expects(:ids_in_list_limit).at_least_once.returns(5)
+ Post.expects(:i_was_called).with([1,2,3]).returns([1])
+ associated_records = Post.send(:associated_records, [1,2,3]) do |some_ids|
+ Post.i_was_called(some_ids)
+ end
+ assert_equal [1], associated_records
+ end
+
def test_including_duplicate_objects_from_belongs_to
popular_post = Post.create!(:title => 'foo', :body => "I like cars!")
comment = popular_post.comments.create!(:body => "lol")
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index ecfc769f3a..33c53e695b 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1282,4 +1282,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
comment = post.comments.build
assert post.comments.include?(comment)
end
+
+ def test_load_target_respects_protected_attributes
+ topic = Topic.create!
+ reply = topic.replies.create(:title => "reply 1")
+ reply.approved = false
+ reply.save!
+
+ # Save with a different object instance, so the instance that's still held
+ # in topic.relies doesn't know about the changed attribute.
+ reply2 = Reply.find(reply.id)
+ reply2.approved = true
+ reply2.save!
+
+ # Force loading the collection from the db. This will merge the existing
+ # object (reply) with what gets loaded from the db (which includes the
+ # changed approved attribute). approved is a protected attribute, so if mass
+ # assignment is used, it won't get updated and will still be false.
+ first = topic.replies.to_a.first
+ assert_equal reply.id, first.id
+ assert_equal true, first.approved?
+ end
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index fa5c2e49df..081583038f 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -551,8 +551,8 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase
def test_that_we_can_load_associations_that_have_the_same_reciprocal_name_from_different_models
assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do
i = Interest.find(:first)
- z = i.zine
- m = i.man
+ i.zine
+ i.man
end
end
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index dd8152b219..83c605d2bb 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -120,7 +120,7 @@ class AssociationsTest < ActiveRecord::TestCase
def test_force_reload_is_uncached
firm = Firm.create!("name" => "A New Firm, Inc")
- client = Client.create!("name" => "TheClient.com", :firm => firm)
+ Client.create!("name" => "TheClient.com", :firm => firm)
ActiveRecord::Base.cache do
firm.clients.each {}
assert_queries(0) { assert_not_nil firm.clients.each {} }
@@ -270,17 +270,17 @@ class OverridingAssociationsTest < ActiveRecord::TestCase
def test_habtm_association_redefinition_callbacks_should_differ_and_not_inherited
# redeclared association on AR descendant should not inherit callbacks from superclass
- callbacks = PeopleList.read_inheritable_attribute(:before_add_for_has_and_belongs_to_many)
+ callbacks = PeopleList.before_add_for_has_and_belongs_to_many
assert_equal([:enlist], callbacks)
- callbacks = DifferentPeopleList.read_inheritable_attribute(:before_add_for_has_and_belongs_to_many)
+ callbacks = DifferentPeopleList.before_add_for_has_and_belongs_to_many
assert_equal([], callbacks)
end
def test_has_many_association_redefinition_callbacks_should_differ_and_not_inherited
# redeclared association on AR descendant should not inherit callbacks from superclass
- callbacks = PeopleList.read_inheritable_attribute(:before_add_for_has_many)
+ callbacks = PeopleList.before_add_for_has_many
assert_equal([:enlist], callbacks)
- callbacks = DifferentPeopleList.read_inheritable_attribute(:before_add_for_has_many)
+ callbacks = DifferentPeopleList.before_add_for_has_many
assert_equal([], callbacks)
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index ab9a65944f..bb0166a60c 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -568,7 +568,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_bulk_update_respects_access_control
privatize("title=(value)")
- assert_raise(ActiveRecord::UnknownAttributeError) { topic = @target.new(:title => "Rants about pants") }
+ assert_raise(ActiveRecord::UnknownAttributeError) { @target.new(:title => "Rants about pants") }
assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } }
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 459f9fa55c..b13cb2d7a2 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -355,8 +355,6 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
end
def test_invalid_adding_before_save
- no_of_firms = Firm.count
- no_of_clients = Client.count
new_firm = Firm.new("name" => "A New Firm, Inc")
new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")])
assert !c.persisted?
@@ -461,7 +459,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_many_before_save
company = companies(:first_firm)
- new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
+ assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
company.name += '-changed'
assert_queries(3) { assert company.save }
@@ -481,7 +479,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_many_via_block_before_save
company = companies(:first_firm)
- new_clients = assert_no_queries do
+ assert_no_queries do
company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client|
client.name = "changed"
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 9f2b0c9c86..26f388ca46 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -182,7 +182,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_initialize_with_invalid_attribute
begin
- topic = Topic.new({ "title" => "test",
+ Topic.new({ "title" => "test",
"last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"})
rescue ActiveRecord::MultiparameterAssignmentErrors => ex
assert_equal(1, ex.errors.size)
@@ -397,6 +397,15 @@ class BasicsTest < ActiveRecord::TestCase
assert_not_equal Topic.new, Topic.new
end
+ def test_equality_of_destroyed_records
+ topic_1 = Topic.new(:title => 'test_1')
+ topic_1.save
+ topic_2 = Topic.find(topic_1.id)
+ topic_1.destroy
+ assert_equal topic_1, topic_2
+ assert_equal topic_2, topic_1
+ end
+
def test_hashing
assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
end
@@ -963,7 +972,6 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_nil_serialized_attribute_with_class_constraint
- myobj = MyObject.new('value1', 'value2')
topic = Topic.new
assert_nil topic.content
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 61fbf01a50..5cb8485b4b 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -54,6 +54,19 @@ class CalculationsTest < ActiveRecord::TestCase
c = Account.sum(:credit_limit, :group => :firm_id)
[1,6,2].each { |firm_id| assert c.keys.include?(firm_id) }
end
+
+ def test_should_group_by_multiple_fields
+ c = Account.count(:all, :group => ['firm_id', :credit_limit])
+ [ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) }
+ end
+
+ def test_should_group_by_multiple_fields_having_functions
+ c = Topic.group(:author_name, 'COALESCE(type, title)').count(:all)
+ assert_equal 1, c[["Carl", "The Third Topic of the day"]]
+ assert_equal 1, c[["Mary", "Reply"]]
+ assert_equal 1, c[["David", "The First Topic"]]
+ assert_equal 1, c[["Carl", "Reply"]]
+ end
def test_should_group_by_summed_field
c = Account.sum(:credit_limit, :group => :firm_id)
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 39ce47d9d6..31e4981a1d 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -949,7 +949,7 @@ class FinderTest < ActiveRecord::TestCase
# http://dev.rubyonrails.org/ticket/6778
def test_find_ignores_previously_inserted_record
- post = Post.create!(:title => 'test', :body => 'it out')
+ Post.create!(:title => 'test', :body => 'it out')
assert_equal [], Post.find_all_by_id(nil)
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index d5ef30e137..9ce163a00f 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -58,7 +58,7 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_inserts
- topics = create_fixtures("topics")
+ create_fixtures("topics")
first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'David'")
assert_equal("The First Topic", first_row["title"])
@@ -114,7 +114,7 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_insert_with_datetime
- topics = create_fixtures("tasks")
+ create_fixtures("tasks")
first = Task.find(1)
assert first
end
@@ -240,7 +240,7 @@ if Account.connection.respond_to?(:reset_pk_sequence!)
def test_create_fixtures_resets_sequences_when_not_cached
@instances.each do |instance|
- max_id = create_fixtures(instance.class.table_name).inject(0) do |_max_id, (name, fixture)|
+ max_id = create_fixtures(instance.class.table_name).inject(0) do |_max_id, (_, fixture)|
fixture_id = fixture['id'].to_i
fixture_id > _max_id ? fixture_id : _max_id
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 52f26b71f5..f9bbc5299b 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -13,11 +13,6 @@ require 'active_record'
require 'active_support/dependencies'
require 'connection'
-begin
- require 'ruby-debug'
-rescue LoadError
-end
-
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
new file mode 100644
index 0000000000..afec64750e
--- /dev/null
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -0,0 +1,57 @@
+require "cases/helper"
+
+module ActiveRecord
+ class InvertibleMigrationTest < ActiveRecord::TestCase
+ class SilentMigration < ActiveRecord::Migration
+ def write(text = '')
+ # sssshhhhh!!
+ end
+ end
+
+ class InvertibleMigration < SilentMigration
+ def change
+ create_table("horses") do |t|
+ t.column :content, :text
+ t.column :remind_at, :datetime
+ end
+ end
+ end
+
+ class NonInvertibleMigration < SilentMigration
+ def change
+ create_table("horses") do |t|
+ t.column :content, :text
+ t.column :remind_at, :datetime
+ end
+ remove_column "horses", :content
+ end
+ end
+
+ def teardown
+ if ActiveRecord::Base.connection.table_exists?("horses")
+ ActiveRecord::Base.connection.drop_table("horses")
+ end
+ end
+
+ def test_no_reverse
+ migration = NonInvertibleMigration.new
+ migration.migrate(:up)
+ assert_raises(IrreversibleMigration) do
+ migration.migrate(:down)
+ end
+ end
+
+ def test_up
+ migration = InvertibleMigration.new
+ migration.migrate(:up)
+ assert migration.connection.table_exists?("horses"), "horses should exist"
+ end
+
+ def test_down
+ migration = InvertibleMigration.new
+ migration.migrate :up
+ migration.migrate :down
+ assert !migration.connection.table_exists?("horses")
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
new file mode 100644
index 0000000000..ea2292dda5
--- /dev/null
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -0,0 +1,108 @@
+require "cases/helper"
+
+module ActiveRecord
+ class Migration
+ class CommandRecorderTest < ActiveRecord::TestCase
+ def setup
+ @recorder = CommandRecorder.new
+ end
+
+ def test_respond_to_delegates
+ recorder = CommandRecorder.new(Class.new {
+ def america; end
+ }.new)
+ assert recorder.respond_to?(:america)
+ end
+
+ def test_send_calls_super
+ assert_raises(NoMethodError) do
+ @recorder.send(:create_table, :horses)
+ end
+ end
+
+ def test_send_delegates_to_record
+ recorder = CommandRecorder.new(Class.new {
+ def create_table(name); end
+ }.new)
+ assert recorder.respond_to?(:create_table), 'respond_to? create_table'
+ recorder.send(:create_table, :horses)
+ assert_equal [[:create_table, [:horses]]], recorder.commands
+ end
+
+ def test_unknown_commands_raise_exception
+ @recorder.record :execute, ['some sql']
+ assert_raises(ActiveRecord::IrreversibleMigration) do
+ @recorder.inverse
+ end
+ end
+
+ def test_record
+ @recorder.record :create_table, [:system_settings]
+ assert_equal 1, @recorder.commands.length
+ end
+
+ def test_inverse
+ @recorder.record :create_table, [:system_settings]
+ assert_equal 1, @recorder.inverse.length
+
+ @recorder.record :rename_table, [:old, :new]
+ assert_equal 2, @recorder.inverse.length
+ end
+
+ def test_inverted_commands_are_reveresed
+ @recorder.record :create_table, [:hello]
+ @recorder.record :create_table, [:world]
+ tables = @recorder.inverse.map(&:last)
+ assert_equal [[:world], [:hello]], tables
+ end
+
+ def test_invert_create_table
+ @recorder.record :create_table, [:system_settings]
+ drop_table = @recorder.inverse.first
+ assert_equal [:drop_table, [:system_settings]], drop_table
+ end
+
+ def test_invert_rename_table
+ @recorder.record :rename_table, [:old, :new]
+ rename = @recorder.inverse.first
+ assert_equal [:rename_table, [:new, :old]], rename
+ end
+
+ def test_invert_add_column
+ @recorder.record :add_column, [:table, :column, :type, {}]
+ remove = @recorder.inverse.first
+ assert_equal [:remove_column, [:table, :column]], remove
+ end
+
+ def test_invert_rename_column
+ @recorder.record :rename_column, [:table, :old, :new]
+ rename = @recorder.inverse.first
+ assert_equal [:rename_column, [:table, :new, :old]], rename
+ end
+
+ def test_invert_add_index
+ @recorder.record :add_index, [:table, [:one, :two], {:options => true}]
+ remove = @recorder.inverse.first
+ assert_equal [:remove_index, [:table, {:column => [:one, :two]}]], remove
+ end
+
+ def test_invert_rename_index
+ @recorder.record :rename_index, [:old, :new]
+ rename = @recorder.inverse.first
+ assert_equal [:rename_index, [:new, :old]], rename
+ end
+
+ def test_invert_add_timestamps
+ @recorder.record :add_timestamps, [:table]
+ remove = @recorder.inverse.first
+ assert_equal [:remove_timestamps, [:table]], remove
+ end
+
+ def test_invert_remove_timestamps
+ @recorder.record :remove_timestamps, [:table]
+ add = @recorder.inverse.first
+ assert_equal [:add_timestamps, [:table]], add
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index e6eef805cf..3037d73a1b 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -18,10 +18,11 @@ if ActiveRecord::Base.connection.supports_migrations?
class ActiveRecord::Migration
class <<self
attr_accessor :message_count
- def puts(text="")
- self.message_count ||= 0
- self.message_count += 1
- end
+ end
+
+ def puts(text="")
+ self.class.message_count ||= 0
+ self.class.message_count += 1
end
end
@@ -1165,6 +1166,44 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
end
+ class MockMigration < ActiveRecord::Migration
+ attr_reader :went_up, :went_down
+ def initialize
+ @went_up = false
+ @went_down = false
+ end
+
+ def up
+ @went_up = true
+ super
+ end
+
+ def down
+ @went_down = true
+ super
+ end
+ end
+
+ def test_instance_based_migration_up
+ migration = MockMigration.new
+ assert !migration.went_up, 'have not gone up'
+ assert !migration.went_down, 'have not gone down'
+
+ migration.migrate :up
+ assert migration.went_up, 'have gone up'
+ assert !migration.went_down, 'have not gone down'
+ end
+
+ def test_instance_based_migration_down
+ migration = MockMigration.new
+ assert !migration.went_up, 'have not gone up'
+ assert !migration.went_down, 'have not gone down'
+
+ migration.migrate :down
+ assert !migration.went_up, 'have gone up'
+ assert migration.went_down, 'have not gone down'
+ end
+
def test_migrator_one_up
assert !Person.column_methods_hash.include?(:last_name)
assert !Reminder.table_exists?
@@ -1312,20 +1351,20 @@ if ActiveRecord::Base.connection.supports_migrations?
def test_migrator_verbosity
ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
- assert PeopleHaveLastNames.message_count > 0
+ assert_operator PeopleHaveLastNames.message_count, :>, 0
PeopleHaveLastNames.message_count = 0
ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
- assert PeopleHaveLastNames.message_count > 0
+ assert_operator PeopleHaveLastNames.message_count, :>, 0
PeopleHaveLastNames.message_count = 0
end
def test_migrator_verbosity_off
PeopleHaveLastNames.verbose = false
ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
- assert PeopleHaveLastNames.message_count.zero?
+ assert_equal 0, PeopleHaveLastNames.message_count
ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
- assert PeopleHaveLastNames.message_count.zero?
+ assert_equal 0, PeopleHaveLastNames.message_count
end
def test_migrator_going_down_due_to_version_target
@@ -1947,7 +1986,7 @@ if ActiveRecord::Base.connection.supports_migrations?
@migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
@existing_migrations = Dir[@migrations_path + "/*.rb"]
- Time.travel_to(created_at = Time.utc(2010, 7, 26, 10, 10, 10)) do
+ Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb")
assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb")
@@ -1972,7 +2011,7 @@ if ActiveRecord::Base.connection.supports_migrations?
sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps2"
- Time.travel_to(created_at = Time.utc(2010, 7, 26, 10, 10, 10)) do
+ Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
copied = ActiveRecord::Migration.copy(@migrations_path, sources)
assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb")
assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb")
@@ -1992,7 +2031,7 @@ if ActiveRecord::Base.connection.supports_migrations?
@migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
@existing_migrations = Dir[@migrations_path + "/*.rb"]
- Time.travel_to(created_at = Time.utc(2010, 2, 20, 10, 10, 10)) do
+ Time.travel_to(Time.utc(2010, 2, 20, 10, 10, 10)) do
ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
assert File.exists?(@migrations_path + "/20100301010102_people_have_hobbies.rb")
assert File.exists?(@migrations_path + "/20100301010103_people_have_descriptions.rb")
@@ -2024,11 +2063,26 @@ if ActiveRecord::Base.connection.supports_migrations?
clear
end
+ def test_copying_migrations_to_non_existing_directory
+ @migrations_path = MIGRATIONS_ROOT + "/non_existing"
+ @existing_migrations = []
+
+ Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
+ assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb")
+ assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb")
+ assert_equal 2, copied.length
+ end
+ ensure
+ clear
+ Dir.delete(@migrations_path)
+ end
+
def test_copying_migrations_to_empty_directory
@migrations_path = MIGRATIONS_ROOT + "/empty"
@existing_migrations = []
- Time.travel_to(created_at = Time.utc(2010, 7, 26, 10, 10, 10)) do
+ Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb")
assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb")
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index fb24c65fff..6ac3e3fc56 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -122,7 +122,7 @@ class NamedScopeTest < ActiveRecord::TestCase
:joins => 'JOIN authors ON authors.id = posts.author_id',
:conditions => [ 'authors.author_address_id = ?', address.id ]
)
- assert_equal posts_with_authors_at_address_titles, Post.with_authors_at_address(address).find(:all, :select => 'title')
+ assert_equal posts_with_authors_at_address_titles.map(&:title), Post.with_authors_at_address(address).find(:all, :select => 'title').map(&:title)
end
def test_scope_with_object
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 07262f56be..8ca9d626d1 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -192,7 +192,6 @@ class PersistencesTest < ActiveRecord::TestCase
topic = Topic.create("title" => "New Topic") do |t|
t.author_name = "David"
end
- topicReloaded = Topic.find(topic.id)
assert_equal("New Topic", topic.title)
assert_equal("David", topic.author_name)
end
@@ -270,7 +269,7 @@ class PersistencesTest < ActiveRecord::TestCase
end
def test_record_not_found_exception
- assert_raise(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) }
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(99999) }
end
def test_update_all
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 5bb21a54bd..33916c4e46 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -133,7 +133,6 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
def test_cache_is_expired_by_habtm_delete
ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)
ActiveRecord::Base.cache do
- c = Category.find(1)
p = Post.find(1)
assert p.categories.any?
p.categories.delete_all
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index a27e2e72cd..dae9721a63 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -254,13 +254,11 @@ class HasManyScopingTest< ActiveRecord::TestCase
end
def test_should_maintain_default_scope_on_associations
- person = people(:michael)
magician = BadReference.find(1)
assert_equal [magician], people(:michael).bad_references
end
def test_should_default_scope_on_associations_is_overriden_by_association_conditions
- person = people(:michael)
assert_equal [], people(:michael).fixed_bad_references
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index b44c716db8..535bcd4396 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -426,6 +426,52 @@ class RelationTest < ActiveRecord::TestCase
assert_blank authors.all
end
+ def test_where_with_ar_object
+ author = Author.first
+ authors = Author.scoped.where(:id => author)
+ assert_equal 1, authors.all.length
+ end
+
+ def test_find_with_list_of_ar
+ author = Author.first
+ authors = Author.find([author])
+ assert_equal author, authors.first
+ end
+
+ class Mary < Author; end
+
+ def test_find_by_classname
+ Author.create!(:name => Mary.name)
+ assert_equal 1, Author.where(:name => Mary).size
+ end
+
+ def test_find_by_id_with_list_of_ar
+ author = Author.first
+ authors = Author.find_by_id([author])
+ assert_equal author, authors
+ end
+
+ def test_find_all_using_where_twice_should_or_the_relation
+ david = authors(:david)
+ relation = Author.unscoped
+ relation = relation.where(:name => david.name)
+ relation = relation.where(:name => 'Santiago')
+ relation = relation.where(:id => david.id)
+ assert_equal [david], relation.all
+ end
+
+ def test_find_all_with_multiple_ors
+ david = authors(:david)
+ relation = [
+ { :name => david.name },
+ { :name => 'Santiago' },
+ { :name => 'tenderlove' },
+ ].inject(Author.unscoped) do |memo, param|
+ memo.where(param)
+ end
+ assert_equal [david], relation.all
+ end
+
def test_exists
davids = Author.where(:name => 'David')
assert davids.exists?
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index eb93761fb2..70c098bc6d 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -113,7 +113,7 @@ class TimestampTest < ActiveRecord::TestCase
pet = Pet.first
owner = pet.owner
- owner.update_attribute(:happy_at, (time = 3.days.ago))
+ owner.update_attribute(:happy_at, 3.days.ago)
previously_owner_updated_at = owner.updated_at
pet.name = "I'm a parrot"
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index dd9de3510b..b0ccd71836 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -163,7 +163,7 @@ class TransactionTest < ActiveRecord::TestCase
@first.author_name += '_this_should_not_end_up_in_the_db'
@first.save!
flunk
- rescue => e
+ rescue
assert_equal original_author_name, @first.reload.author_name
assert_equal nbooks_before_save, Book.count
ensure
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index 1246dd4276..56e345990f 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -17,7 +17,7 @@ class AssociationValidationTest < ActiveRecord::TestCase
o = Owner.new('name' => 'nopets')
assert !o.save
assert o.errors[:pets].any?
- pet = o.pets.build('name' => 'apet')
+ o.pets.build('name' => 'apet')
assert o.valid?
end
@@ -27,7 +27,7 @@ class AssociationValidationTest < ActiveRecord::TestCase
assert !o.save
assert o.errors[:pets].any?
- pet = o.pets.build('name' => 'apet')
+ o.pets.build('name' => 'apet')
assert o.valid?
2.times { o.pets.build('name' => 'apet') }
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 9a863c25a8..679d67553b 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -60,7 +60,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
def test_validates_uniqueness_with_validates
Topic.validates :title, :uniqueness => true
- t = Topic.create!('title' => 'abc')
+ Topic.create!('title' => 'abc')
t2 = Topic.new('title' => 'abc')
assert !t2.valid?
@@ -201,7 +201,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
def test_validate_case_sensitive_uniqueness_with_attribute_passed_as_integer
Topic.validates_uniqueness_of(:title, :case_sensitve => true)
- t = Topic.create!('title' => 101)
+ Topic.create!('title' => 101)
t2 = Topic.new('title' => 101)
assert !t2.valid?