diff options
Diffstat (limited to 'activerecord')
28 files changed, 469 insertions, 199 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index d978f13e4b..554bec17d6 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,7 @@ +* Correct query for PostgreSQL 8.2 compatibility. + + *Ben Murphy*, *Matthew Draper* + * `bin/rake db:migrate` uses `ActiveRecord::Tasks::DatabaseTasks.migrations_paths` instead of `Migrator.migrations_paths`. diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 19ef37e228..65eb6fba27 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -5,65 +5,105 @@ require 'active_record/errors' module ActiveRecord class AssociationNotFoundError < ConfigurationError #:nodoc: - def initialize(record, association_name) - super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?") + def initialize(record = nil, association_name = nil) + if record && association_name + super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?") + else + super("Association was not found.") + end end end class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc: - def initialize(reflection, associated_class = nil) - super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})") + def initialize(reflection = nil, associated_class = nil) + if reflection + super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})") + else + super("Could not find the inverse association.") + end end end class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc: - def initialize(owner_class_name, reflection) - super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}") + def initialize(owner_class_name = nil, reflection = nil) + if owner_class_name && reflection + super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}") + else + super("Could not find the association.") + end end end class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc: - def initialize(owner_class_name, reflection, source_reflection) - super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.") + def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil) + if owner_class_name && reflection && source_reflection + super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.") + else + super("Cannot have a has_many :through association.") + end end end class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError #:nodoc: - def initialize(owner_class_name, reflection) - super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.") + def initialize(owner_class_name = nil, reflection = nil) + if owner_class_name && reflection + super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.") + else + super("Cannot have a has_many :through association.") + end end end class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc: - def initialize(owner_class_name, reflection, source_reflection) - super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.") + def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil) + if owner_class_name && reflection && source_reflection + super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.") + else + super("Cannot have a has_many :through association.") + end end end class HasOneThroughCantAssociateThroughCollection < ActiveRecordError #:nodoc: - def initialize(owner_class_name, reflection, through_reflection) - super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.") + def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil) + if owner_class_name && reflection && through_reflection + super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.") + else + super("Cannot have a has_one :through association.") + end end end class HasOneAssociationPolymorphicThroughError < ActiveRecordError #:nodoc: - def initialize(owner_class_name, reflection) - super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.") + def initialize(owner_class_name = nil, reflection = nil) + if owner_class_name && reflection + super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.") + else + super("Cannot have a has_one :through association.") + end end end class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc: - def initialize(reflection) - through_reflection = reflection.through_reflection - source_reflection_names = reflection.source_reflection_names - source_associations = reflection.through_reflection.klass._reflections.keys - super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?") + def initialize(reflection = nil) + if reflection + through_reflection = reflection.through_reflection + source_reflection_names = reflection.source_reflection_names + source_associations = reflection.through_reflection.klass._reflections.keys + super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?") + else + super("Could not find the source association(s).") + end end end class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc: - def initialize(owner, reflection) - super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.") + def initialize(owner = nil, reflection = nil) + if owner && reflection + super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.") + else + super("Cannot modify association.") + end end end @@ -74,20 +114,32 @@ module ActiveRecord end class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc: - def initialize(owner, reflection) - super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.") + def initialize(owner = nil, reflection = nil) + if owner && reflection + super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.") + else + super("Cannot associate new records.") + end end end class HasManyThroughCantDissociateNewRecords < ActiveRecordError #:nodoc: - def initialize(owner, reflection) - super("Cannot dissociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.") + def initialize(owner = nil, reflection = nil) + if owner && reflection + super("Cannot dissociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.") + else + super("Cannot dissociate new records.") + end end end class ThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc: - def initialize(owner, reflection) - super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.") + def initialize(owner = nil, reflection = nil) + if owner && reflection + super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.") + else + super("Through nested associations are read-only.") + end end end @@ -98,14 +150,22 @@ module ActiveRecord end class EagerLoadPolymorphicError < ActiveRecordError #:nodoc: - def initialize(reflection) - super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}") + def initialize(reflection = nil) + if reflection + super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}") + else + super("Eager load polymorphic error.") + end end end class ReadOnlyAssociation < ActiveRecordError #:nodoc: - def initialize(reflection) - super("Cannot add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.") + def initialize(reflection = nil) + if reflection + super("Cannot add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.") + else + super("Read-only reflection error.") + end end end @@ -113,8 +173,12 @@ module ActiveRecord # (has_many, has_one) when there is at least 1 child associated instance. # ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project class DeleteRestrictionError < ActiveRecordError #:nodoc: - def initialize(name) - super("Cannot delete record because of dependent #{name}") + def initialize(name = nil) + if name + super("Cannot delete record because of dependent #{name}") + else + super("Delete restriction error.") + end end end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 29c711082a..79b6b80d84 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -881,7 +881,7 @@ module ActiveRecord !!@association.include?(record) end - def arel + def arel #:nodoc: scope.arel end 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 4653904105..08803303cf 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -23,6 +23,11 @@ module ActiveRecord table_name[0...table_alias_length].tr('.', '_') end + # Returns an array of table names defined in the database. + def tables(name = nil) + raise NotImplementedError, "#tables is not implemented" + end + # Checks to see if the table +table_name+ exists on the database. # # table_exists?(:developers) @@ -854,7 +859,7 @@ module ActiveRecord ActiveRecord::SchemaMigration.create_table end - def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths) + def assume_migrated_upto_version(version, migrations_paths) migrations_paths = Array(migrations_paths) version = version.to_i sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name) diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index ff43c7ec42..734b384e80 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,6 +1,6 @@ require 'active_record/connection_adapters/abstract_mysql_adapter' -gem 'mysql2', '~> 0.3.18' +gem 'mysql2', '>= 0.3.18', '< 0.5' require 'mysql2' module ActiveRecord diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb index 191c828e60..6155e53632 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb @@ -36,7 +36,7 @@ module ActiveRecord WHERE t.typname IN (%s) OR t.typtype IN (%s) - OR t.typinput::varchar = 'array_in' + OR t.typinput = 'array_in(cstring,oid,integer)'::regprocedure OR t.typelem != 0 SQL end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 718f04871d..6721fe144f 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -63,7 +63,7 @@ module ActiveRecord class RecordNotSaved < ActiveRecordError attr_reader :record - def initialize(message, record = nil) + def initialize(message = nil, record = nil) @record = record super(message) end @@ -80,7 +80,7 @@ module ActiveRecord class RecordNotDestroyed < ActiveRecordError attr_reader :record - def initialize(message, record = nil) + def initialize(message = nil, record = nil) @record = record super(message) end @@ -92,9 +92,9 @@ module ActiveRecord class StatementInvalid < ActiveRecordError attr_reader :original_exception - def initialize(message, original_exception = nil) - super(message) + def initialize(message = nil, original_exception = nil) @original_exception = original_exception + super(message) end end @@ -134,10 +134,14 @@ module ActiveRecord class StaleObjectError < ActiveRecordError attr_reader :record, :attempted_action - def initialize(record, attempted_action) - super("Attempted to #{attempted_action} a stale object: #{record.class.name}") - @record = record - @attempted_action = attempted_action + def initialize(record = nil, attempted_action = nil) + if record && attempted_action + @record = record + @attempted_action = attempted_action + super("Attempted to #{attempted_action} a stale object: #{record.class.name}.") + else + super("Stale object error.") + end end end @@ -196,7 +200,7 @@ module ActiveRecord class AttributeAssignmentError < ActiveRecordError attr_reader :exception, :attribute - def initialize(message, exception, attribute) + def initialize(message = nil, exception = nil, attribute = nil) super(message) @exception = exception @attribute = attribute @@ -209,7 +213,7 @@ module ActiveRecord class MultiparameterAssignmentErrors < ActiveRecordError attr_reader :errors - def initialize(errors) + def initialize(errors = nil) @errors = errors end end @@ -218,11 +222,15 @@ module ActiveRecord class UnknownPrimaryKey < ActiveRecordError attr_reader :model - def initialize(model, description = nil) - message = "Unknown primary key for table #{model.table_name} in model #{model}." - message += "\n#{description}" if description - super(message) - @model = model + def initialize(model = nil, description = nil) + if model + message = "Unknown primary key for table #{model.table_name} in model #{model}." + message += "\n#{description}" if description + @model = model + super(message) + else + super("Unknown primary key.") + end end end diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index e613d157aa..c26842014d 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -82,7 +82,7 @@ module ActiveRecord # Returns the class descending directly from ActiveRecord::Base, or # an abstract class, if any, in the inheritance hierarchy. # - # If A extends AR::Base, A.base_class will return A. If B descends from A + # If A extends ActiveRecord::Base, A.base_class will return A. If B descends from A # through some arbitrarily deep hierarchy, B.base_class will return A. # # If B < A and C < B and if A is an abstract_class then both B.base_class diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index 4d597a0ab1..b63caa4473 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -47,20 +47,40 @@ module ActiveRecord binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect end - name = color(name, nil, true) + name = colorize_payload_name(name, payload[:name]) sql = color(sql, sql_color(sql), true) debug " #{name} #{sql}#{binds}" end + private + + def colorize_payload_name(name, payload_name) + if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists + color(name, MAGENTA, true) + else + color(name, CYAN, true) + end + end + def sql_color(sql) case sql - when /\s*\Ainsert/i then GREEN - when /\s*\Aselect/i then BLUE - when /\s*\Aupdate/i then YELLOW - when /\s*\Adelete/i then RED - when /transaction\s*\Z/i then CYAN - else MAGENTA + when /\A\s*rollback/mi + RED + when /\s*.*?select .*for update/mi, /\A\s*lock/mi + WHITE + when /\A\s*select/i + BLUE + when /\A\s*insert/i + GREEN + when /\A\s*update/i + YELLOW + when /\A\s*delete/i + RED + when /transaction\s*\Z/i + CYAN + else + MAGENTA end end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 112d52024f..f087a1eaa6 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -84,35 +84,53 @@ module ActiveRecord end class DuplicateMigrationVersionError < MigrationError#:nodoc: - def initialize(version) - super("Multiple migrations have the version number #{version}") + def initialize(version = nil) + if version + super("Multiple migrations have the version number #{version}.") + else + super("Duplicate migration version error.") + end end end class DuplicateMigrationNameError < MigrationError#:nodoc: - def initialize(name) - super("Multiple migrations have the name #{name}") + def initialize(name = nil) + if name + super("Multiple migrations have the name #{name}.") + else + super("Duplicate migration name.") + end end end class UnknownMigrationVersionError < MigrationError #:nodoc: - def initialize(version) - super("No migration with version number #{version}") + def initialize(version = nil) + if version + super("No migration with version number #{version}.") + else + super("Unknown migration version.") + end end end class IllegalMigrationNameError < MigrationError#:nodoc: - def initialize(name) - super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)") + def initialize(name = nil) + if name + super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed).") + else + super("Illegal name for migration.") + end end end class PendingMigrationError < MigrationError#:nodoc: - def initialize - if defined?(Rails.env) - super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate RAILS_ENV=#{::Rails.env}") + def initialize(message = nil) + if !message && defined?(Rails.env) + super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate RAILS_ENV=#{::Rails.env}.") + elsif !message + super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate.") else - super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate") + super end end end @@ -937,10 +955,6 @@ module ActiveRecord Array(@migrations_paths) end - def migrations_path - migrations_paths.first - end - def migrations(paths) paths = Array(paths) diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 5a6f42ba09..2b0c755ef4 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -240,7 +240,7 @@ module ActiveRecord end # Returns a hash where the keys are column names and the values are - # default values when instantiating the AR object for this table. + # default values when instantiating the Active Record object for this table. def column_defaults load_schema _default_attributes.to_hash diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index cc0c59b52f..d543a84ff1 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -2,7 +2,8 @@ require 'active_record' db_namespace = namespace :db do task :load_config do - ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {} + ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {} + ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths end namespace :create do diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 5360db6a19..f8913eba06 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -32,6 +32,7 @@ module ActiveRecord end def self.add_reflection(ar, name, reflection) + ar.clear_reflections_cache ar._reflections = ar._reflections.merge(name.to_s => reflection) end @@ -67,18 +68,22 @@ module ActiveRecord # # @api public def reflections - ref = {} - _reflections.each do |name, reflection| - parent_reflection = reflection.parent_reflection + @__reflections ||= begin + ref = {} - if parent_reflection - parent_name = parent_reflection.name - ref[parent_name.to_s] = parent_reflection - else - ref[name] = reflection + _reflections.each do |name, reflection| + parent_reflection = reflection.parent_reflection + + if parent_reflection + parent_name = parent_reflection.name + ref[parent_name.to_s] = parent_reflection + else + ref[name] = reflection + end end + + ref end - ref end # Returns an array of AssociationReflection objects for all the @@ -118,6 +123,10 @@ module ActiveRecord def reflect_on_all_autosave_associations reflections.values.select { |reflection| reflection.options[:autosave] } end + + def clear_reflections_cache #:nodoc: + @__reflections = nil + end end # Holds all the methods that are shared between MacroReflection, AssociationReflection diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index d26db7d4cf..e232516b0c 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -24,12 +24,12 @@ module ActiveRecord end def build_from_hash(attributes) - attributes = convert_dot_notation_to_hash(attributes.stringify_keys) + attributes = convert_dot_notation_to_hash(attributes) expand_from_hash(attributes) end def create_binds(attributes) - attributes = convert_dot_notation_to_hash(attributes.stringify_keys) + attributes = convert_dot_notation_to_hash(attributes) create_binds_for_hash(attributes) end diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb index 159889d3b8..e81be63cd3 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb @@ -10,10 +10,10 @@ module ActiveRecord table = value.associated_table if value.base_class - queries[table.association_foreign_type] = value.base_class.name + queries[table.association_foreign_type.to_s] = value.base_class.name end - queries[table.association_foreign_key] = value.ids + queries[table.association_foreign_key.to_s] = value.ids predicate_builder.build_from_hash(queries) end diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb index 0430922be3..23eaab4699 100644 --- a/activerecord/lib/active_record/relation/where_clause_factory.rb +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -15,6 +15,7 @@ module ActiveRecord when Hash attributes = predicate_builder.resolve_column_aliases(opts) attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes) + attributes.stringify_keys! attributes, binds = predicate_builder.create_binds(attributes) diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index 0a5546a760..c1a42dc629 100644 --- a/activerecord/lib/active_record/schema.rb +++ b/activerecord/lib/active_record/schema.rb @@ -28,24 +28,6 @@ module ActiveRecord # ActiveRecord::Schema is only supported by database adapters that also # support migrations, the two features being very similar. class Schema < Migration - - # Returns the migrations paths. - # - # ActiveRecord::Schema.new.migrations_paths - # # => ["db/migrate"] # Rails migration path by default. - def migrations_paths - ActiveRecord::Migrator.migrations_paths - end - - def define(info, &block) # :nodoc: - instance_eval(&block) - - unless info[:version].blank? - initialize_schema_migrations_table - connection.assume_migrated_upto_version(info[:version], migrations_paths) - end - end - # Eval the given block. All methods available to the current connection # adapter are available within the block, so you can easily use the # database definition DSL to build up your schema (+create_table+, @@ -60,5 +42,23 @@ module ActiveRecord def self.define(info={}, &block) new.define(info, &block) end + + def define(info, &block) # :nodoc: + instance_eval(&block) + + if info[:version].present? + initialize_schema_migrations_table + connection.assume_migrated_upto_version(info[:version], migrations_paths) + end + end + + private + # Returns the migrations paths. + # + # ActiveRecord::Schema.new.migrations_paths + # # => ["db/migrate"] # Rails migration path by default. + def migrations_paths # :nodoc: + ActiveRecord::Migrator.migrations_paths + end end end diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index a1adf8e3ee..fac566e12b 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -6,8 +6,10 @@ module ActiveRecord included do # Stores the default scope for the class. class_attribute :default_scopes, instance_writer: false, instance_predicate: false + class_attribute :default_scope_override, instance_predicate: false self.default_scopes = [] + self.default_scope_override = nil end module ClassMethods @@ -99,12 +101,18 @@ module ActiveRecord self.default_scopes += [scope] end - def build_default_scope(base_rel = relation) # :nodoc: + def build_default_scope(base_rel = nil) # :nodoc: return if abstract_class? - if !Base.is_a?(method(:default_scope).owner) + + if self.default_scope_override.nil? + self.default_scope_override = !Base.is_a?(method(:default_scope).owner) + end + + if self.default_scope_override # The user has defined their own default scope method, so call that evaluate_default_scope { default_scope } elsif default_scopes.any? + base_rel ||= relation evaluate_default_scope do default_scopes.inject(base_rel) do |default_scope, scope| default_scope.merge(base_rel.scoping { scope.call }) diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index 41f1c55c3c..f9bb1cf5e0 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -10,13 +10,15 @@ module ActiveRecord end def resolve_column_aliases(hash) - hash = hash.dup - hash.keys.grep(Symbol) do |key| - if klass.attribute_alias? key - hash[klass.attribute_alias(key)] = hash.delete key + # This method is a hot spot, so for now, use Hash[] to dup the hash. + # https://bugs.ruby-lang.org/issues/7166 + new_hash = Hash[hash] + hash.each do |key, _| + if (key.is_a?(Symbol)) && klass.attribute_alias?(key) + new_hash[klass.attribute_alias(key)] = new_hash.delete(key) end end - hash + new_hash end def arel_attribute(column_name) diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 0b5dc6ed33..f1141a8613 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -221,12 +221,6 @@ module ActiveRecord end end - def load_schema_current_if_exists(format = ActiveRecord::Base.schema_format, file = nil, environment = env) - if File.exist?(file || schema_file(format)) - load_schema_current(format, file, environment) - end - end - def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env) each_current_configuration(environment) { |configuration| load_schema configuration, format, file diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 20e4235788..e759475cfb 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -15,14 +15,21 @@ module ActiveRecord # # == Time Zone aware attributes # - # By default, ActiveRecord::Base keeps all the datetime columns time zone aware by executing following code. + # Active Record keeps all the <tt>datetime</tt> and <tt>time</tt> columns + # time-zone aware. By default, these values are stored in the database as UTC + # and converted back to the current Time.zone when pulled from the database. # - # config.active_record.time_zone_aware_attributes = true + # This feature can be turned off completely by setting: # - # This feature can easily be turned off by assigning value <tt>false</tt> . + # config.active_record.time_zone_aware_attributes = false # - # If your attributes are time zone aware and you desire to skip time zone conversion to the current Time.zone - # when reading certain attributes then you can do following: + # You can also specify that only <tt>datetime</tt> columns should be time-zone + # aware (while <tt>time</tt> should not) by setting: + # + # ActiveRecord::Base.time_zone_aware_types = [:datetime] + # + # Finally, you can indicate specific attributes of a model for which time zone + # conversion should not applied, for instance by setting: # # class Topic < ActiveRecord::Base # self.skip_time_zone_conversion_for_attributes = [:written_on] diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 34d96b19fe..4113ca4561 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -12,10 +12,16 @@ module ActiveRecord class RecordInvalid < ActiveRecordError attr_reader :record - def initialize(record) - @record = record - errors = @record.errors.full_messages.join(", ") - super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid")) + def initialize(record = nil) + if record + @record = record + errors = @record.errors.full_messages.join(", ") + message = I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", errors: errors, default: :"errors.messages.record_invalid") + else + message = "Record invalid" + end + + super(message) end end @@ -54,7 +60,7 @@ module ActiveRecord # Validations with no <tt>:on</tt> option will run no matter the context. Validations with # some <tt>:on</tt> option will only run in the specified context. def valid?(context = nil) - context ||= (new_record? ? :create : :update) + context ||= default_validation_context output = super(context) errors.empty? && output end @@ -63,6 +69,10 @@ module ActiveRecord protected + def default_validation_context + new_record? ? :create : :update + end + def raise_validation_error raise(RecordInvalid.new(self)) end diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb deleted file mode 100644 index 2dd6ec5fe6..0000000000 --- a/activerecord/test/cases/adapters/postgresql/view_test.rb +++ /dev/null @@ -1,64 +0,0 @@ -require "cases/helper" -require "cases/view_test" - -class UpdateableViewTest < ActiveRecord::PostgreSQLTestCase - fixtures :books - - class PrintedBook < ActiveRecord::Base - self.primary_key = "id" - end - - setup do - @connection = ActiveRecord::Base.connection - @connection.execute <<-SQL - CREATE VIEW printed_books - AS SELECT id, name, status, format FROM books WHERE format = 'paperback' - SQL - end - - teardown do - @connection.execute "DROP VIEW printed_books" if @connection.table_exists? "printed_books" - end - - def test_update_record - book = PrintedBook.first - book.name = "AWDwR" - book.save! - book.reload - assert_equal "AWDwR", book.name - end - - def test_insert_record - PrintedBook.create! name: "Rails in Action", status: 0, format: "paperback" - - new_book = PrintedBook.last - assert_equal "Rails in Action", new_book.name - end - - def test_update_record_to_fail_view_conditions - book = PrintedBook.first - book.format = "ebook" - book.save! - - assert_raises ActiveRecord::RecordNotFound do - book.reload - end - end -end - -if ActiveRecord::Base.connection.respond_to?(:supports_materialized_views?) && - ActiveRecord::Base.connection.supports_materialized_views? -class MaterializedViewTest < ActiveRecord::PostgreSQLTestCase - include ViewBehavior - - private - def create_view(name, query) - @connection.execute "CREATE MATERIALIZED VIEW #{name} AS #{query}" - end - - def drop_view(name) - @connection.execute "DROP MATERIALIZED VIEW #{name}" if @connection.table_exists? name - - end -end -end diff --git a/activerecord/test/cases/errors_test.rb b/activerecord/test/cases/errors_test.rb new file mode 100644 index 0000000000..0711a372f2 --- /dev/null +++ b/activerecord/test/cases/errors_test.rb @@ -0,0 +1,16 @@ +require_relative "../cases/helper" + +class ErrorsTest < ActiveRecord::TestCase + def test_can_be_instantiated_with_no_args + base = ActiveRecord::ActiveRecordError + error_klasses = ObjectSpace.each_object(Class).select { |klass| klass < base } + + error_klasses.each do |error_klass| + begin + error_klass.new.inspect + rescue ArgumentError + raise "Instance of #{error_klass} can't be initialized with no arguments" + end + end + end +end diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 4192d12ff4..3846ba8e7f 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -7,6 +7,20 @@ require "active_support/log_subscriber/test_helper" class LogSubscriberTest < ActiveRecord::TestCase include ActiveSupport::LogSubscriber::TestHelper include ActiveSupport::Logger::Severity + REGEXP_CLEAR = Regexp.escape(ActiveRecord::LogSubscriber::CLEAR) + REGEXP_BOLD = Regexp.escape(ActiveRecord::LogSubscriber::BOLD) + REGEXP_MAGENTA = Regexp.escape(ActiveRecord::LogSubscriber::MAGENTA) + REGEXP_CYAN = Regexp.escape(ActiveRecord::LogSubscriber::CYAN) + SQL_COLORINGS = { + SELECT: Regexp.escape(ActiveRecord::LogSubscriber::BLUE), + INSERT: Regexp.escape(ActiveRecord::LogSubscriber::GREEN), + UPDATE: Regexp.escape(ActiveRecord::LogSubscriber::YELLOW), + DELETE: Regexp.escape(ActiveRecord::LogSubscriber::RED), + LOCK: Regexp.escape(ActiveRecord::LogSubscriber::WHITE), + ROLLBACK: Regexp.escape(ActiveRecord::LogSubscriber::RED), + TRANSACTION: REGEXP_CYAN, + OTHER: REGEXP_MAGENTA + } class TestDebugLogSubscriber < ActiveRecord::LogSubscriber attr_reader :debugs @@ -71,6 +85,90 @@ class LogSubscriberTest < ActiveRecord::TestCase assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last) end + def test_basic_query_logging_coloration + event = Struct.new(:duration, :payload) + logger = TestDebugLogSubscriber.new + logger.colorize_logging = true + SQL_COLORINGS.each do |verb, color_regex| + logger.sql(event.new(0, sql: verb.to_s)) + assert_match(/#{REGEXP_BOLD}#{color_regex}#{verb}#{REGEXP_CLEAR}/i, logger.debugs.last) + end + end + + def test_basic_payload_name_logging_coloration_generic_sql + event = Struct.new(:duration, :payload) + logger = TestDebugLogSubscriber.new + logger.colorize_logging = true + SQL_COLORINGS.each do |verb, _| + logger.sql(event.new(0, sql: verb.to_s)) + assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) + + logger.sql(event.new(0, {sql: verb.to_s, name: "SQL"})) + assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA}SQL \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) + end + end + + def test_basic_payload_name_logging_coloration_named_sql + event = Struct.new(:duration, :payload) + logger = TestDebugLogSubscriber.new + logger.colorize_logging = true + SQL_COLORINGS.each do |verb, _| + logger.sql(event.new(0, {sql: verb.to_s, name: "Model Load"})) + assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Load \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) + + logger.sql(event.new(0, {sql: verb.to_s, name: "Model Exists"})) + assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Exists \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) + + logger.sql(event.new(0, {sql: verb.to_s, name: "ANY SPECIFIC NAME"})) + assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}ANY SPECIFIC NAME \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) + end + end + + def test_query_logging_coloration_with_nested_select + event = Struct.new(:duration, :payload) + logger = TestDebugLogSubscriber.new + logger.colorize_logging = true + SQL_COLORINGS.slice(:SELECT, :INSERT, :UPDATE, :DELETE).each do |verb, color_regex| + logger.sql(event.new(0, sql: "#{verb} WHERE ID IN SELECT")) + assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{color_regex}#{verb} WHERE ID IN SELECT#{REGEXP_CLEAR}/i, logger.debugs.last) + end + end + + def test_query_logging_coloration_with_multi_line_nested_select + event = Struct.new(:duration, :payload) + logger = TestDebugLogSubscriber.new + logger.colorize_logging = true + SQL_COLORINGS.slice(:SELECT, :INSERT, :UPDATE, :DELETE).each do |verb, color_regex| + sql = <<-EOS + #{verb} + WHERE ID IN ( + SELECT ID FROM THINGS + ) + EOS + logger.sql(event.new(0, sql: sql)) + assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{color_regex}.*#{verb}.*#{REGEXP_CLEAR}/mi, logger.debugs.last) + end + end + + def test_query_logging_coloration_with_lock + event = Struct.new(:duration, :payload) + logger = TestDebugLogSubscriber.new + logger.colorize_logging = true + sql = <<-EOS + SELECT * FROM + (SELECT * FROM mytable FOR UPDATE) ss + WHERE col1 = 5; + EOS + logger.sql(event.new(0, sql: sql)) + assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*FOR UPDATE.*#{REGEXP_CLEAR}/mi, logger.debugs.last) + + sql = <<-EOS + LOCK TABLE films IN SHARE MODE; + EOS + logger.sql(event.new(0, sql: sql)) + assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*LOCK TABLE.*#{REGEXP_CLEAR}/mi, logger.debugs.last) + end + def test_exists_query_logging Developer.exists? 1 wait diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index f4f316f393..a429d06aad 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -52,6 +52,13 @@ class ValidationsTest < ActiveRecord::TestCase assert r.valid?(:special_case) end + def test_invalid_using_multiple_contexts + r = WrongReply.new(:title => 'Wrong Create') + assert r.invalid?([:special_case, :create]) + assert_equal "Invalid", r.errors[:author_name].join + assert_equal "is Wrong Create", r.errors[:title].join + end + def test_validate r = WrongReply.new diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb index f9dca1e196..1eb1430065 100644 --- a/activerecord/test/cases/view_test.rb +++ b/activerecord/test/cases/view_test.rb @@ -110,4 +110,70 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase assert_nil Paperback.primary_key end end + +# sqlite dose not support CREATE, INSERT, and DELETE for VIEW +if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) +class UpdateableViewTest < ActiveRecord::TestCase + self.use_transactional_tests = false + fixtures :books + + class PrintedBook < ActiveRecord::Base + self.primary_key = "id" + end + + setup do + @connection = ActiveRecord::Base.connection + @connection.execute <<-SQL + CREATE VIEW printed_books + AS SELECT id, name, status, format FROM books WHERE format = 'paperback' + SQL + end + + teardown do + @connection.execute "DROP VIEW printed_books" if @connection.table_exists? "printed_books" + end + + def test_update_record + book = PrintedBook.first + book.name = "AWDwR" + book.save! + book.reload + assert_equal "AWDwR", book.name + end + + def test_insert_record + PrintedBook.create! name: "Rails in Action", status: 0, format: "paperback" + + new_book = PrintedBook.last + assert_equal "Rails in Action", new_book.name + end + + def test_update_record_to_fail_view_conditions + book = PrintedBook.first + book.format = "ebook" + book.save! + + assert_raises ActiveRecord::RecordNotFound do + book.reload + end + end +end +end # end fo `if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)` +end # end fo `if ActiveRecord::Base.connection.supports_views?` + +if ActiveRecord::Base.connection.respond_to?(:supports_materialized_views?) && + ActiveRecord::Base.connection.supports_materialized_views? +class MaterializedViewTest < ActiveRecord::PostgreSQLTestCase + include ViewBehavior + + private + def create_view(name, query) + @connection.execute "CREATE MATERIALIZED VIEW #{name} AS #{query}" + end + + def drop_view(name) + @connection.execute "DROP MATERIALIZED VIEW #{name}" if @connection.table_exists? name + + end +end end diff --git a/activerecord/test/models/face.rb b/activerecord/test/models/face.rb index 91e46f83e5..af76fea52c 100644 --- a/activerecord/test/models/face.rb +++ b/activerecord/test/models/face.rb @@ -1,7 +1,7 @@ class Face < ActiveRecord::Base belongs_to :man, :inverse_of => :face belongs_to :polymorphic_man, :polymorphic => true, :inverse_of => :polymorphic_face - # Oracle identifier lengh is limited to 30 bytes or less, `polymorphic` renamed `poly` + # Oracle identifier length is limited to 30 bytes or less, `polymorphic` renamed `poly` belongs_to :poly_man_without_inverse, :polymorphic => true # These is a "broken" inverse_of for the purposes of testing belongs_to :horrible_man, :class_name => 'Man', :inverse_of => :horrible_face |