diff options
Diffstat (limited to 'activerecord')
34 files changed, 285 insertions, 110 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 3f42fa34ef..d8f68d7c28 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,15 @@ *Rails 3.0.0 [beta 4/release candidate] (unreleased)* +* Add index length support for MySQL. #1852 [Emili Parreno, Pratik Naik] + + Example: + + add_index(:accounts, :name, :name => 'by_name', :length => 10) + => CREATE INDEX by_name ON accounts(name(10)) + + add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15}) + => CREATE INDEX by_name_surname ON accounts(name(10), surname(15)) + * find_or_create_by_attr(value, ...) works when attr is protected. #4457 [Santiago Pastorino, Marc-André Lafortune] * New callbacks: after_commit and after_rollback. Do expensive operations like image thumbnailing after_commit instead of after_save. #2991 [Brian Durand] diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 9d39a0c49b..f23d881c7b 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1399,7 +1399,7 @@ module ActiveRecord primary_key = reflection.source_reflection.primary_key_name send(through.name).select("DISTINCT #{through.quoted_table_name}.#{primary_key}").map!(&:"#{primary_key}") else - send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map!(&:id) + send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").except(:includes).map!(&:id) end end end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index dd44bd8d51..cf4594ad7f 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -8,7 +8,7 @@ module ActiveRecord include AttributeMethods::Write included do - if self < Timestamp + if self < ::ActiveRecord::Timestamp raise "You cannot include Dirty after Timestamp" end diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 411330dda2..82d94b848a 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -6,7 +6,7 @@ module ActiveRecord # Returns this record's primary key value wrapped in an Array # or nil if the record is a new_record? def to_key - new_record? ? nil : [ send(self.class.primary_key) ] + new_record? ? nil : [ id ] end module ClassMethods diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 650a91b385..c02af328c1 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -668,7 +668,6 @@ module ActiveRecord #:nodoc: name = "#{full_table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}" end - @quoted_table_name = nil set_table_name(name) name end @@ -702,6 +701,7 @@ module ActiveRecord #:nodoc: # set_table_name "project" # end def set_table_name(value = nil, &block) + @quoted_table_name = nil define_attr_method :table_name, value, &block end alias :table_name= :set_table_name @@ -1190,7 +1190,11 @@ module ActiveRecord #:nodoc: # default_scope order('last_name, first_name') # end def default_scope(options = {}) - self.default_scoping << construct_finder_arel(options) + self.default_scoping << construct_finder_arel(options, default_scoping.pop) + end + + def clear_default_scope + self.default_scoping.clear end def scoped_methods #:nodoc: @@ -1463,6 +1467,7 @@ module ActiveRecord #:nodoc: @attributes_cache = {} @new_record = true ensure_proper_type + @changed_attributes = other.changed_attributes.dup if scope = self.class.send(:current_scoped_methods) create_with = scope.scope_for_create 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 6c477e48ce..7d58bc2adf 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -258,7 +258,7 @@ module ActiveRecord end end - class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc: + class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc: end # Abstract representation of a column definition. Instances of this type @@ -494,7 +494,7 @@ module ActiveRecord end %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type| - class_eval <<-EOV + class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{column_type}(*args) # def string(*args) options = args.extract_options! # options = args.extract_options! column_names = args # column_names = args @@ -694,7 +694,7 @@ module ActiveRecord # t.string(:goat) # t.string(:goat, :sheep) %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type| - class_eval <<-EOV + class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{column_type}(*args) # def string(*args) options = args.extract_options! # options = args.extract_options! column_names = args # column_names = args diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index e8cba1bd41..1255ef09be 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -256,18 +256,32 @@ module ActiveRecord # name. # # ===== Examples + # # ====== Creating a simple index # add_index(:suppliers, :name) # generates # CREATE INDEX suppliers_name_index ON suppliers(name) + # # ====== Creating a unique index # add_index(:accounts, [:branch_id, :party_id], :unique => true) # generates # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id) + # # ====== Creating a named index # add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party') # generates # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id) + # + # ====== Creating an index with specific key length + # add_index(:accounts, :name, :name => 'by_name', :length => 10) + # generates + # CREATE INDEX by_name ON accounts(name(10)) + # + # add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15}) + # generates + # CREATE INDEX by_name_surname ON accounts(name(10), surname(15)) + # + # Note: SQLite doesn't support index length def add_index(table_name, column_name, options = {}) column_names = Array.wrap(column_name) index_name = index_name(table_name, :column => column_names) @@ -278,7 +292,9 @@ module ActiveRecord else index_type = options end - quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ") + + quoted_column_names = quoted_columns_for_index(column_names, options).join(", ") + execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})" end @@ -430,6 +446,11 @@ module ActiveRecord end protected + # Overridden by the mysql adapter for supporting index lengths + def quoted_columns_for_index(column_names, options = {}) + column_names.map {|name| quote_column_name(name) } + end + def options_include_default?(options) options.include?(:default) && !(options[:null] == false && options[:default].nil?) end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 55d9d20bb5..e12924e63f 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -15,26 +15,26 @@ module MysqlCompat #:nodoc: # Ruby driver has a version string and returns null values in each_hash # C driver >= 2.7 returns null values in each_hash if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700) - target.class_eval <<-'end_eval' - def all_hashes # def all_hashes - rows = [] # rows = [] - each_hash { |row| rows << row } # each_hash { |row| rows << row } - rows # rows - end # end + target.class_eval <<-'end_eval', __FILE__, __LINE__ + 1 + def all_hashes # def all_hashes + rows = [] # rows = [] + each_hash { |row| rows << row } # each_hash { |row| rows << row } + rows # rows + end # end end_eval # adapters before 2.7 don't have a version constant # and don't return null values in each_hash else - target.class_eval <<-'end_eval' - def all_hashes # def all_hashes - rows = [] # rows = [] - all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f| - fields[f.name] = nil; fields # fields[f.name] = nil; fields - } # } - each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) } - rows # rows - end # end + target.class_eval <<-'end_eval', __FILE__, __LINE__ + 1 + def all_hashes # def all_hashes + rows = [] # rows = [] + all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f| + fields[f.name] = nil; fields # fields[f.name] = nil; fields + } # } + each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) } + rows # rows + end # end end_eval end @@ -461,10 +461,11 @@ module ActiveRecord if current_index != row[2] next if row[2] == "PRIMARY" # skip the primary key current_index = row[2] - indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", []) + indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], []) end indexes.last.columns << row[4] + indexes.last.lengths << row[7] end result.free indexes @@ -512,7 +513,7 @@ module ActiveRecord def change_column(table_name, column_name, type, options = {}) #:nodoc: column = column_for(table_name, column_name) - unless options_include_default?(options) + if has_default?(type) && !options_include_default?(options) options[:default] = column.default end @@ -594,6 +595,18 @@ module ActiveRecord end protected + def quoted_columns_for_index(column_names, options = {}) + length = options[:length] if options.is_a?(Hash) + + quoted_column_names = case length + when Hash + column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) } + when Fixnum + column_names.map {|name| "#{quote_column_name(name)}(#{length})"} + else + column_names.map {|name| quote_column_name(name) } + end + end def translate_exception(exception, message) return super unless exception.respond_to?(:errno) @@ -662,6 +675,10 @@ module ActiveRecord end column end + + def has_default?(sql_type) + sql_type =~ :binary || sql_type == :text #mysql forbids defaults on blob and text columns + end end end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 4bf33c3856..8099aaa7f7 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -891,6 +891,7 @@ module ActiveRecord instances.size == 1 ? instances.first : instances end + private table_name end end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 71057efa15..ceb0902fde 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -113,12 +113,11 @@ module ActiveRecord lock_col = self.class.locking_column previous_value = send(lock_col).to_i - affected_rows = connection.delete( - "DELETE FROM #{self.class.quoted_table_name} " + - "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id} " + - "AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}", - "#{self.class.name} Destroy" - ) + table = self.class.arel_table + predicate = table[self.class.primary_key].eq(id) + predicate = predicate.and(table[self.class.locking_column].eq(previous_value)) + + affected_rows = self.class.unscoped.where(predicate).delete_all unless affected_rows == 1 raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}" diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index aeed52e72a..3d8f4a030b 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -99,11 +99,7 @@ module ActiveRecord block_given? ? relation.extending(Module.new(&block)) : relation end - singleton_class.instance_eval do - define_method name do |*args| - scopes[name].call(*args) - end - end + singleton_class.send :define_method, name, &scopes[name] end def named_scope(*args, &block) diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 898df0a67a..a32fb7d399 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -15,6 +15,12 @@ module ActiveRecord config.generators.orm :active_record, :migration => true, :timestamps => true + config.app_middleware.insert_after "::ActionDispatch::Callbacks", + "ActiveRecord::QueryCache" + + config.app_middleware.insert_after "::ActionDispatch::Callbacks", + "ActiveRecord::ConnectionAdapters::ConnectionManagement" + rake_tasks do load "active_record/railties/databases.rake" end @@ -58,16 +64,9 @@ module ActiveRecord end end - # Setup database middleware after initializers have run - initializer "active_record.initialize_database_middleware", :after => "action_controller.set_configs" do |app| - middleware = app.config.middleware - middleware.insert_after "::ActionDispatch::Callbacks", ActiveRecord::QueryCache - middleware.insert_after "::ActionDispatch::Callbacks", ActiveRecord::ConnectionAdapters::ConnectionManagement - end - initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app| - ActiveSupport.on_load(:active_record) do - unless app.config.cache_classes + unless app.config.cache_classes + ActiveSupport.on_load(:active_record) do ActionDispatch::Callbacks.after do ActiveRecord::Base.reset_subclasses ActiveRecord::Base.clear_reloadable_connections! diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 1c61e7d450..412be895c4 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -71,7 +71,12 @@ module ActiveRecord yield records break if records.size < batch_size - records = relation.where(primary_key.gt(records.last.id)).all + + if primary_key_offset = records.last.id + records = relation.where(primary_key.gt(primary_key_offset)).all + else + raise "Primary key not included in the custom select clause" + end end end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 858d298470..44baeb6c84 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -45,9 +45,8 @@ module ActiveRecord calculate(:count, column_name, options) end - # Calculates the average value on a given column. The value is returned as - # a float, or +nil+ if there's no row. See +calculate+ for examples with - # options. + # Calculates the average value on a given column. Returns +nil+ if there's + # no row. See +calculate+ for examples with options. # # Person.average('age') # => 35.8 def average(column_name, options = {}) @@ -241,9 +240,9 @@ module ActiveRecord def type_cast_calculated_value(value, column, operation = nil) if value.is_a?(String) || value.nil? case operation - when 'count' then value.to_i - when 'sum' then type_cast_using_column(value || '0', column) - when 'average' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d + when 'count' then value.to_i + when 'sum' then type_cast_using_column(value || '0', column) + when 'average' then value.try(:to_d) else type_cast_using_column(value, column) end else diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 8d8bb659e1..6782554854 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -10,7 +10,7 @@ module ActiveRecord attr_accessor :"#{query_method}_values" next if [:where, :having].include?(query_method) - class_eval <<-CEVAL, __FILE__ + class_eval <<-CEVAL, __FILE__, __LINE__ + 1 def #{query_method}(*args, &block) new_relation = clone new_relation.send(:apply_modules, Module.new(&block)) if block_given? @@ -22,7 +22,7 @@ module ActiveRecord end [:where, :having].each do |query_method| - class_eval <<-CEVAL, __FILE__ + class_eval <<-CEVAL, __FILE__, __LINE__ + 1 def #{query_method}(*args, &block) new_relation = clone new_relation.send(:apply_modules, Module.new(&block)) if block_given? @@ -36,7 +36,7 @@ module ActiveRecord ActiveRecord::Relation::SINGLE_VALUE_METHODS.each do |query_method| attr_accessor :"#{query_method}_value" - class_eval <<-CEVAL, __FILE__ + class_eval <<-CEVAL, __FILE__, __LINE__ + 1 def #{query_method}(value = true, &block) new_relation = clone new_relation.send(:apply_modules, Module.new(&block)) if block_given? diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index c8e1b4f53a..cd54653581 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -178,6 +178,9 @@ HEADER statment_parts << (':name => ' + index.name.inspect) statment_parts << ':unique => true' if index.unique + index_lengths = index.lengths.compact if index.lengths.is_a?(Array) + statment_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect) if index_lengths.present? + ' ' + statment_parts.join(', ') end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 55c4236874..be64e00bd1 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -36,49 +36,40 @@ module ActiveRecord # The validation process on save can be skipped by passing false. The regular Base#save method is # replaced with this when the validations module is mixed in, which it is by default. - def save(options=nil) - return super if valid?(options) - false - end - - def save_without_validation! - save!(:validate => false) + def save(options={}) + perform_validations(options) ? super : false end # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false # if the record is not valid. - def save!(options = nil) - return super if valid?(options) - raise RecordInvalid.new(self) + def save!(options={}) + perform_validations(options) ? super : raise(RecordInvalid.new(self)) end # Runs all the specified validations and returns true if no errors were added otherwise false. - def valid?(options = nil) - perform_validation = case options - when NilClass - true - when Hash - options[:validate] != false - else - ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller - options - end + def valid?(context = nil) + context ||= (new_record? ? :create : :update) + super(context) - if perform_validation - errors.clear + deprecated_callback_method(:validate) + deprecated_callback_method(:"validate_on_#{context}") - self.validation_context = new_record? ? :create : :update - _run_validate_callbacks + errors.empty? + end - deprecated_callback_method(:validate) + protected - if new_record? - deprecated_callback_method(:validate_on_create) - else - deprecated_callback_method(:validate_on_update) - end + def perform_validations(options={}) + perform_validation = case options + when Hash + options[:validate] != false + else + ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller + options + end - errors.empty? + if perform_validation + valid?(options.is_a?(Hash) ? options[:context] : nil) else true end diff --git a/activerecord/test/cases/active_schema_test_mysql.rb b/activerecord/test/cases/active_schema_test_mysql.rb index 9aff538ce9..f4d123be15 100644 --- a/activerecord/test/cases/active_schema_test_mysql.rb +++ b/activerecord/test/cases/active_schema_test_mysql.rb @@ -15,6 +15,23 @@ class ActiveSchemaTest < ActiveRecord::TestCase end end + def test_add_index + expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)" + assert_equal expected, add_index(:people, :last_name, :length => nil) + + expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))" + assert_equal expected, add_index(:people, :last_name, :length => 10) + + expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))" + assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15) + + expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)" + assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15}) + + expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))" + assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10}) + end + def test_drop_table assert_equal "DROP TABLE `people`", drop_table(:people) end diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb index e8db6d5dab..2beb3f8365 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -17,7 +17,7 @@ module Remembered module ClassMethods def remembered; @@remembered ||= []; end - def rand; @@remembered.rand; end + def random_element; @@remembered.random_element; end end end @@ -79,14 +79,14 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase [Circle, Square, Triangle, NonPolyOne, NonPolyTwo].map(&:create!) end 1.upto(NUM_SIMPLE_OBJS) do - PaintColor.create!(:non_poly_one_id => NonPolyOne.rand.id) - PaintTexture.create!(:non_poly_two_id => NonPolyTwo.rand.id) + PaintColor.create!(:non_poly_one_id => NonPolyOne.random_element.id) + PaintTexture.create!(:non_poly_two_id => NonPolyTwo.random_element.id) end 1.upto(NUM_SHAPE_EXPRESSIONS) do - shape_type = [Circle, Square, Triangle].rand - paint_type = [PaintColor, PaintTexture].rand - ShapeExpression.create!(:shape_type => shape_type.to_s, :shape_id => shape_type.rand.id, - :paint_type => paint_type.to_s, :paint_id => paint_type.rand.id) + shape_type = [Circle, Square, Triangle].random_element + paint_type = [PaintColor, PaintTexture].random_element + ShapeExpression.create!(:shape_type => shape_type.to_s, :shape_id => shape_type.random_element.id, + :paint_type => paint_type.to_s, :paint_id => paint_type.random_element.id) 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 b55b08bf9d..6e47967696 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -900,6 +900,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert !company.clients.loaded? end + def test_get_ids_ignores_include_option + assert_equal [readers(:michael_welcome).id], posts(:welcome).readers_with_person_ids + end + def test_get_ids_for_unloaded_finder_sql_associations_loads_them company = companies(:first_firm) assert !company.clients_using_sql.loaded? diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 8bdf8bcd55..dca72be4fd 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -289,7 +289,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_array_methods_called_by_method_missing - assert true, authors(:david).categories.any? { |category| category.name == 'General' } + assert authors(:david).categories.any? { |category| category.name == 'General' } assert_nothing_raised { authors(:david).categories.sort } end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 2995cc6f72..5cc5dff633 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -765,7 +765,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase @ship.destroy @pirate.reload.catchphrase = "Arr" @pirate.save - assert 'Arr', @pirate.reload.catchphrase + assert_equal 'Arr', @pirate.reload.catchphrase end def test_should_automatically_save_the_associated_model @@ -885,7 +885,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase @pirate.destroy @ship.reload.name = "The Vile Insanity" @ship.save - assert 'The Vile Insanity', @ship.reload.name + assert_equal 'The Vile Insanity', @ship.reload.name end def test_should_automatically_save_the_associated_model diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index bbc4e543d5..b7ae619787 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1793,6 +1793,18 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "bar", k.table_name end + def test_quoted_table_name_after_set_table_name + klass = Class.new(ActiveRecord::Base) + + klass.set_table_name "foo" + assert_equal "foo", klass.table_name + assert_equal klass.connection.quote_table_name("foo"), klass.quoted_table_name + + klass.set_table_name "bar" + assert_equal "bar", klass.table_name + assert_equal klass.connection.quote_table_name("bar"), klass.quoted_table_name + end + def test_set_table_name_with_block k = Class.new( ActiveRecord::Base ) k.set_table_name { "ks" } diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 83deabb5b7..dcc49e12ca 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -17,6 +17,20 @@ class EachTest < ActiveRecord::TestCase end end + def test_each_should_raise_if_select_is_set_without_id + assert_raise(RuntimeError) do + Post.find_each(:select => :title, :batch_size => 1) { |post| post } + end + end + + def test_each_should_execute_if_id_is_in_select + assert_queries(4) do + Post.find_each(:select => "id, title, type", :batch_size => 2) do |post| + assert_kind_of Post, post + end + end + end + def test_each_should_raise_if_the_order_is_set assert_raise(RuntimeError) do Post.find_each(:order => "title") { |post| post } diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 28a1ae5feb..8473150338 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -20,8 +20,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_average_field value = Account.average(:credit_limit) - assert_kind_of BigDecimal, value - assert_equal BigDecimal.new('53.0'), value + assert_equal 53.0, value end def test_should_return_nil_as_average diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 7a17ef1ee0..3ea2948f62 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -338,6 +338,15 @@ class DirtyTest < ActiveRecord::TestCase assert !pirate.changed? end + def test_cloned_objects_should_not_copy_dirty_flag_from_creator + pirate = Pirate.create!(:catchphrase => "shiver me timbers") + pirate_clone = pirate.clone + pirate_clone.reset_catchphrase! + pirate.catchphrase = "I love Rum" + assert pirate.catchphrase_changed? + assert !pirate_clone.catchphrase_changed? + end + def test_reverted_changes_are_not_dirty phrase = "shiver me timbers" pirate = Pirate.create!(:catchphrase => phrase) diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index e78db8969d..c73ad50a71 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -722,10 +722,10 @@ class FinderTest < ActiveRecord::TestCase def test_find_all_by_one_attribute_with_options topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC") - assert topics(:first), topics.last + assert_equal topics(:first), topics.last topics = Topic.find_all_by_content("Have a nice day", :order => "id") - assert topics(:first), topics.first + assert_equal topics(:first), topics.first end def test_find_all_by_array_attribute diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 3ce23209cc..8008b86f81 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -256,6 +256,11 @@ class FixturesWithoutInstantiationTest < ActiveRecord::TestCase def test_fixtures_from_root_yml_without_instantiation assert !defined?(@unknown), "@unknown is not defined" end + + def test_visibility_of_accessor_method + assert_equal false, respond_to?(:topics, false), "should be private method" + assert_equal true, respond_to?(:topics, true), "confirm to respond surely" + end def test_accessor_methods assert_equal "The First Topic", topics(:first).title diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index a3b496a0e6..3a6354ec6d 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -587,6 +587,18 @@ class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase end end +class ClearDefaultScopeTest < ActiveRecord::TestCase + fixtures :developers + + def test_should_clear_default_scope + klass = Class.new(DeveloperCalledDavid) + klass.__send__ :clear_default_scope + expected = Developer.all.collect { |dev| dev.name } + actual = klass.all.collect { |dev| dev.name } + assert_equal expected, actual + end +end + class DefaultScopingTest < ActiveRecord::TestCase fixtures :developers, :posts @@ -615,15 +627,30 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_default_scoping_with_inheritance # Inherit a class having a default scope and define a new default scope klass = Class.new(DeveloperOrderedBySalary) - klass.send :default_scope, {} + klass.send :default_scope, :limit => 1 # Scopes added on children should append to parent scope - assert klass.scoped.order_values.blank? + assert_equal 1, klass.scoped.limit_value + assert_equal ['salary DESC'], klass.scoped.order_values # Parent should still have the original scope + assert_equal nil, DeveloperOrderedBySalary.scoped.limit_value assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values end + def test_default_scope_called_twice_merges_conditions + Developer.destroy_all + Developer.create!(:name => "David", :salary => 80000) + Developer.create!(:name => "David", :salary => 100000) + Developer.create!(:name => "Brian", :salary => 100000) + + klass = Class.new(Developer) + klass.__send__ :default_scope, :conditions => { :name => "David" } + klass.__send__ :default_scope, :conditions => { :salary => 100000 } + assert_equal 1, klass.count + assert_equal "David", klass.first.name + assert_equal 100000, klass.first.salary + end def test_method_scope expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary } received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary } diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index a3d1ceaa1f..768a44f6df 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -92,6 +92,14 @@ if ActiveRecord::Base.connection.supports_migrations? assert_nothing_raised { Person.connection.remove_index("people", "last_name_and_first_name") } assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) } assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } + assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => 10) } + assert_nothing_raised { Person.connection.remove_index("people", "last_name") } + assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => {:last_name => 10}) } + assert_nothing_raised { Person.connection.remove_index("people", ["last_name"]) } + assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => 10) } + assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } + assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) } + assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } end # quoting @@ -852,6 +860,18 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal "Tester", Person.new.first_name end + unless current_adapter?(:PostgreSQLAdapter) + def test_change_column_type_default_should_change + old_columns = Person.connection.columns(Person.table_name, "#{name} Columns") + assert !old_columns.find { |c| c.name == 'data' } + + assert_nothing_raised do + Person.connection.add_column "people", "data", :string, :default => '' + Person.connection.change_column "people", "data", :binary + end + end + end + def test_change_column_quotes_column_names Person.connection.create_table :testings do |t| t.column :select, :string diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index e4cafad11e..2007f5492f 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -142,6 +142,11 @@ class NamedScopeTest < ActiveRecord::TestCase assert_equal authors(:david).posts & Post.containing_the_letter_a, authors(:david).posts.containing_the_letter_a end + def test_named_scope_with_STI + assert_equal 3,Post.containing_the_letter_a.count + assert_equal 1,SpecialPost.containing_the_letter_a.count + end + def test_has_many_through_associations_have_access_to_named_scopes assert_not_equal Comment.containing_the_letter_e, authors(:david).comments assert !Comment.containing_the_letter_e.empty? @@ -296,7 +301,7 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_rand_should_select_a_random_object_from_proxy - assert_kind_of Topic, Topic.approved.rand + assert_kind_of Topic, Topic.approved.random_element end def test_should_use_where_in_query_for_named_scope diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index e1fb911cc9..3a34df2426 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -62,6 +62,23 @@ class ValidationsTest < ActiveRecord::TestCase assert_equal ["is Wrong Update"], r.errors[:title], "A reply with a bad content should contain an error" end + def test_error_on_given_context + r = WrongReply.new + assert !r.valid?(:special_case) + assert "Invalid", r.errors[:title].join + + r.title = "secret" + r.content = "Good" + assert r.valid?(:special_case) + + r.title = nil + assert !r.save(:context => :special_case) + assert "Invalid", r.errors[:title].join + + r.title = "secret" + assert r.save(:context => :special_case) + end + def test_invalid_record_exception assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! } assert_raise(ActiveRecord::RecordInvalid) { WrongReply.new.save! } @@ -135,12 +152,6 @@ class ValidationsTest < ActiveRecord::TestCase end end - def test_create_without_validation_bang - count = WrongReply.count - assert_nothing_raised { WrongReply.new.save_without_validation! } - assert count+1, WrongReply.count - end - def test_validates_acceptance_of_with_non_existant_table Object.const_set :IncorporealModel, Class.new(ActiveRecord::Base) diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index d092c4bf09..dd06822cfd 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -69,6 +69,7 @@ class Post < ActiveRecord::Base has_many :authors, :through => :categorizations has_many :readers + has_many :readers_with_person, :include => :person, :class_name => "Reader" has_many :people, :through => :readers has_many :people_with_callbacks, :source=>:person, :through => :readers, :before_add => lambda {|owner, reader| log(:added, :before, reader.first_name) }, diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb index 264a49b465..6cc9ee038a 100644 --- a/activerecord/test/models/reply.rb +++ b/activerecord/test/models/reply.rb @@ -17,6 +17,7 @@ class WrongReply < Reply validate :check_empty_title validate :check_content_mismatch, :on => :create validate :check_wrong_update, :on => :update + validate :check_title_is_secret, :on => :special_case def check_empty_title errors[:title] << "Empty" unless attribute_present?("title") @@ -39,6 +40,10 @@ class WrongReply < Reply def check_wrong_update errors[:title] << "is Wrong Update" if attribute_present?("title") && title == "Wrong Update" end + + def check_title_is_secret + errors[:title] << "Invalid" unless title == "secret" + end end class SillyReply < Reply |