diff options
Diffstat (limited to 'activerecord')
60 files changed, 759 insertions, 229 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 98d2fa62d7..c346b5d93d 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,28 @@ +* Setting a has_one association on a new record no longer causes an empty + transaction. + + *Dylan Thacker-Smith* + +* Fix `AR::Relation#merge` sometimes failing to preserve `readonly(false)` flag. + + *thedarkone* + +* Re-use `order` argument pre-processing for `reorder`. + + *Paul Nikitochkin* + +* Fix PredicateBuilder so polymorphic association keys in `where` clause can + accept objects other than direct descendants of `ActiveRecord::Base` (decorated + models, for example). + + *Mikhail Dieterle* + +* PostgreSQL adapter recognizes negative money values formatted with + parentheses (eg. `($1.25) # => -1.25`)). + Fixes #11899. + + *Yves Senn* + * Stop interpreting SQL 'string' columns as :string type because there is no common STRING datatype in SQL. @@ -12,7 +37,6 @@ Example: record = User.new - record.logged_in_from_ip # is type of an inet or a cidr # Before: diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc index c3ee34da55..ca1f2fd665 100644 --- a/activerecord/RUNNING_UNIT_TESTS.rdoc +++ b/activerecord/RUNNING_UNIT_TESTS.rdoc @@ -24,6 +24,7 @@ Simply executing <tt>bundle exec rake test</tt> is equivalent to the following: $ bundle exec rake test_mysql2 $ bundle exec rake test_postgresql $ bundle exec rake test_sqlite3 + $ bundle exec rake test_sqlite3_mem There should be tests available for each database backend listed in the {Config File}[rdoc-label:label-Config+File]. (the exact set of available tests is diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 5ceda933f2..33cbafc6aa 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -164,7 +164,7 @@ module ActiveRecord private # Returns the specified association instance if it responds to :loaded?, nil otherwise. def association_instance_get(name) - @association_cache[name.to_sym] + @association_cache[name] end # Set the specified association instance. diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 8eec4f56af..e1fa5225b5 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -50,8 +50,8 @@ module ActiveRecord # Checks whether record is different to the current target, without loading it def different_target?(record) - if record.nil? - owner[reflection.foreign_key] + if record.nil? + owner[reflection.foreign_key] else record.id != owner[reflection.foreign_key] end diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index 34de1a1f32..1059fc032d 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -17,7 +17,7 @@ module ActiveRecord::Associations::Builder end self.extensions = [] - VALID_OPTIONS = [:class_name, :foreign_key, :validate] + VALID_OPTIONS = [:class_name, :class, :foreign_key, :validate] attr_reader :name, :scope, :options diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 228c500f0a..8744a57355 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -542,7 +542,8 @@ module ActiveRecord def include_in_memory?(record) if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection) - owner.send(reflection.through_reflection.name).any? { |source| + assoc = owner.association(reflection.through_reflection.name) + assoc.reader.any? { |source| target = source.send(reflection.source_reflection.name) target.respond_to?(:include?) ? target.include?(record) : target == record } || target.include?(record) diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 607ed0da46..a3fcca8a27 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -125,7 +125,11 @@ module ActiveRecord end def foreign_key_present? - owner.attribute_present?(reflection.association_primary_key) + if reflection.klass.primary_key + owner.attribute_present?(reflection.association_primary_key) + else + false + end end end end diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index a74dd1cdab..56331bbb0b 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -140,7 +140,21 @@ module ActiveRecord case method when :destroy - count = scope.destroy_all.length + if scope.klass.primary_key + count = scope.destroy_all.length + else + scope.to_a.each do |record| + record.run_callbacks :destroy + end + + arel = scope.arel + + stmt = Arel::DeleteManager.new arel.engine + stmt.from scope.klass.arel_table + stmt.wheres = arel.constraints + + count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values) + end when :nullify count = scope.update_all(source_reflection.foreign_key => nil) else diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 3ab1ea1ff4..0008600418 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -27,6 +27,8 @@ module ActiveRecord return self.target if !(target || record) if (target != record) || record.changed? + save &&= owner.persisted? + transaction_if(save) do remove_target!(options[:dependent]) if target && !target.destroyed? @@ -34,7 +36,7 @@ module ActiveRecord set_owner_attributes(record) set_inverse_instance(record) - if owner.persisted? && save && !record.save + if save && !record.save nullify_owner_attributes(record) set_owner_attributes(target) if target raise RecordNotSaved, "Failed to save the new associated #{reflection.name}." diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb index b534569063..8024105472 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb @@ -62,7 +62,20 @@ module ActiveRecord end def extract_record(row) - Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}] + # This code is performance critical as it is called per row. + # see: https://github.com/rails/rails/pull/12185 + hash = {} + + index = 0 + length = column_names_with_alias.length + + while index < length + column_name, alias_name = column_names_with_alias[index] + hash[column_name] = row[alias_name] + index += 1 + end + + hash end def record_id(row) diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 2317e34bc0..6cc9d6c079 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -85,9 +85,11 @@ module ActiveRecord def initialize(records, associations, preload_scope = nil) @records = Array.wrap(records).compact.uniq @associations = Array.wrap(associations) - @preload_scope = preload_scope || Relation.create(nil, nil) + @preload_scope = preload_scope || NULL_RELATION end + NULL_RELATION = Struct.new(:values).new({}) + def run unless records.empty? associations.each { |association| preload(association) } diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 0cc836f991..928da71eed 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -29,6 +29,10 @@ module ActiveRecord end def records_for(ids) + query_scope(ids) + end + + def query_scope(ids) scope.where(association_key.in(ids)) end @@ -52,12 +56,9 @@ module ActiveRecord raise NotImplementedError end - # We're converting to a string here because postgres will return the aliased association - # key in a habtm as a string (for whatever reason) def owners_by_key @owners_by_key ||= owners.group_by do |owner| - key = owner[owner_key_name] - key && key.to_s + owner[owner_key_name] end end @@ -71,27 +72,34 @@ module ActiveRecord owners_map = owners_by_key owner_keys = owners_map.keys.compact - if klass.nil? || owner_keys.empty? - records = [] - else + # Each record may have multiple owners, and vice-versa + records_by_owner = Hash[owners.map { |owner| [owner, []] }] + + if klass && owner_keys.any? # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000) # Make several smaller queries if necessary or make one query if the adapter supports it sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size) - records = sliced.flat_map { |slice| records_for(slice).to_a } + sliced.each { |slice| + records = records_for(slice) + caster = type_caster(records, association_key_name) + records.each do |record| + owner_key = caster.call record[association_key_name] + + owners_map[owner_key].each do |owner| + records_by_owner[owner] << record + end + end + } end - # Each record may have multiple owners, and vice-versa - records_by_owner = Hash[owners.map { |owner| [owner, []] }] - records.each do |record| - owner_key = record[association_key_name].to_s - - owners_map[owner_key].each do |owner| - records_by_owner[owner] << record - end - end records_by_owner end + IDENTITY = lambda { |value| value } + def type_caster(results, name) + IDENTITY + end + def reflection_scope @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped end diff --git a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb index 9a3fada380..c042a44b21 100644 --- a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb @@ -12,7 +12,7 @@ module ActiveRecord # Unlike the other associations, we want to get a raw array of rows so that we can # access the aliased column on the join table def records_for(ids) - scope = super + scope = query_scope ids klass.connection.select_all(scope.arel, 'SQL', scope.bind_values) end @@ -40,6 +40,11 @@ module ActiveRecord end end + def type_caster(results, name) + caster = results.column_types.fetch(name, results.identity_type) + lambda { |value| caster.type_cast value } + end + def build_scope super.joins(join).select(join_select) end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index de06931845..2c625cec04 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -27,17 +27,16 @@ module ActiveRecord def through_records_by_owner Preloader.new(owners, through_reflection.name, through_scope).run - Hash[owners.map do |owner| - through_records = Array.wrap(owner.send(through_reflection.name)) + should_reset = (through_scope != through_reflection.klass.unscoped) || + (reflection.options[:source_type] && through_reflection.collection?) - # Dont cache the association - we would only be caching a subset - if (through_scope != through_reflection.klass.unscoped) || - (reflection.options[:source_type] && through_reflection.collection?) - owner.association(through_reflection.name).reset - end + owners.each_with_object({}) do |owner, h| + association = owner.association through_reflection.name + h[owner] = Array(association.reader) - [owner, through_records] - end] + # Dont cache the association - we would only be caching a subset + association.reset if should_reset + end end def through_scope diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index 10238555f0..02dc464536 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -42,7 +42,6 @@ module ActiveRecord scope.first.tap { |record| set_inverse_instance(record) } end - # Implemented by subclasses def replace(record) raise NotImplementedError, "Subclasses must implement a replace(record) method" 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 41b5a6e926..f168282ea3 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -51,7 +51,7 @@ module ActiveRecord def create_time_zone_conversion_attribute?(name, column) time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && - [:datetime, :timestamp].include?(column.type) + (:datetime == column.type || :timestamp == column.type) end end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b06add096f..04e3dd49e7 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -18,6 +18,7 @@ require 'arel' require 'active_record/errors' require 'active_record/log_subscriber' require 'active_record/explain_subscriber' +require 'active_record/relation/delegation' module ActiveRecord #:nodoc: # = Active Record @@ -290,6 +291,7 @@ module ActiveRecord #:nodoc: extend Translation extend DynamicMatchers extend Explain + extend Delegation::DelegateCache include Persistence include ReadonlyAttributes diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index c8fa4ef9af..552a22d28a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -51,7 +51,6 @@ module ActiveRecord return value unless column case column.type - when :binary then value when :integer then value.to_i when :float then value.to_f else diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 751655e61c..527d13b9b0 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -139,7 +139,8 @@ module ActiveRecord exec_cache(sql, binds) types = {} - result.fields.each_with_index do |fname, i| + fields = result.fields + fields.each_with_index do |fname, i| ftype = result.ftype i fmod = result.fmod i types[fname] = OID::TYPE_MAP.fetch(ftype, fmod) { |oid, mod| @@ -148,7 +149,7 @@ module ActiveRecord } end - ret = ActiveRecord::Result.new(result.fields, result.values, types) + ret = ActiveRecord::Result.new(fields, result.values, types) result.clear return ret end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 58c6235059..dab876af14 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -34,12 +34,17 @@ module ActiveRecord class Money < Type def type_cast(value) return if value.nil? + return value unless String === value # Because money output is formatted according to the locale, there are two # cases to consider (note the decimal separators): # (1) $12,345,678.12 # (2) $12.345.678,12 + # Negative values are represented as follows: + # (3) -$2.55 + # (4) ($2.55) + value.sub!(/^\((.+)\)$/, '-\1') # (4) case value when /^-?\D+[\d,]+\.\d{2}$/ # (1) value.gsub!(/[^-\d.]/, '') diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index b2a81a184a..9a26e5df3f 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -379,16 +379,16 @@ module ActiveRecord @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} } - def self.default_fixture_model_name(fixture_set_name) # :nodoc: - ActiveRecord::Base.pluralize_table_names ? + def self.default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc: + config.pluralize_table_names ? fixture_set_name.singularize.camelize : fixture_set_name.camelize end - def self.default_fixture_table_name(fixture_set_name) # :nodoc: - "#{ ActiveRecord::Base.table_name_prefix }"\ + def self.default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc: + "#{ config.table_name_prefix }"\ "#{ fixture_set_name.tr('/', '_') }"\ - "#{ ActiveRecord::Base.table_name_suffix }".to_sym + "#{ config.table_name_suffix }".to_sym end def self.reset_cache @@ -436,9 +436,47 @@ module ActiveRecord cattr_accessor :all_loaded_fixtures self.all_loaded_fixtures = {} - def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}) + class ClassCache + def initialize(class_names, config) + @class_names = class_names.stringify_keys + @config = config + + # Remove string values that aren't constants or subclasses of AR + @class_names.delete_if { |k,klass| + unless klass.is_a? Class + klass = klass.safe_constantize + ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name will be removed in Rails 4.2, consider using the class itself instead.") + end + !insert_class(@class_names, k, klass) + } + end + + def [](fs_name) + @class_names.fetch(fs_name) { + klass = default_fixture_model(fs_name, @config).safe_constantize + insert_class(@class_names, fs_name, klass) + } + end + + private + + def insert_class(class_names, name, klass) + # We only want to deal with AR objects. + if klass && klass < ActiveRecord::Base + class_names[name] = klass + else + class_names[name] = nil + end + end + + def default_fixture_model(fs_name, config) + ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config) + end + end + + def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base) fixture_set_names = Array(fixture_set_names).map(&:to_s) - class_names = class_names.stringify_keys + class_names = ClassCache.new class_names, config # FIXME: Apparently JK uses this. connection = block_given? ? yield : ActiveRecord::Base.connection @@ -452,10 +490,12 @@ module ActiveRecord fixtures_map = {} fixture_sets = files_to_read.map do |fs_name| + klass = class_names[fs_name] + conn = klass ? klass.connection : connection fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new - connection, + conn, fs_name, - class_names[fs_name] || default_fixture_model_name(fs_name), + klass, ::File.join(fixtures_directory, fs_name)) end @@ -497,27 +537,31 @@ module ActiveRecord Zlib.crc32(label.to_s) % MAX_ID end - attr_reader :table_name, :name, :fixtures, :model_class + attr_reader :table_name, :name, :fixtures, :model_class, :config - def initialize(connection, name, class_name, path) - @fixtures = {} # Ordered hash + def initialize(connection, name, class_name, path, config = ActiveRecord::Base) @name = name @path = path + @config = config + @model_class = nil + + if class_name.is_a?(String) + ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name will be removed in Rails 4.2, consider using the class itself instead.") + end if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any? @model_class = class_name else - @model_class = class_name.constantize rescue nil + @model_class = class_name.safe_constantize if class_name end - @connection = ( model_class.respond_to?(:connection) ? - model_class.connection : connection ) + @connection = connection @table_name = ( model_class.respond_to?(:table_name) ? model_class.table_name : - self.class.default_fixture_table_name(name) ) + self.class.default_fixture_table_name(name, config) ) - read_fixture_files + @fixtures = read_fixture_files path, @model_class end def [](x) @@ -539,7 +583,7 @@ module ActiveRecord # Return a hash of rows to be inserted. The key is the table, the value is # a list of rows to insert to that table. def table_rows - now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now + now = config.default_timezone == :utc ? Time.now.utc : Time.now now = now.to_s(:db) # allow a standard key to be used for doing defaults in YAML @@ -551,7 +595,7 @@ module ActiveRecord rows[table_name] = fixtures.map do |label, fixture| row = fixture.to_hash - if model_class && model_class < ActiveRecord::Base + if model_class # fill in timestamp columns if they aren't specified and the model is set to record_timestamps if model_class.record_timestamps timestamp_column_names.each do |c_name| @@ -591,8 +635,12 @@ module ActiveRecord row[fk_name] = ActiveRecord::FixtureSet.identify(value) end + when :has_many + if association.options[:through] + add_join_records(rows, row, HasManyThroughProxy.new(association)) + end when :has_and_belongs_to_many - handle_habtm(rows, row, association) + add_join_records(rows, row, HABTMProxy.new(association)) end end end @@ -602,18 +650,56 @@ module ActiveRecord rows end + class ReflectionProxy # :nodoc: + def initialize(association) + @association = association + end + + def join_table + @association.join_table + end + + def name + @association.name + end + end + + class HasManyThroughProxy < ReflectionProxy # :nodoc: + def rhs_key + @association.foreign_key + end + + def lhs_key + @association.through_reflection.foreign_key + end + end + + class HABTMProxy < ReflectionProxy # :nodoc: + def rhs_key + @association.association_foreign_key + end + + def lhs_key + @association.foreign_key + end + end + private def primary_key_name @primary_key_name ||= model_class && model_class.primary_key end - def handle_habtm(rows, row, association) + def add_join_records(rows, row, association) + # This is the case when the join table has no fixtures file if (targets = row.delete(association.name.to_s)) - targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) table_name = association.join_table + lhs_key = association.lhs_key + rhs_key = association.rhs_key + + targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) rows[table_name].concat targets.map { |target| - { association.foreign_key => row[primary_key_name], - association.association_foreign_key => ActiveRecord::FixtureSet.identify(target) } + { lhs_key => row[primary_key_name], + rhs_key => ActiveRecord::FixtureSet.identify(target) } } end end @@ -636,12 +722,12 @@ module ActiveRecord @column_names ||= @connection.columns(@table_name).collect { |c| c.name } end - def read_fixture_files - yaml_files = Dir["#{@path}/{**,*}/*.yml"].select { |f| + def read_fixture_files(path, model_class) + yaml_files = Dir["#{path}/{**,*}/*.yml"].select { |f| ::File.file?(f) - } + [yaml_file_path] + } + [yaml_file_path(path)] - yaml_files.each do |file| + yaml_files.each_with_object({}) do |file, fixtures| FixtureSet::File.open(file) do |fh| fh.each do |fixture_name, row| fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class) @@ -650,8 +736,8 @@ module ActiveRecord end end - def yaml_file_path - "#{@path}.yml" + def yaml_file_path(path) + "#{path}.yml" end end @@ -723,14 +809,16 @@ module ActiveRecord class_attribute :use_transactional_fixtures class_attribute :use_instantiated_fixtures # true, false, or :no_instances class_attribute :pre_loaded_fixtures + class_attribute :config self.fixture_table_names = [] self.use_transactional_fixtures = true self.use_instantiated_fixtures = false self.pre_loaded_fixtures = false + self.config = ActiveRecord::Base self.fixture_class_names = Hash.new do |h, fixture_set_name| - h[fixture_set_name] = ActiveRecord::FixtureSet.default_fixture_model_name(fixture_set_name) + h[fixture_set_name] = ActiveRecord::FixtureSet.default_fixture_model_name(fixture_set_name, self.config) end end @@ -743,13 +831,6 @@ module ActiveRecord # 'namespaced/fixture' => Another::Model # # The keys must be the fixture names, that coincide with the short paths to the fixture files. - #-- - # It is also possible to pass the class name instead of the class: - # set_fixture_class 'some_fixture' => 'SomeModel' - # I think this option is redundant, i propose to deprecate it. - # Isn't it easier to always pass the class itself? - # (2011-12-20 alexeymuranov) - #++ def set_fixture_class(class_names = {}) self.fixture_class_names = self.fixture_class_names.merge(class_names.stringify_keys) end @@ -763,7 +844,7 @@ module ActiveRecord end self.fixture_table_names |= fixture_set_names - require_fixture_classes(fixture_set_names) + require_fixture_classes(fixture_set_names, self.config) setup_fixture_accessors(fixture_set_names) end @@ -778,7 +859,7 @@ module ActiveRecord end end - def require_fixture_classes(fixture_set_names = nil) + def require_fixture_classes(fixture_set_names = nil, config = ActiveRecord::Base) if fixture_set_names fixture_set_names = fixture_set_names.map { |n| n.to_s } else @@ -786,7 +867,7 @@ module ActiveRecord end fixture_set_names.each do |file_name| - file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names + file_name = file_name.singularize if config.pluralize_table_names try_to_load_dependency(file_name) end end @@ -838,7 +919,7 @@ module ActiveRecord !self.class.uses_transaction?(method_name) end - def setup_fixtures + def setup_fixtures(config = ActiveRecord::Base) if pre_loaded_fixtures && !use_transactional_fixtures raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures' end @@ -852,7 +933,7 @@ module ActiveRecord if @@already_loaded_fixtures[self.class] @loaded_fixtures = @@already_loaded_fixtures[self.class] else - @loaded_fixtures = load_fixtures + @loaded_fixtures = load_fixtures(config) @@already_loaded_fixtures[self.class] = @loaded_fixtures end @fixture_connections = enlist_fixture_connections @@ -863,11 +944,11 @@ module ActiveRecord else ActiveRecord::FixtureSet.reset_cache @@already_loaded_fixtures[self.class] = nil - @loaded_fixtures = load_fixtures + @loaded_fixtures = load_fixtures(config) end # Instantiate fixtures for every test if requested. - instantiate_fixtures if use_instantiated_fixtures + instantiate_fixtures(config) if use_instantiated_fixtures end def teardown_fixtures @@ -889,19 +970,19 @@ module ActiveRecord end private - def load_fixtures - fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names) + def load_fixtures(config) + fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config) Hash[fixtures.map { |f| [f.name, f] }] end # for pre_loaded_fixtures, only require the classes once. huge speed improvement @@required_fixture_classes = false - def instantiate_fixtures + def instantiate_fixtures(config) if pre_loaded_fixtures raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::FixtureSet.all_loaded_fixtures.empty? unless @@required_fixture_classes - self.class.require_fixture_classes ActiveRecord::FixtureSet.all_loaded_fixtures.keys + self.class.require_fixture_classes ActiveRecord::FixtureSet.all_loaded_fixtures.keys, config @@required_fixture_classes = true end ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?) diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 19c6f8148b..a1ad4f6255 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -373,23 +373,23 @@ module ActiveRecord class << self attr_accessor :delegate # :nodoc: attr_accessor :disable_ddl_transaction # :nodoc: - end - def self.check_pending! - raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration? - end + def check_pending! + raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration? + end - def self.method_missing(name, *args, &block) # :nodoc: - (delegate || superclass.delegate).send(name, *args, &block) - end + def method_missing(name, *args, &block) # :nodoc: + (delegate || superclass.delegate).send(name, *args, &block) + end - def self.migrate(direction) - new.migrate direction - end + def migrate(direction) + new.migrate direction + end - # Disable DDL transactions for this migration. - def self.disable_ddl_transaction! - @disable_ddl_transaction = true + # Disable DDL transactions for this migration. + def disable_ddl_transaction! + @disable_ddl_transaction = true + end end def disable_ddl_transaction # :nodoc: @@ -617,8 +617,8 @@ module ActiveRecord say_with_time "#{method}(#{arg_list})" do unless @connection.respond_to? :revert unless arguments.empty? || method == :execute - arguments[0] = Migrator.proper_table_name(arguments.first) - arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table + arguments[0] = proper_table_name(arguments.first, table_name_options) + arguments[1] = proper_table_name(arguments.second, table_name_options) if method == :rename_table end end return super unless connection.respond_to?(method) @@ -671,6 +671,17 @@ module ActiveRecord copied end + # Finds the correct table name given an Active Record object. + # Uses the Active Record object's own table_name, or pre/suffix from the + # options passed in. + def proper_table_name(name, options = {}) + if name.respond_to? :table_name + name.table_name + else + "#{options[:table_name_prefix]}#{name}#{options[:table_name_suffix]}" + end + end + # Determines the version number of the next migration. def next_migration_number(number) if ActiveRecord::Base.timestamped_migrations @@ -680,6 +691,13 @@ module ActiveRecord end end + def table_name_options(config = ActiveRecord::Base) + { + table_name_prefix: config.table_name_prefix, + table_name_suffix: config.table_name_suffix + } + end + private def execute_block if connection.respond_to? :execute_block @@ -809,12 +827,16 @@ module ActiveRecord migrations(migrations_paths).last || NullMigration.new end - def proper_table_name(name) - # Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string + def proper_table_name(name, options = {}) + ActiveSupport::Deprecation.warn "ActiveRecord::Migrator.proper_table_name is deprecated and will be removed in Rails 4.2. Use the proper_table_name instance method on ActiveRecord::Migration instead" + options = { + table_name_prefix: ActiveRecord::Base.table_name_prefix, + table_name_suffix: ActiveRecord::Base.table_name_suffix + }.merge(options) if name.respond_to? :table_name name.table_name else - "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}" + "#{options[:table_name_prefix]}#{name}#{options[:table_name_suffix]}" end end diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 23541d1d27..75c0c1bda8 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -224,13 +224,20 @@ module ActiveRecord def decorate_columns(columns_hash) # :nodoc: return if columns_hash.empty? - columns_hash.each do |name, col| - if serialized_attributes.key?(name) - columns_hash[name] = AttributeMethods::Serialization::Type.new(col) - end - if create_time_zone_conversion_attribute?(name, col) - columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(col) - end + @serialized_column_names ||= self.columns_hash.keys.find_all do |name| + serialized_attributes.key?(name) + end + + @serialized_column_names.each do |name| + columns_hash[name] = AttributeMethods::Serialization::Type.new(columns_hash[name]) + end + + @time_zone_column_names ||= self.columns_hash.find_all do |name, col| + create_time_zone_conversion_attribute?(name, col) + end.map!(&:first) + + @time_zone_column_names.each do |name| + columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(columns_hash[name]) end columns_hash @@ -284,16 +291,19 @@ module ActiveRecord undefine_attribute_methods connection.schema_cache.clear_table_cache!(table_name) if table_exists? - @arel_engine = nil - @column_defaults = nil - @column_names = nil - @columns = nil - @columns_hash = nil - @column_types = nil - @content_columns = nil - @dynamic_methods_hash = nil - @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column - @relation = nil + @arel_engine = nil + @column_defaults = nil + @column_names = nil + @columns = nil + @columns_hash = nil + @column_types = nil + @content_columns = nil + @dynamic_methods_hash = nil + @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column + @relation = nil + @serialized_column_names = nil + @time_zone_column_names = nil + @cached_time_zone = nil end # This is a hook for use by modules that need to do extra stuff to diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index f428f160cf..150a90f5db 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -121,6 +121,7 @@ module ActiveRecord @scope = scope @options = options @active_record = active_record + @klass = options[:class] @plural_name = active_record.pluralize_table_names ? name.to_s.pluralize : name.to_s end @@ -422,7 +423,6 @@ module ActiveRecord def valid_inverse_reflection?(reflection) reflection && klass.name == reflection.active_record.name && - klass.primary_key == reflection.active_record_primary_key && can_find_inverse_of_automatically?(reflection) end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 52a538e5b5..27c04b0952 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -161,8 +161,7 @@ module ActiveRecord result = result.map do |attributes| values = klass.initialize_attributes(attributes).values - iter = columns.each - values.map { |value| iter.next.type_cast value } + columns.zip(values).map { |column, value| column.type_cast value } end columns.one? ? result.map!(&:first) : result end diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index b6f80ac5c7..e28938f9d0 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -1,8 +1,33 @@ -require 'thread' -require 'thread_safe' +require 'active_support/concern' module ActiveRecord module Delegation # :nodoc: + module DelegateCache + def relation_delegate_class(klass) # :nodoc: + @relation_delegate_cache[klass] + end + + def initialize_relation_delegate_cache # :nodoc: + @relation_delegate_cache = cache = {} + [ + ActiveRecord::Relation, + ActiveRecord::Associations::CollectionProxy, + ActiveRecord::AssociationRelation + ].each do |klass| + delegate = Class.new(klass) { + include ClassSpecificRelation + } + const_set klass.name.gsub('::', '_'), delegate + cache[klass] = delegate + end + end + + def inherited(child_class) + child_class.initialize_relation_delegate_cache + super + end + end + extend ActiveSupport::Concern # This module creates compiled delegation methods dynamically at runtime, which makes @@ -71,32 +96,14 @@ module ActiveRecord end module ClassMethods # :nodoc: - @@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2) - def create(klass, *args) relation_class_for(klass).new(klass, *args) end private - # Cache the constants in @@subclasses because looking them up via const_get - # make instantiation significantly slower. + def relation_class_for(klass) - if klass && (klass_name = klass.name) - my_cache = @@subclasses.compute_if_absent(self) { ThreadSafe::Cache.new } - # This hash is keyed by klass.name to avoid memory leaks in development mode - my_cache.compute_if_absent(klass_name) do - # Cache#compute_if_absent guarantees that the block will only executed once for the given klass_name - subclass_name = "#{name.gsub('::', '_')}_#{klass_name.gsub('::', '_')}" - - if const_defined?(subclass_name) - const_get(subclass_name) - else - const_set(subclass_name, Class.new(self) { include ClassSpecificRelation }) - end - end - else - ActiveRecord::Relation - end + klass.relation_delegate_class(self) end end diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index c08158d38b..c05632e688 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -58,7 +58,11 @@ module ActiveRecord def merge normal_values.each do |name| value = values[name] - relation.send("#{name}!", *value) unless value.blank? + # The unless clause is here mostly for performance reasons (since the `send` call might be moderately + # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that + # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values + # don't fall through the cracks. + relation.send("#{name}!", *value) unless value.nil? || (value.blank? && false != value) end merge_multi_values @@ -107,11 +111,11 @@ module ActiveRecord bind_values = filter_binds(lhs_binds, removed) + rhs_binds conn = relation.klass.connection - bviter = bind_values.each.with_index + bv_index = 0 where_values.map! do |node| if Arel::Nodes::Equality === node && Arel::Nodes::BindParam === node.right - (column, _), i = bviter.next - substitute = conn.substitute_at column, i + substitute = conn.substitute_at(bind_values[bv_index].first, bv_index) + bv_index += 1 Arel::Nodes::Equality.new(node.left, substitute) else node diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 8948f2bba5..c60cd27a83 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -55,7 +55,7 @@ module ActiveRecord # # For polymorphic relationships, find the foreign key and type: # PriceEstimate.where(estimate_of: treasure) - if klass && value.class < Base && reflection = klass.reflect_on_association(column.to_sym) + if klass && value.is_a?(Base) && reflection = klass.reflect_on_association(column.to_sym) if reflection.polymorphic? queries << build(table[reflection.foreign_type], value.class.base_class) end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 9f2a039d94..1c6ea94c0b 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -289,17 +289,7 @@ module ActiveRecord end def order!(*args) # :nodoc: - args.flatten! - validate_order_args(args) - - references = args.grep(String) - references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact! - references!(references) if references.any? - - # if a symbol is given we prepend the quoted table name - args.map! do |arg| - arg.is_a?(Symbol) ? Arel::Nodes::Ascending.new(klass.arel_table[arg]) : arg - end + preprocess_order_args(args) self.order_values += args self @@ -320,8 +310,7 @@ module ActiveRecord end def reorder!(*args) # :nodoc: - args.flatten! - validate_order_args(args) + preprocess_order_args(args) self.reordering_value = true self.order_values = args @@ -1036,6 +1025,20 @@ module ActiveRecord end end + def preprocess_order_args(order_args) + order_args.flatten! + validate_order_args(order_args) + + references = order_args.grep(String) + references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact! + references!(references) if references.any? + + # if a symbol is given we prepend the quoted table name + order_args.map! do |arg| + arg.is_a?(Symbol) ? Arel::Nodes::Ascending.new(klass.arel_table[arg]) : arg + end + end + # Checks to make sure that the arguments are not blank. Note that if some # blank-like object were initially passed into the query method, then this # method will not raise an error. diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index 253368ae5b..d0f1cb5b75 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -93,7 +93,21 @@ module ActiveRecord # used as keys in ActiveRecord::Base's @attributes hash columns = @columns.map { |c| c.dup.freeze } @rows.map { |row| - Hash[columns.zip(row)] + # In the past we used Hash[columns.zip(row)] + # though elegant, the verbose way is much more efficient + # both time and memory wise cause it avoids a big array allocation + # this method is called a lot and needs to be micro optimised + hash = {} + + index = 0 + length = columns.length + + while index < length + hash[columns[index]] = row[index] + index += 1 + end + + hash } end end diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index a5d6aad3f0..01fec31544 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -100,11 +100,7 @@ module ActiveRecord elsif default_scopes.any? evaluate_default_scope do default_scopes.inject(relation) do |default_scope, scope| - if !scope.is_a?(Relation) && scope.respond_to?(:call) - default_scope.merge(unscoped { scope.call }) - else - default_scope.merge(scope) - end + default_scope.merge(unscoped { scope.call }) end end end diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 75b6f4f8ce..3dbab08a99 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -298,6 +298,14 @@ _SQL assert_equal(-567.89, @second_money.wealth) end + def test_money_type_cast + column = PostgresqlMoney.columns.find { |c| c.name == 'wealth' } + assert_equal(12345678.12, column.type_cast("$12,345,678.12")) + assert_equal(12345678.12, column.type_cast("$12.345.678,12")) + assert_equal(-1.15, column.type_cast("-$1.15")) + assert_equal(-2.25, column.type_cast("($2.25)")) + end + def test_create_tstzrange skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT') diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index a534f0e56a..1122f8b9a1 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -52,6 +52,11 @@ module ActiveRecord c = Column.new(nil, nil, 'text') assert_equal "'666'", @conn.quote(fixnum, c) end + + def test_quote_time_usec + assert_equal "'1970-01-01 00:00:00.000000'", @conn.quote(Time.at(0)) + assert_equal "'1970-01-01 00:00:00.000000'", @conn.quote(Time.at(0).to_datetime) + end end 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 e693d34f99..811d91f849 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -52,12 +52,10 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase def test_cascaded_eager_association_loading_with_join_for_count categories = Category.joins(:categorizations).includes([{:posts=>:comments}, :authors]) - assert_nothing_raised do - assert_equal 4, categories.count - assert_equal 4, categories.to_a.count - assert_equal 3, categories.distinct.count - assert_equal 3, categories.to_a.uniq.size # Must uniq since instantiating with inner joins will get dupes - end + assert_equal 4, categories.count + assert_equal 4, categories.to_a.count + assert_equal 3, categories.distinct.count + assert_equal 3, categories.to_a.uniq.size # Must uniq since instantiating with inner joins will get dupes end def test_cascaded_eager_association_loading_with_duplicated_includes diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 28bf48f4fd..8d3d6962fc 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1172,8 +1172,11 @@ class EagerAssociationTest < ActiveRecord::TestCase } end - test "works in combination with order(:symbol)" do - author = Author.includes(:posts).references(:posts).order(:name).where('posts.title IS NOT NULL').first + test "works in combination with order(:symbol) and reorder(:symbol)" do + author = Author.includes(:posts).references(:posts).order(:name).find_by('posts.title IS NOT NULL') + assert_equal authors(:bob), author + + author = Author.includes(:posts).references(:posts).reorder(:name).find_by('posts.title IS NOT NULL') assert_equal authors(:bob), author end end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 712a770133..bb927841ab 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -605,6 +605,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_join_table_alias + # FIXME: `references` has no impact on the aliases generated for the join + # query. The fact that we pass `:developers_projects_join` to `references` + # and that the SQL string contains `developers_projects_join` is merely a + # coincidence. assert_equal( 3, Developer.references(:developers_projects_join).merge( @@ -615,6 +619,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_join_with_group + # FIXME: `references` has no impact on the aliases generated for the join + # query. The fact that we pass `:developers_projects_join` to `references` + # and that the SQL string contains `developers_projects_join` is merely a + # coincidence. group = Developer.columns.inject([]) do |g, c| g << "developers.#{c.name}" g << "developers_projects_2.#{c.name}" diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index ce51853bf3..4c0fa88917 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -45,6 +45,24 @@ class HasManyAssociationsTest < ActiveRecord::TestCase Client.destroyed_client_ids.clear end + def test_anonymous_has_many + developer = Class.new(ActiveRecord::Base) { + self.table_name = 'developers' + dev = self + + developer_project = Class.new(ActiveRecord::Base) { + self.table_name = 'developers_projects' + belongs_to :developer, :class => dev + } + has_many :developer_projects, :class => developer_project, :foreign_key => 'developer_id' + } + dev = developer.first + named = Developer.find(dev.id) + assert_operator dev.developer_projects.count, :>, 0 + assert_equal named.projects.map(&:id).sort, + dev.developer_projects.map(&:project_id).sort + end + def test_create_from_association_should_respect_default_scope car = Car.create(:name => 'honda') assert_equal 'honda', car.name diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 941e851aae..dd8b426b25 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -28,7 +28,8 @@ require 'models/club' class HasManyThroughAssociationsTest < ActiveRecord::TestCase fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags, :owners, :pets, :toys, :jobs, :references, :companies, :members, :author_addresses, - :subscribers, :books, :subscriptions, :developers, :categorizations, :essays + :subscribers, :books, :subscriptions, :developers, :categorizations, :essays, + :categories_posts # Dummies to force column loads so query counts are clean. def setup @@ -36,6 +37,101 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase Reader.create :person_id => 0, :post_id => 0 end + def make_model(name) + Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } } + end + + def test_singleton_has_many_through + book = make_model "Book" + subscription = make_model "Subscription" + subscriber = make_model "Subscriber" + + subscriber.primary_key = 'nick' + subscription.belongs_to :book, class: book + subscription.belongs_to :subscriber, class: subscriber + + book.has_many :subscriptions, class: subscription + book.has_many :subscribers, through: :subscriptions, class: subscriber + + anonbook = book.first + namebook = Book.find anonbook.id + + assert_operator anonbook.subscribers.count, :>, 0 + anonbook.subscribers.each do |s| + assert_instance_of subscriber, s + end + assert_equal namebook.subscribers.map(&:id).sort, + anonbook.subscribers.map(&:id).sort + end + + def test_no_pk_join_table_append + lesson, _, student = make_no_pk_hm_t + + sicp = lesson.new(:name => "SICP") + ben = student.new(:name => "Ben Bitdiddle") + sicp.students << ben + assert sicp.save! + end + + def test_no_pk_join_table_delete + lesson, lesson_student, student = make_no_pk_hm_t + + sicp = lesson.new(:name => "SICP") + ben = student.new(:name => "Ben Bitdiddle") + louis = student.new(:name => "Louis Reasoner") + sicp.students << ben + sicp.students << louis + assert sicp.save! + + sicp.students.reload + assert_operator lesson_student.count, :>=, 2 + assert_no_difference('student.count') do + assert_difference('lesson_student.count', -2) do + sicp.students.destroy(*student.all.to_a) + end + end + end + + def test_no_pk_join_model_callbacks + lesson, lesson_student, student = make_no_pk_hm_t + + after_destroy_called = false + lesson_student.after_destroy do + after_destroy_called = true + end + + sicp = lesson.new(:name => "SICP") + ben = student.new(:name => "Ben Bitdiddle") + sicp.students << ben + assert sicp.save! + + sicp.students.reload + sicp.students.destroy(*student.all.to_a) + assert after_destroy_called, "after destroy should be called" + end + + def make_no_pk_hm_t + lesson = make_model 'Lesson' + student = make_model 'Student' + + lesson_student = make_model 'LessonStudent' + lesson_student.table_name = 'lessons_students' + + lesson_student.belongs_to :lesson, :class => lesson + lesson_student.belongs_to :student, :class => student + lesson.has_many :lesson_students, :class => lesson_student + lesson.has_many :students, :through => :lesson_students, :class => student + [lesson, lesson_student, student] + end + + def test_pk_is_not_required_for_join + post = Post.includes(:scategories).first + post2 = Post.includes(:categories).first + + assert_operator post.categories.length, :>, 0 + assert_equal post2.categories, post.categories + end + def test_include? person = Person.new post = Post.new diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 4fdf9a9643..9cd4db8dc9 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -505,6 +505,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_no_queries { company.account = nil } account = Account.find(2) assert_queries { company.account = account } + + assert_no_queries { Firm.new.account = account } end def test_has_one_assignment_triggers_save_on_change diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index 9b1abc3b81..cf3c07845c 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -371,7 +371,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase prev_default_scope = Club.default_scopes [:includes, :preload, :joins, :eager_load].each do |q| - Club.default_scopes = [Club.send(q, :category)] + Club.default_scopes = [proc { Club.send(q, :category) }] assert_equal categories(:general), members(:groucho).reload.club_category end ensure diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index c91cf89f6d..dc2de7b523 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1324,6 +1324,35 @@ class BasicsTest < ActiveRecord::TestCase assert_equal 1, post.comments.length end + def test_marshal_between_processes + skip "fork isn't supported" unless Process.respond_to?(:fork) + + # Define a new model to ensure there are no caches + if self.class.const_defined?("Post", false) + flunk "there should be no post constant" + end + + self.class.const_set("Post", Class.new(ActiveRecord::Base) { + has_many :comments + }) + + rd, wr = IO.pipe + + ActiveRecord::Base.connection_handler.clear_all_connections! + + fork do + rd.close + post = Post.new + post.comments.build + wr.write Marshal.dump(post) + wr.close + end + + wr.close + assert Marshal.load rd.read + rd.close + end + def test_marshalling_new_record_round_trip_with_associations post = Post.new post.comments.build diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb index f73e449610..1e6ccecfab 100644 --- a/activerecord/test/cases/dup_test.rb +++ b/activerecord/test/cases/dup_test.rb @@ -126,7 +126,7 @@ module ActiveRecord def test_dup_with_default_scope prev_default_scopes = Topic.default_scopes - Topic.default_scopes = [Topic.where(:approved => true)] + Topic.default_scopes = [proc { Topic.where(:approved => true) }] topic = Topic.new(:approved => false) assert !topic.dup.approved?, "should not be overridden by default scopes" ensure diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 949e994f8d..1b9ef14ec9 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -51,6 +51,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal true, Topic.exists?(heading: "The First Topic") assert_equal true, Topic.exists?(:author_name => "Mary", :approved => true) assert_equal true, Topic.exists?(["parent_id = ?", 1]) + assert_equal true, Topic.exists?(id: [1, 9999]) assert_equal false, Topic.exists?(45) assert_equal false, Topic.exists?(Topic.new) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index c07c9c3b74..e61deb1080 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -84,6 +84,12 @@ class FixturesTest < ActiveRecord::TestCase assert fixtures.detect { |f| f.name == 'collections' }, "no fixtures named 'collections' in #{fixtures.map(&:name).inspect}" end + def test_create_symbol_fixtures_is_deprecated + assert_deprecated do + ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, :collections, :collections => 'Course') { Course.connection } + end + end + def test_attributes topics = create_fixtures("topics").first assert_equal("The First Topic", topics["first"]["title"]) @@ -190,11 +196,11 @@ class FixturesTest < ActiveRecord::TestCase end def test_empty_yaml_fixture - assert_not_nil ActiveRecord::FixtureSet.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/yml/accounts") + assert_not_nil ActiveRecord::FixtureSet.new( Account.connection, "accounts", Account, FIXTURES_ROOT + "/naked/yml/accounts") end def test_empty_yaml_fixture_with_a_comment_in_it - assert_not_nil ActiveRecord::FixtureSet.new( Account.connection, "companies", 'Company', FIXTURES_ROOT + "/naked/yml/companies") + assert_not_nil ActiveRecord::FixtureSet.new( Account.connection, "companies", Company, FIXTURES_ROOT + "/naked/yml/companies") end def test_nonexistent_fixture_file @@ -204,19 +210,19 @@ class FixturesTest < ActiveRecord::TestCase assert Dir[nonexistent_fixture_path+"*"].empty? assert_raise(Errno::ENOENT) do - ActiveRecord::FixtureSet.new( Account.connection, "companies", 'Company', nonexistent_fixture_path) + ActiveRecord::FixtureSet.new( Account.connection, "companies", Company, nonexistent_fixture_path) end end def test_dirty_dirty_yaml_file assert_raise(ActiveRecord::Fixture::FormatError) do - ActiveRecord::FixtureSet.new( Account.connection, "courses", 'Course', FIXTURES_ROOT + "/naked/yml/courses") + ActiveRecord::FixtureSet.new( Account.connection, "courses", Course, FIXTURES_ROOT + "/naked/yml/courses") end end def test_omap_fixtures assert_nothing_raised do - fixtures = ActiveRecord::FixtureSet.new(Account.connection, 'categories', 'Category', FIXTURES_ROOT + "/categories_ordered") + fixtures = ActiveRecord::FixtureSet.new(Account.connection, 'categories', Category, FIXTURES_ROOT + "/categories_ordered") fixtures.each.with_index do |(name, fixture), i| assert_equal "fixture_no_#{i}", name @@ -263,6 +269,41 @@ class FixturesTest < ActiveRecord::TestCase end end +class HasManyThroughFixture < ActiveSupport::TestCase + def make_model(name) + Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } } + end + + def test_has_many_through + pt = make_model "ParrotTreasure" + parrot = make_model "Parrot" + treasure = make_model "Treasure" + + pt.table_name = "parrots_treasures" + pt.belongs_to :parrot, :class => parrot + pt.belongs_to :treasure, :class => treasure + + parrot.has_many :parrot_treasures, :class => pt + parrot.has_many :treasures, :through => :parrot_treasures + + parrots = File.join FIXTURES_ROOT, 'parrots' + + fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots + rows = fs.table_rows + assert_equal load_has_and_belongs_to_many['parrots_treasures'], rows['parrots_treasures'] + end + + def load_has_and_belongs_to_many + parrot = make_model "Parrot" + parrot.has_and_belongs_to_many :treasures + + parrots = File.join FIXTURES_ROOT, 'parrots' + + fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots + fs.table_rows + end +end + if Account.connection.respond_to?(:reset_pk_sequence!) class FixturesResetPkSequenceTest < ActiveRecord::TestCase fixtures :accounts @@ -449,7 +490,7 @@ class OverRideFixtureMethodTest < ActiveRecord::TestCase end class CheckSetTableNameFixturesTest < ActiveRecord::TestCase - set_fixture_class :funny_jokes => 'Joke' + set_fixture_class :funny_jokes => Joke fixtures :funny_jokes # Set to false to blow away fixtures cache and ensure our fixtures are loaded # and thus takes into account our set_fixture_class @@ -532,7 +573,7 @@ class InvalidTableNameFixturesTest < ActiveRecord::TestCase end class CheckEscapedYamlFixturesTest < ActiveRecord::TestCase - set_fixture_class :funny_jokes => 'Joke' + set_fixture_class :funny_jokes => Joke fixtures :funny_jokes # Set to false to blow away fixtures cache and ensure our fixtures are loaded # and thus takes into account our set_fixture_class @@ -574,7 +615,7 @@ class FixturesBrokenRollbackTest < ActiveRecord::TestCase end private - def load_fixtures + def load_fixtures(config) raise 'argh' end end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index ed080b2995..931caff727 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -321,12 +321,53 @@ class MigrationTest < ActiveRecord::TestCase assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name end - def test_proper_table_name - assert_equal "table", ActiveRecord::Migrator.proper_table_name('table') - assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table) - assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder) + def test_proper_table_name_on_migrator + assert_deprecated do + assert_equal "table", ActiveRecord::Migrator.proper_table_name('table') + end + assert_deprecated do + assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table) + end + assert_deprecated do + assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder) + end + Reminder.reset_table_name + assert_deprecated do + assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder) + end + + # Use the model's own prefix/suffix if a model is given + ActiveRecord::Base.table_name_prefix = "ARprefix_" + ActiveRecord::Base.table_name_suffix = "_ARsuffix" + Reminder.table_name_prefix = 'prefix_' + Reminder.table_name_suffix = '_suffix' + Reminder.reset_table_name + assert_deprecated do + assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder) + end + Reminder.table_name_prefix = '' + Reminder.table_name_suffix = '' + Reminder.reset_table_name + + # Use AR::Base's prefix/suffix if string or symbol is given + ActiveRecord::Base.table_name_prefix = "prefix_" + ActiveRecord::Base.table_name_suffix = "_suffix" + Reminder.reset_table_name + assert_deprecated do + assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table') + end + assert_deprecated do + assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table) + end + end + + def test_proper_table_name_on_migration + migration = ActiveRecord::Migration.new + assert_equal "table", migration.proper_table_name('table') + assert_equal "table", migration.proper_table_name(:table) + assert_equal "reminders", migration.proper_table_name(Reminder) Reminder.reset_table_name - assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder) + assert_equal Reminder.table_name, migration.proper_table_name(Reminder) # Use the model's own prefix/suffix if a model is given ActiveRecord::Base.table_name_prefix = "ARprefix_" @@ -334,7 +375,7 @@ class MigrationTest < ActiveRecord::TestCase Reminder.table_name_prefix = 'prefix_' Reminder.table_name_suffix = '_suffix' Reminder.reset_table_name - assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder) + assert_equal "prefix_reminders_suffix", migration.proper_table_name(Reminder) Reminder.table_name_prefix = '' Reminder.table_name_suffix = '' Reminder.reset_table_name @@ -343,8 +384,8 @@ class MigrationTest < ActiveRecord::TestCase ActiveRecord::Base.table_name_prefix = "prefix_" ActiveRecord::Base.table_name_suffix = "_suffix" Reminder.reset_table_name - assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table') - assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table) + assert_equal "prefix_table_suffix", migration.proper_table_name('table', migration.table_name_options) + assert_equal "prefix_table_suffix", migration.proper_table_name(:table, migration.table_name_options) end def test_rename_table_with_prefix_and_suffix diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index d333be3560..3e460fa3d6 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -81,6 +81,31 @@ module ActiveRecord assert_equal expected.to_sql, actual.to_sql end + def test_decorated_polymorphic_where + treasure_decorator = Struct.new(:model) do + def self.method_missing(method, *args, &block) + Treasure.send(method, *args, &block) + end + + def is_a?(klass) + model.is_a?(klass) + end + + def method_missing(method, *args, &block) + model.send(method, *args, &block) + end + end + + treasure = Treasure.new + treasure.id = 1 + decorated_treasure = treasure_decorator.new(treasure) + + expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: 1) + actual = PriceEstimate.where(estimate_of: decorated_treasure) + + assert_equal expected.to_sql, actual.to_sql + end + def test_aliased_attribute expected = Topic.where(heading: 'The First Topic') actual = Topic.where(title: 'The First Topic') diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index f99801c437..e77de19ac3 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -9,6 +9,10 @@ module ActiveRecord fixtures :posts, :comments, :authors class FakeKlass < Struct.new(:table_name, :name) + extend ActiveRecord::Delegation::DelegateCache + + inherited self + def self.connection Post.connection end @@ -76,8 +80,8 @@ module ActiveRecord end def test_table_name_delegates_to_klass - relation = Relation.new FakeKlass.new('foo'), :b - assert_equal 'foo', relation.table_name + relation = Relation.new FakeKlass.new('posts'), :b + assert_equal 'posts', relation.table_name end def test_scope_for_create @@ -177,14 +181,26 @@ module ActiveRecord end test 'merging a hash interpolates conditions' do - klass = stub_everything - klass.stubs(:sanitize_sql).with(['foo = ?', 'bar']).returns('foo = bar') + klass = Class.new(FakeKlass) do + def self.sanitize_sql(args) + raise unless args == ['foo = ?', 'bar'] + 'foo = bar' + end + end relation = Relation.new(klass, :b) relation.merge!(where: ['foo = ?', 'bar']) assert_equal ['foo = bar'], relation.where_values end + def test_merging_readonly_false + relation = Relation.new FakeKlass, :b + readonly_false_relation = relation.readonly(false) + # test merging in both directions + assert_equal false, relation.merge(readonly_false_relation).readonly_value + assert_equal false, readonly_false_relation.merge(relation).readonly_value + end + def test_relation_merging_with_merged_joins_as_symbols special_comments_with_ratings = SpecialComment.joins(:ratings) posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings) @@ -210,6 +226,9 @@ module ActiveRecord class RelationMutationTest < ActiveSupport::TestCase class FakeKlass < Struct.new(:table_name, :name) + extend ActiveRecord::Delegation::DelegateCache + inherited self + def arel_table Post.arel_table end @@ -217,6 +236,10 @@ module ActiveRecord def connection Post.connection end + + def relation_delegate_class(klass) + self.class.relation_delegate_class(klass) + end end def relation @@ -296,6 +319,15 @@ module ActiveRecord assert relation.reordering_value end + test '#reorder! with symbol prepends the table name' do + assert relation.reorder!(:name).equal?(relation) + node = relation.order_values.first + + assert node.ascending? + assert_equal :name, node.expr.name + assert_equal "posts", node.expr.relation.name + end + test 'reverse_order!' do assert relation.reverse_order!.equal?(relation) assert relation.reverse_order_value diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index e1a760d240..b3ca45c4c8 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1344,6 +1344,24 @@ class RelationTest < ActiveRecord::TestCase assert_equal [], scope.references_values end + def test_automatically_added_reorder_references + scope = Post.reorder('comments.body') + assert_equal %w(comments), scope.references_values + + scope = Post.reorder('comments.body', 'yaks.body') + assert_equal %w(comments yaks), scope.references_values + + # Don't infer yaks, let's not go down that road again... + scope = Post.reorder('comments.body, yaks.body') + assert_equal %w(comments), scope.references_values + + scope = Post.reorder('comments.body asc') + assert_equal %w(comments), scope.references_values + + scope = Post.reorder('foo(comments.body)') + assert_equal [], scope.references_values + end + def test_presence topics = Topic.all diff --git a/activerecord/test/fixtures/tasks.yml b/activerecord/test/fixtures/tasks.yml index 402ca85faf..c38b32b0e5 100644 --- a/activerecord/test/fixtures/tasks.yml +++ b/activerecord/test/fixtures/tasks.yml @@ -1,4 +1,4 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html first_task: id: 1 starting: 2005-03-30t06:30:00.00+01:00 diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index feb828de31..7dad8041f3 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -8,9 +8,7 @@ class Author < ActiveRecord::Base has_many :posts_sorted_by_id_limited, -> { order('posts.id').limit(1) }, :class_name => "Post" has_many :posts_with_categories, -> { includes(:categories) }, :class_name => "Post" has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order("posts.id") }, :class_name => "Post" - has_many :posts_containing_the_letter_a, :class_name => "Post" has_many :posts_with_special_categorizations, :class_name => 'PostWithSpecialCategorization' - has_many :posts_with_extension, :class_name => "Post" has_one :post_about_thinking, -> { where("posts.title like '%thinking%'") }, :class_name => 'Post' has_one :post_about_thinking_with_last_comment, -> { where("posts.title like '%thinking%'").includes(:last_comment) }, :class_name => 'Post' has_many :comments, through: :posts do @@ -32,7 +30,6 @@ class Author < ActiveRecord::Base has_many :welcome_posts, -> { where(:title => 'Welcome to the weblog') }, :class_name => 'Post' has_many :comments_desc, -> { order('comments.id DESC') }, :through => :posts, :source => :comments - has_many :limited_comments, -> { limit(1) }, :through => :posts, :source => :comments has_many :funky_comments, :through => :posts, :source => :comments has_many :ordered_uniq_comments, -> { distinct.order('comments.id') }, :through => :posts, :source => :comments has_many :ordered_uniq_comments_desc, -> { distinct.order('comments.id DESC') }, :through => :posts, :source => :comments diff --git a/activerecord/test/models/auto_id.rb b/activerecord/test/models/auto_id.rb index d720e2be5e..82c6544bd5 100644 --- a/activerecord/test/models/auto_id.rb +++ b/activerecord/test/models/auto_id.rb @@ -1,4 +1,4 @@ class AutoId < ActiveRecord::Base - def self.table_name () "auto_id_tests" end - def self.primary_key () "auto_id" end + self.table_name = "auto_id_tests" + self.primary_key = "auto_id" end diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index a14a9febba..6d257dbe7e 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -1,12 +1,9 @@ class Car < ActiveRecord::Base - has_many :bulbs has_many :funky_bulbs, class_name: 'FunkyBulb', dependent: :destroy has_many :foo_bulbs, -> { where(:name => 'foo') }, :class_name => "Bulb" - has_many :frickinawesome_bulbs, -> { where :frickinawesome => true }, :class_name => "Bulb" has_one :bulb - has_one :frickinawesome_bulb, -> { where :frickinawesome => true }, :class_name => "Bulb" has_many :tyres has_many :engines, :dependent => :destroy diff --git a/activerecord/test/models/citation.rb b/activerecord/test/models/citation.rb index 545aa8110d..3d87eb795c 100644 --- a/activerecord/test/models/citation.rb +++ b/activerecord/test/models/citation.rb @@ -1,6 +1,3 @@ class Citation < ActiveRecord::Base belongs_to :reference_of, :class_name => "Book", :foreign_key => :book2_id - - belongs_to :book1, :class_name => "Book", :foreign_key => :book1_id - belongs_to :book2, :class_name => "Book", :foreign_key => :book2_id end diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb index 816c5e6937..566e0873f1 100644 --- a/activerecord/test/models/club.rb +++ b/activerecord/test/models/club.rb @@ -2,7 +2,6 @@ class Club < ActiveRecord::Base has_one :membership has_many :memberships, :inverse_of => false has_many :members, :through => :memberships - has_many :current_memberships has_one :sponsor has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member" belongs_to :category diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index c5d4ec0833..8104c607b5 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -49,7 +49,6 @@ class Firm < Company has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, :class_name => "Client" has_many :clients_like_ms_with_hash_conditions, -> { where(:name => 'Microsoft').order("id") }, :class_name => "Client" has_many :plain_clients, :class_name => 'Client' - has_many :readonly_clients, -> { readonly }, :class_name => 'Client' has_many :clients_using_primary_key, :class_name => 'Client', :primary_key => 'name', :foreign_key => 'firm_name' has_many :clients_using_primary_key_with_delete_all, :class_name => 'Client', @@ -167,7 +166,6 @@ class ExclusivelyDependentFirm < Company has_one :account, :foreign_key => "firm_id", :dependent => :delete has_many :dependent_sanitized_conditional_clients_of_firm, -> { order("id").where("name = 'BigShot Inc.'") }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all has_many :dependent_conditional_clients_of_firm, -> { order("id").where("name = ?", 'BigShot Inc.') }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all - has_many :dependent_hash_conditional_clients_of_firm, -> { order("id").where(:name => 'BigShot Inc.') }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all end class SpecialClient < Client diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb index cc47c7bc18..72095f9236 100644 --- a/activerecord/test/models/member.rb +++ b/activerecord/test/models/member.rb @@ -2,7 +2,6 @@ class Member < ActiveRecord::Base has_one :current_membership has_one :selected_membership has_one :membership - has_many :fellow_members, :through => :club, :source => :members has_one :club, :through => :current_membership has_one :selected_club, :through => :selected_membership, :source => :club has_one :favourite_club, -> { where "memberships.favourite = ?", true }, :through => :membership, :source => :club diff --git a/activerecord/test/models/movie.rb b/activerecord/test/models/movie.rb index 6384b4c801..c441be2bef 100644 --- a/activerecord/test/models/movie.rb +++ b/activerecord/test/models/movie.rb @@ -1,5 +1,3 @@ class Movie < ActiveRecord::Base - def self.primary_key - "movieid" - end + self.primary_key = "movieid" end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 93a7a2073c..452d54580a 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -1,4 +1,10 @@ class Post < ActiveRecord::Base + class CategoryPost < ActiveRecord::Base + self.table_name = "categories_posts" + belongs_to :category + belongs_to :post + end + module NamedExtension def author 'lifo' @@ -72,6 +78,8 @@ class Post < ActiveRecord::Base has_many :special_comments_ratings, :through => :special_comments, :source => :ratings has_many :special_comments_ratings_taggings, :through => :special_comments_ratings, :source => :taggings + has_many :category_posts, :class_name => 'CategoryPost' + has_many :scategories, through: :category_posts, source: :category has_and_belongs_to_many :categories has_and_belongs_to_many :special_categories, :join_table => "categories_posts", :association_foreign_key => 'category_id' @@ -122,7 +130,6 @@ class Post < ActiveRecord::Base has_many :secure_readers has_many :readers_with_person, -> { includes(:person) }, :class_name => "Reader" has_many :people, :through => :readers - has_many :secure_people, :through => :secure_readers has_many :single_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/project.rb b/activerecord/test/models/project.rb index c094b726b4..7f42a4b1f8 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -1,7 +1,6 @@ class Project < ActiveRecord::Base has_and_belongs_to_many :developers, -> { distinct.order 'developers.name desc, developers.id desc' } has_and_belongs_to_many :readonly_developers, -> { readonly }, :class_name => "Developer" - has_and_belongs_to_many :selected_developers, -> { distinct.select "developers.*" }, :class_name => "Developer" has_and_belongs_to_many :non_unique_developers, -> { order 'developers.name desc, developers.id desc' }, :class_name => 'Developer' has_and_belongs_to_many :limited_developers, -> { limit 1 }, :class_name => "Developer" has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").distinct }, :class_name => "Developer" diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 17035bf338..40c8e97fc2 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -34,7 +34,6 @@ class Topic < ActiveRecord::Base has_many :replies, :dependent => :destroy, :foreign_key => "parent_id" has_many :approved_replies, -> { approved }, class_name: 'Reply', foreign_key: "parent_id", counter_cache: 'replies_count' - has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title" has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id" has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id" |