aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md42
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb47
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb43
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb4
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb7
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb1
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb4
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb6
-rw-r--r--activerecord/lib/active_record/type/internal/abstract_json.rb6
-rw-r--r--activerecord/lib/active_record/type/time.rb12
-rw-r--r--activerecord/test/cases/adapters/mysql2/json_test.rb15
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb17
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb11
-rw-r--r--activerecord/test/cases/comment_test.rb91
-rw-r--r--activerecord/test/cases/dirty_test.rb4
-rw-r--r--activerecord/test/cases/finder_test.rb7
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb5
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb7
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb40
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb33
-rw-r--r--activerecord/test/cases/tasks/sqlite_rake_test.rb30
32 files changed, 495 insertions, 81 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 291e5a2513..9e51fb0ca3 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,45 @@
+* Added notice when a database is successfully created or dropped.
+
+ Example:
+
+ $ bin/rails db:create
+ Created database 'blog_development'
+ Created database 'blog_test'
+
+ $ bin/rails db:drop
+ Dropped database 'blog_development'
+ Dropped database 'blog_test'
+
+ Changed older notices
+ `blog_development already exists` to `Database 'blog_development' already exists`.
+ and
+ `Couldn't drop blog_development` to `Couldn't drop database 'blog_development'`.
+
+ *bogdanvlviv*
+
+* Database comments. Annotate database objects (tables, columns, indexes)
+ with comments stored in database metadata. PostgreSQL & MySQL support.
+
+ create_table :pages, force: :cascade, comment: 'CMS content pages' do |t|
+ t.string :path, comment: 'Path fragment of page URL used for routing'
+ t.string :locale, comment: 'RFC 3066 locale code of website language section'
+ t.index [:path, :locale], comment: 'Look up pages by URI'
+ end
+
+ *Andrey Novikov*
+
+* Add `quoted_time` for truncating the date part of a TIME column value.
+ This fixes queries on TIME column on MariaDB, as it doesn't ignore the
+ date part of the string when it coerces to time.
+
+ *Ryuta Kamizono*
+
+* Properly accept all valid JSON primitives in the JSON data type.
+
+ Fixes #24234
+
+ *Sean Griffin*
+
* MariaDB 5.3+ supports microsecond datetime precision.
*Jeremy Daer*
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 882f1225fc..48437a1c9e 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -64,11 +64,11 @@ module ActiveRecord
foreign_key = join_keys.foreign_key
value = transform_value(owner[foreign_key])
- scope = scope.where(table.name.to_sym => { key => value })
+ scope = scope.where(table.name => { key => value })
if reflection.type
polymorphic_type = transform_value(owner.class.base_class.name)
- scope = scope.where(table.name.to_sym => { reflection.type => polymorphic_type })
+ scope = scope.where(table.name => { reflection.type => polymorphic_type })
end
scope
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 2eeefb13d7..860ef17dca 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -146,6 +146,10 @@ module ActiveRecord
end
end
+ def quoted_time(value) # :nodoc:
+ quoted_date(value).sub(/\A2000-01-01 /, '')
+ end
+
def prepare_binds_for_database(binds) # :nodoc:
binds.map(&:value_for_database)
end
@@ -166,6 +170,7 @@ module ActiveRecord
# BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s('F')
when Numeric, ActiveSupport::Duration then value.to_s
+ when Type::Time::Value then "'#{quoted_time(value)}'"
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
when Class then "'#{value}'"
@@ -181,6 +186,7 @@ module ActiveRecord
when false then unquoted_false
# BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s('F')
+ when Type::Time::Value then quoted_time(value)
when Date, Time then quoted_date(value)
when *types_which_need_no_typecasting
value
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index 0ba4d94e3c..f3f1618473 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -54,6 +54,7 @@ module ActiveRecord
end
create_sql << "(#{statements.join(', ')}) " if statements.present?
+ add_table_options!(create_sql, table_options(o))
create_sql << "#{o.options}"
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
create_sql
@@ -82,6 +83,16 @@ module ActiveRecord
"DROP CONSTRAINT #{quote_column_name(name)}"
end
+ def table_options(o)
+ options = {}
+ options[:comment] = o.comment
+ options
+ end
+
+ def add_table_options!(sql, _options)
+ sql
+ end
+
def column_options(o)
column_options = {}
column_options[:null] = o.null unless o.null.nil?
@@ -92,6 +103,7 @@ module ActiveRecord
column_options[:auto_increment] = o.auto_increment
column_options[:primary_key] = o.primary_key
column_options[:collation] = o.collation
+ column_options[:comment] = o.comment
column_options
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 4f97c7c065..1603c38e35 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -3,14 +3,14 @@ module ActiveRecord
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using) #:nodoc:
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:
end
# Abstract representation of a column definition. Instances of this type
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
@@ -207,9 +207,9 @@ module ActiveRecord
include ColumnMethods
attr_accessor :indexes
- attr_reader :name, :temporary, :options, :as, :foreign_keys
+ attr_reader :name, :temporary, :options, :as, :foreign_keys, :comment
- def initialize(name, temporary, options, as = nil)
+ def initialize(name, temporary, options, as = nil, comment = nil)
@columns_hash = {}
@indexes = {}
@foreign_keys = []
@@ -218,6 +218,7 @@ module ActiveRecord
@options = options
@as = as
@name = name
+ @comment = comment
end
def primary_keys(name = nil) # :nodoc:
@@ -373,6 +374,7 @@ module ActiveRecord
column.auto_increment = options[:auto_increment]
column.primary_key = type == :primary_key || options[:primary_key]
column.collation = options[:collation]
+ column.comment = options[:comment]
column
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index 4880d216d6..69d2780f7c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -46,12 +46,14 @@ module ActiveRecord
spec[:collation] = collation
end
+ spec[:comment] = column.comment.inspect if column.comment
+
spec
end
# Lists the valid migration options
def migration_keys
- [:name, :limit, :precision, :scale, :default, :null, :collation]
+ [:name, :limit, :precision, :scale, :default, :null, :collation, :comment]
end
private
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 020d9bbdca..ca2539f845 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -18,6 +18,11 @@ module ActiveRecord
nil
end
+ # Returns comment associated with given table in database
+ def table_comment(table_name)
+ nil
+ end
+
# Truncates a table alias according to the limits of the current adapter.
def table_alias_for(table_name)
table_name[0...table_alias_length].tr('.', '_')
@@ -255,7 +260,7 @@ module ActiveRecord
#
# See also TableDefinition#column for details on how to create columns.
def create_table(table_name, options = {})
- td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
+ td = create_table_definition table_name, options[:temporary], options[:options], options[:as], options[:comment]
if options[:id] != false && !options[:as]
pk = options.fetch(:primary_key) do
@@ -265,7 +270,7 @@ module ActiveRecord
if pk.is_a?(Array)
td.primary_keys pk
else
- td.primary_key pk, options.fetch(:id, :primary_key), options
+ td.primary_key pk, options.fetch(:id, :primary_key), options.except(:comment)
end
end
@@ -283,6 +288,13 @@ module ActiveRecord
end
end
+ if supports_comments? && !supports_comments_in_create?
+ change_table_comment(table_name, options[:comment]) if options[:comment]
+ td.columns.each do |column|
+ change_column_comment(table_name, column.name, column.comment) if column.comment
+ end
+ end
+
result
end
@@ -776,7 +788,8 @@ module ActiveRecord
# [<tt>:type</tt>]
# The reference column type. Defaults to +:integer+.
# [<tt>:index</tt>]
- # Add an appropriate index. Defaults to false.
+ # Add an appropriate index. Defaults to false.
+ # See #add_index for usage of this option.
# [<tt>:foreign_key</tt>]
# Add an appropriate foreign key constraint. Defaults to false.
# [<tt>:polymorphic</tt>]
@@ -796,6 +809,14 @@ module ActiveRecord
#
# add_reference(:products, :supplier, polymorphic: true, index: true)
#
+ # ====== Create a supplier_id column with a unique index
+ #
+ # add_reference(:products, :supplier, index: { unique: true })
+ #
+ # ====== Create a supplier_id column with a named index
+ #
+ # add_reference(:products, :supplier, index: { name: "my_supplier_index" })
+ #
# ====== Create a supplier_id column and appropriate foreign key
#
# add_reference(:products, :supplier, foreign_key: true)
@@ -1078,7 +1099,7 @@ module ActiveRecord
def add_index_options(table_name, column_name, options = {}) #:nodoc:
column_names = Array(column_name)
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :comment)
index_type = options[:type].to_s if options.key?(:type)
index_type ||= options[:unique] ? "UNIQUE" : ""
@@ -1106,13 +1127,25 @@ module ActiveRecord
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
- [index_name, index_type, index_columns, index_options, algorithm, using]
+ comment = options[:comment] if options.key?(:comment)
+
+ [index_name, index_type, index_columns, index_options, algorithm, using, comment]
end
def options_include_default?(options)
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
end
+ # Adds comment for given table or drops it if +nil+ given
+ def change_table_comment(table_name, comment)
+ raise NotImplementedError, "change_table_comment is not implemented"
+ end
+
+ # Adds comment for given table column or drops it if +nil+ given
+ def change_column_comment(table_name, column_name, comment) #:nodoc:
+ raise NotImplementedError, "change_column_comment is not implemented"
+ end
+
protected
def add_index_sort_order(option_strings, column_names, options = {})
if options.is_a?(Hash) && order = options[:order]
@@ -1194,8 +1227,8 @@ module ActiveRecord
end
private
- def create_table_definition(name, temporary = false, options = nil, as = nil)
- TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(name, temporary = false, options = nil, as = nil, comment = nil)
+ TableDefinition.new(name, temporary, options, as, comment)
end
def create_alter_table(name)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 069346253a..b753c348de 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -278,6 +278,16 @@ module ActiveRecord
false
end
+ # Does adapter supports comments on database objects (tables, columns, indexes)?
+ def supports_comments?
+ false
+ end
+
+ # Can comments for tables, columns, and indexes be specified in create/alter table statements?
+ def supports_comments_in_create?
+ false
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 1797af4e97..cea5a04006 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -77,10 +77,14 @@ module ActiveRecord
}
end
- def version
+ def version #:nodoc:
@version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
end
+ def mariadb? # :nodoc:
+ full_version =~ /mariadb/i
+ end
+
# Returns true, since this connection adapter supports migrations.
def supports_migrations?
true
@@ -156,8 +160,8 @@ module ActiveRecord
raise NotImplementedError
end
- def new_column(field, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc:
- MySQL::Column.new(field, default, sql_type_metadata, null, table_name, default_function, collation)
+ def new_column(field, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil, comment = nil) # :nodoc:
+ MySQL::Column.new(field, default, sql_type_metadata, null, table_name, default_function, collation, comment)
end
# Must return the MySQL error number from the exception, if the exception has an
@@ -369,7 +373,7 @@ module ActiveRecord
mysql_index_type = row[:Index_type].downcase.to_sym
index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using, row[:Index_comment].presence)
end
indexes.last.columns << row[:Column_name]
@@ -390,10 +394,18 @@ module ActiveRecord
else
default, default_function = field[:Default], nil
end
- new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation])
+ new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], field[:Comment].presence)
end
end
+ def table_comment(table_name)
+ select_value(<<-SQL.strip_heredoc, 'SCHEMA')
+ SELECT table_comment
+ FROM INFORMATION_SCHEMA.TABLES
+ WHERE table_name=#{quote(table_name)};
+ SQL
+ end
+
def create_table(table_name, options = {}) #:nodoc:
super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
end
@@ -478,8 +490,10 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
+ index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
+ sql << " COMMENT #{quote(comment)}" if comment
+ execute sql
end
def foreign_keys(table_name)
@@ -517,7 +531,12 @@ module ActiveRecord
raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
# strip AUTO_INCREMENT
- raw_table_options.sub(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
+ raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
+
+ # strip COMMENT
+ raw_table_options.sub!(/ COMMENT='.+'/, '')
+
+ raw_table_options
end
# Maps logical Rails types to MySQL-specific data types.
@@ -782,10 +801,6 @@ module ActiveRecord
subselect.from subsubselect.as('__active_record_temp')
end
- def mariadb?
- full_version =~ /mariadb/i
- end
-
def supports_rename_index?
mariadb? ? false : version >= '5.7.6'
end
@@ -866,8 +881,8 @@ module ActiveRecord
create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
end
- def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
- MySQL::TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(name, temporary = false, options = nil, as = nil, comment = nil) # :nodoc:
+ MySQL::TableDefinition.new(name, temporary, options, as, comment)
end
def integer_to_sql(limit) # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 2e718b29fa..1c798d99f2 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -5,7 +5,7 @@ module ActiveRecord
module ConnectionAdapters
# An abstract definition of a column in a table.
class Column
- attr_reader :name, :default, :sql_type_metadata, :null, :table_name, :default_function, :collation
+ attr_reader :name, :default, :sql_type_metadata, :null, :table_name, :default_function, :collation, :comment
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
@@ -15,7 +15,7 @@ module ActiveRecord
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
# +sql_type_metadata+ is various information about the type of the column
# +null+ determines if this column allows +NULL+ values.
- def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil)
+ def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment = nil)
@name = name.freeze
@table_name = table_name
@sql_type_metadata = sql_type_metadata
@@ -23,6 +23,7 @@ module ActiveRecord
@default = default
@default_function = default_function
@collation = collation
+ @comment = comment
end
def has_default?
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
index 1e2c859af9..5ab81640e8 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
@@ -2,6 +2,9 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
class SchemaCreation < AbstractAdapter::SchemaCreation
+ delegate :quote, to: :@conn
+ private :quote
+
private
def visit_DropForeignKey(name)
@@ -22,6 +25,13 @@ module ActiveRecord
add_column_position!(change_column_sql, column_options(o.column))
end
+ def add_table_options!(sql, options)
+ super
+ if options[:comment]
+ sql << "COMMENT #{quote(options[:comment])} "
+ end
+ end
+
def column_options(o)
column_options = super
column_options[:charset] = o.charset
@@ -35,7 +45,11 @@ module ActiveRecord
if options[:collation]
sql << " COLLATE #{options[:collation]}"
end
- super
+ new_sql = super
+ if options[:comment]
+ new_sql << " COMMENT #{quote(options[:comment])}"
+ end
+ new_sql
end
def add_column_position!(sql, options)
@@ -48,8 +62,9 @@ module ActiveRecord
end
def index_in_create(table_name, column_name, options)
- index_name, index_type, index_columns, _, _, index_using = @conn.add_index_options(table_name, column_name, options)
- "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns}) "
+ index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options)
+ index_option = " COMMENT #{quote(comment)}" if comment
+ "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_option} "
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index e7541748de..ec343a5a57 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -45,6 +45,14 @@ module ActiveRecord
!mariadb? && version >= '5.7.8'
end
+ def supports_comments?
+ true
+ end
+
+ def supports_comments_in_create?
+ true
+ end
+
# HELPER METHODS ===========================================
def each_hash(result) # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 39e8492688..36331204f7 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/string/strip'
+
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
@@ -172,7 +174,8 @@ module ActiveRecord
table = Utils.extract_schema_qualified_name(table_name.to_s)
result = query(<<-SQL, 'SCHEMA')
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
INNER JOIN pg_class i ON d.indexrelid = i.oid
@@ -190,6 +193,7 @@ module ActiveRecord
indkey = row[2].split(" ").map(&:to_i)
inddef = row[3]
oid = row[4]
+ comment = row[5]
columns = Hash[query(<<-SQL, "SCHEMA")]
SELECT a.attnum, a.attname
@@ -207,7 +211,7 @@ module ActiveRecord
where = inddef.scan(/WHERE (.+)$/).flatten[0]
using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
- IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
+ IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using, comment)
end
end.compact
end
@@ -215,18 +219,33 @@ module ActiveRecord
# Returns the list of all column definitions for a table.
def columns(table_name) # :nodoc:
table_name = table_name.to_s
- column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation|
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation, comment|
oid = oid.to_i
fmod = fmod.to_i
type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
default_value = extract_value_from_default(default)
default_function = extract_default_function(default_value, default)
- new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, collation)
+ new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, collation, comment)
end
end
- def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc:
- PostgreSQLColumn.new(name, default, sql_type_metadata, null, table_name, default_function, collation)
+ def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil, comment = nil) # :nodoc:
+ PostgreSQLColumn.new(name, default, sql_type_metadata, null, table_name, default_function, collation, comment)
+ end
+
+ # Returns a comment stored in database for given table
+ def table_comment(table_name) # :nodoc:
+ name = Utils.extract_schema_qualified_name(table_name.to_s)
+ return nil unless name.identifier
+
+ select_value(<<-SQL.strip_heredoc, 'SCHEMA')
+ SELECT pg_catalog.obj_description(c.oid, 'pg_class')
+ FROM pg_catalog.pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relname = '#{name.identifier}'
+ AND c.relkind IN ('r') -- (r)elation/table
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
+ SQL
end
# Returns the current database name.
@@ -445,6 +464,7 @@ module ActiveRecord
def add_column(table_name, column_name, type, options = {}) #:nodoc:
clear_cache!
super
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
@@ -466,6 +486,7 @@ module ActiveRecord
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
end
# Changes the default value of a table column.
@@ -494,6 +515,18 @@ module ActiveRecord
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
end
+ # Adds comment for given table column or drops it if +comment+ is a +nil+
+ def change_column_comment(table_name, column_name, comment) # :nodoc:
+ clear_cache!
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
+ end
+
+ # Adds comment for given table or drops it if +comment+ is a +nil+
+ def change_table_comment(table_name, comment) # :nodoc:
+ clear_cache!
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
+ end
+
# Renames a column in a table.
def rename_column(table_name, column_name, new_column_name) #:nodoc:
clear_cache!
@@ -502,8 +535,10 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
+ result = execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
+ execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
+ result # Result of execute is used in tests in activerecord/test/cases/adapters/postgresql/active_schema_test.rb
end
def remove_index(table_name, options = {}) #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 6497b1cc31..cf04f32206 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -159,6 +159,10 @@ module ActiveRecord
postgresql_version >= 90200
end
+ def supports_comments?
+ true
+ end
+
def index_algorithms
{ concurrently: 'CONCURRENTLY' }
end
@@ -712,7 +716,7 @@ module ActiveRecord
# Returns the list of a table's column names, data types, and default values.
#
# The underlying query is roughly:
- # SELECT column.name, column.type, default.value
+ # SELECT column.name, column.type, default.value, column.comment
# FROM column LEFT JOIN default
# ON column.table_id = default.table_id
# AND column.num = default.column_num
@@ -732,7 +736,8 @@ module ActiveRecord
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
(SELECT c.collname FROM pg_collation c, pg_type t
- WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation)
+ WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation),
+ col_description(a.attrelid, a.attnum) AS comment
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
@@ -746,8 +751,8 @@ module ActiveRecord
$1.strip if $1
end
- def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
- PostgreSQL::TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(name, temporary = false, options = nil, as = nil, comment = nil) # :nodoc:
+ PostgreSQL::TableDefinition.new(name, temporary, options, as, comment)
end
def can_perform_case_insensitive_comparison_for?(column)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
index d3a91f73c8..d5a181d3e2 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -14,6 +14,10 @@ module ActiveRecord
@quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
end
+ def quoted_time(value)
+ quoted_date(value)
+ end
+
private
def _quote(value)
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 550416238f..ecce949370 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -84,7 +84,6 @@ module ActiveRecord
return ["1=0"] if attributes.empty?
attributes.flat_map do |key, value|
- key = key.to_s
if value.is_a?(Hash)
associated_predicate_builder(key).expand_from_hash(value)
else
@@ -137,11 +136,11 @@ module ActiveRecord
end
def convert_dot_notation_to_hash(attributes)
- dot_notation = attributes.keys.select do |s|
- s.respond_to?(:include?) && s.include?(".".freeze)
+ dot_notation = attributes.select do |k, v|
+ k.include?(".".freeze) && !v.is_a?(Hash)
end
- dot_notation.each do |key|
+ dot_notation.each_key do |key|
table_name, column_name = key.split(".".freeze)
value = attributes.delete(key)
attributes[table_name] ||= {}
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
index c0ccb00b6f..dbf172a577 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_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index affcd9aed1..bc4adb4ed7 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -138,6 +138,9 @@ HEADER
table_options = @connection.table_options(table)
tbl.print ", options: #{table_options.inspect}" unless table_options.blank?
+ comment = @connection.table_comment(table)
+ tbl.print ", comment: #{comment.inspect}" if comment
+
tbl.puts " do |t|"
# then dump all non-primary key columns
@@ -209,6 +212,7 @@ HEADER
statement_parts << "where: #{index.where.inspect}" if index.where
statement_parts << "using: #{index.using.inspect}" if index.using
statement_parts << "type: #{index.type.inspect}" if index.type
+ statement_parts << "comment: #{index.comment.inspect}" if index.comment
" #{statement_parts.join(', ')}"
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 8881986f1b..9aea5b360b 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -107,8 +107,9 @@ module ActiveRecord
def create(*arguments)
configuration = arguments.first
class_for_adapter(configuration['adapter']).new(*arguments).create
+ $stdout.puts "Created database '#{configuration['database']}'"
rescue DatabaseAlreadyExists
- $stderr.puts "#{configuration['database']} already exists"
+ $stderr.puts "Database '#{configuration['database']}' already exists"
rescue Exception => error
$stderr.puts error
$stderr.puts "Couldn't create database for #{configuration.inspect}"
@@ -133,11 +134,12 @@ module ActiveRecord
def drop(*arguments)
configuration = arguments.first
class_for_adapter(configuration['adapter']).new(*arguments).drop
+ $stdout.puts "Dropped database '#{configuration['database']}'"
rescue ActiveRecord::NoDatabaseError
$stderr.puts "Database '#{configuration['database']}' does not exist"
rescue Exception => error
$stderr.puts error
- $stderr.puts "Couldn't drop #{configuration['database']}"
+ $stderr.puts "Couldn't drop database '#{configuration['database']}'"
raise
end
diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb
index 097d1bd363..513c938088 100644
--- a/activerecord/lib/active_record/type/internal/abstract_json.rb
+++ b/activerecord/lib/active_record/type/internal/abstract_json.rb
@@ -17,11 +17,7 @@ module ActiveRecord
end
def serialize(value)
- if value.is_a?(::Array) || value.is_a?(::Hash)
- ::ActiveSupport::JSON.encode(value)
- else
- value
- end
+ ::ActiveSupport::JSON.encode(value)
end
def accessor
diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb
index 70988d84ff..7da49e43c7 100644
--- a/activerecord/lib/active_record/type/time.rb
+++ b/activerecord/lib/active_record/type/time.rb
@@ -2,6 +2,18 @@ module ActiveRecord
module Type
class Time < ActiveModel::Type::Time
include Internal::Timezone
+
+ class Value < DelegateClass(::Time) # :nodoc:
+ end
+
+ def serialize(value)
+ case value = super
+ when ::Time
+ Value.new(value)
+ else
+ value
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb
index c8c933af5e..9c3fef1b59 100644
--- a/activerecord/test/cases/adapters/mysql2/json_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/json_test.rb
@@ -161,12 +161,19 @@ class Mysql2JSONTest < ActiveRecord::Mysql2TestCase
assert_not json.changed?
end
- def test_assigning_invalid_json
- json = JsonDataType.new
+ def test_assigning_string_literal
+ json = JsonDataType.create(payload: "foo")
+ assert_equal "foo", json.payload
+ end
- json.payload = 'foo'
+ def test_assigning_number
+ json = JsonDataType.create(payload: 1.234)
+ assert_equal 1.234, json.payload
+ end
- assert_nil json.payload
+ def test_assigning_boolean
+ json = JsonDataType.create(payload: true)
+ assert_equal true, json.payload
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index b3b121b4fb..663de680b5 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -38,7 +38,7 @@ module PostgresqlJSONSharedTestCases
end
def test_default
- @connection.add_column 'json_data_type', 'permissions', column_type, default: '{"users": "read", "posts": ["read", "write"]}'
+ @connection.add_column 'json_data_type', 'permissions', column_type, default: {"users": "read", "posts": ["read", "write"]}
JsonDataType.reset_column_information
assert_equal({"users"=>"read", "posts"=>["read", "write"]}, JsonDataType.column_defaults['permissions'])
@@ -178,12 +178,19 @@ module PostgresqlJSONSharedTestCases
assert_not json.changed?
end
- def test_assigning_invalid_json
- json = JsonDataType.new
+ def test_assigning_string_literal
+ json = JsonDataType.create(payload: "foo")
+ assert_equal "foo", json.payload
+ end
- json.payload = 'foo'
+ def test_assigning_number
+ json = JsonDataType.create(payload: 1.234)
+ assert_equal 1.234, json.payload
+ end
- assert_nil json.payload
+ def test_assigning_boolean
+ json = JsonDataType.create(payload: true)
+ assert_equal true, json.payload
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index 87a892db37..f3ec2b98d3 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -8,9 +8,7 @@ module ActiveRecord
class SQLite3Adapter
class QuotingTest < ActiveRecord::SQLite3TestCase
def setup
- @conn = Base.sqlite3_connection :database => ':memory:',
- :adapter => 'sqlite3',
- :timeout => 100
+ @conn = ActiveRecord::Base.connection
end
def test_type_cast_binary_encoding_without_logger
@@ -89,6 +87,13 @@ module ActiveRecord
assert_equal "'hello'", @conn.quote(type.serialize(value))
end
+
+ def test_quoted_time_returns_date_qualified_time
+ value = ::Time.utc(2000, 1, 1, 12, 30, 0, 999999)
+ type = Type::Time.new
+
+ assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value))
+ end
end
end
end
diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb
new file mode 100644
index 0000000000..53a490cf21
--- /dev/null
+++ b/activerecord/test/cases/comment_test.rb
@@ -0,0 +1,91 @@
+require 'cases/helper'
+require 'support/schema_dumping_helper'
+
+class CommentTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+ self.use_transactional_tests = false if current_adapter?(:Mysql2Adapter)
+
+ class Commented < ActiveRecord::Base
+ self.table_name = 'commenteds'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+
+ @connection.transaction do
+ @connection.create_table('commenteds', comment: 'A table with comment', force: true) do |t|
+ t.string 'name', comment: 'Comment should help clarify the column purpose'
+ t.boolean 'obvious', comment: 'Question is: should you comment obviously named objects?'
+ t.string 'content'
+ t.index 'name', comment: %Q["Very important" index that powers all the performance.\nAnd it's fun!]
+ end
+ end
+ end
+
+ teardown do
+ @connection.drop_table 'commenteds', if_exists: true
+ end
+
+ if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
+
+ def test_column_created_in_block
+ Commented.reset_column_information
+ column = Commented.columns_hash['name']
+ assert_equal :string, column.type
+ assert_equal 'Comment should help clarify the column purpose', column.comment
+ end
+
+ def test_add_column_with_comment_later
+ @connection.add_column :commenteds, :rating, :integer, comment: 'I am running out of imagination'
+ Commented.reset_column_information
+ column = Commented.columns_hash['rating']
+
+ assert_equal :integer, column.type
+ assert_equal 'I am running out of imagination', column.comment
+ end
+
+ def test_add_index_with_comment_later
+ @connection.add_index :commenteds, :obvious, name: 'idx_obvious', comment: 'We need to see obvious comments'
+ index = @connection.indexes('commenteds').find { |idef| idef.name == 'idx_obvious' }
+ assert_equal 'We need to see obvious comments', index.comment
+ end
+
+ def test_add_comment_to_column
+ @connection.change_column :commenteds, :content, :string, comment: 'Whoa, content describes itself!'
+
+ Commented.reset_column_information
+ column = Commented.columns_hash['content']
+
+ assert_equal :string, column.type
+ assert_equal 'Whoa, content describes itself!', column.comment
+ end
+
+ def test_remove_comment_from_column
+ @connection.change_column :commenteds, :obvious, :string, comment: nil
+
+ Commented.reset_column_information
+ column = Commented.columns_hash['obvious']
+
+ assert_equal :string, column.type
+ assert_nil column.comment
+ end
+
+ def test_schema_dump_with_comments
+ # Do all the stuff from other tests
+ @connection.add_column :commenteds, :rating, :integer, comment: 'I am running out of imagination'
+ @connection.change_column :commenteds, :content, :string, comment: 'Whoa, content describes itself!'
+ @connection.change_column :commenteds, :obvious, :string, comment: nil
+ @connection.add_index :commenteds, :obvious, name: 'idx_obvious', comment: 'We need to see obvious comments'
+ # And check that these changes are reflected in dump
+ output = dump_table_schema 'commenteds'
+ assert_match %r[create_table "commenteds",.+\s+comment: "A table with comment"], output
+ assert_match %r[t\.string\s+"name",\s+comment: "Comment should help clarify the column purpose"], output
+ assert_match %r[t\.string\s+"obvious"\n], output
+ assert_match %r[t\.string\s+"content",\s+comment: "Whoa, content describes itself!"], output
+ assert_match %r[t\.integer\s+"rating",\s+comment: "I am running out of imagination"], output
+ assert_match %r[add_index\s+.+\s+comment: "\\\"Very important\\\" index that powers all the performance.\\nAnd it's fun!"], output
+ assert_match %r[add_index\s+.+\s+name: "idx_obvious",.+\s+comment: "We need to see obvious comments"], output
+ end
+
+ end
+end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index cd1967c373..a3f8d26100 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -37,8 +37,8 @@ class DirtyTest < ActiveRecord::TestCase
def test_attribute_changes
# New record - no changes.
pirate = Pirate.new
- assert !pirate.catchphrase_changed?
- assert_nil pirate.catchphrase_change
+ assert_equal false, pirate.catchphrase_changed?
+ assert_equal false, pirate.non_validated_parrot_id_changed?
# Change catchphrase.
pirate.catchphrase = 'arrr'
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 692c6bf2d0..f03df2d99e 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -652,11 +652,16 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(approved: true).find(1) }
end
- def test_find_on_hash_conditions_with_explicit_table_name
+ def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_string
assert Topic.where('topics.approved' => false).find(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => true).find(1) }
end
+ def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_symbol
+ assert Topic.where('topics.approved': false).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved': true).find(1) }
+ end
+
def test_find_on_hash_conditions_with_hashed_table_name
assert Topic.where(topics: { approved: false }).find(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(topics: { approved: true }).find(1) }
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index b9ce6bbc55..70c64f3e71 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -55,6 +55,11 @@ module ActiveRecord
assert index_exists?(table_name, :tag_id, name: 'index_taggings_on_tag_id')
end
+ def test_creates_named_unique_index
+ add_reference table_name, :tag, index: { name: 'index_taggings_on_tag_id', unique: true }
+ assert index_exists?(table_name, :tag_id, name: 'index_taggings_on_tag_id', unique: true )
+ end
+
def test_creates_reference_id_with_specified_type
add_reference table_name, :user, type: :string
assert column_exists?(table_name, :user_id, :string)
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 0aac5bad31..e6d731e1e1 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -8,6 +8,13 @@ module ActiveRecord
ActiveRecord::Tasks::MySQLDatabaseTasks.stubs(:new).returns @mysql_tasks
ActiveRecord::Tasks::PostgreSQLDatabaseTasks.stubs(:new).returns @postgresql_tasks
ActiveRecord::Tasks::SQLiteDatabaseTasks.stubs(:new).returns @sqlite_tasks
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
end
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 1632f04854..2541a97537 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -12,6 +12,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_establishes_connection_without_database
@@ -48,14 +55,20 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
- def test_create_when_database_exists_outputs_info_to_stderr
- $stderr.expects(:puts).with("my-app-db already exists").once
+ def test_when_database_created_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ assert_equal $stdout.string, "Created database 'my-app-db'\n"
+ end
+
+ def test_create_when_database_exists_outputs_info_to_stderr
ActiveRecord::Base.connection.stubs(:create_database).raises(
- ActiveRecord::StatementInvalid.new("Can't create database 'dev'; database exists:")
+ ActiveRecord::Tasks::DatabaseAlreadyExists
)
ActiveRecord::Tasks::DatabaseTasks.create @configuration
+
+ assert_equal $stderr.string, "Database 'my-app-db' already exists\n"
end
end
@@ -77,6 +90,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:establish_connection).
raises(@error).
then.returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_root_password_is_requested
@@ -160,6 +180,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_establishes_connection_to_mysql_database
@@ -173,6 +200,12 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.drop @configuration
end
+
+ def test_when_database_dropped_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+
+ assert_equal $stdout.string, "Dropped database 'my-app-db'\n"
+ end
end
class MySQLPurgeTest < ActiveRecord::TestCase
@@ -307,6 +340,5 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
end
-
end
end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index ba53f340ae..353aecdda6 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -12,6 +12,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_establishes_connection_to_postgresql_database
@@ -63,14 +70,20 @@ module ActiveRecord
assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration }
end
- def test_create_when_database_exists_outputs_info_to_stderr
- $stderr.expects(:puts).with("my-app-db already exists").once
+ def test_when_database_created_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ assert_equal $stdout.string, "Created database 'my-app-db'\n"
+ end
+
+ def test_create_when_database_exists_outputs_info_to_stderr
ActiveRecord::Base.connection.stubs(:create_database).raises(
- ActiveRecord::StatementInvalid.new('database "my-app-db" already exists')
+ ActiveRecord::Tasks::DatabaseAlreadyExists
)
ActiveRecord::Tasks::DatabaseTasks.create @configuration
+
+ assert_equal $stderr.string, "Database 'my-app-db' already exists\n"
end
end
@@ -84,6 +97,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_establishes_connection_to_postgresql_database
@@ -101,6 +121,12 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.drop @configuration
end
+
+ def test_when_database_dropped_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+
+ assert_equal $stdout.string, "Dropped database 'my-app-db'\n"
+ end
end
class PostgreSQLPurgeTest < ActiveRecord::TestCase
@@ -273,6 +299,5 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
end
-
end
end
diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb
index 0aea0c3b38..13bc2c60fb 100644
--- a/activerecord/test/cases/tasks/sqlite_rake_test.rb
+++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb
@@ -15,6 +15,13 @@ module ActiveRecord
File.stubs(:exist?).returns(false)
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_db_checks_database_exists
@@ -23,12 +30,18 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
end
+ def test_when_db_created_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+
+ assert_equal $stdout.string, "Created database '#{@database}'\n"
+ end
+
def test_db_create_when_file_exists
File.stubs(:exist?).returns(true)
- $stderr.expects(:puts).with("#{@database} already exists")
-
ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+
+ assert_equal $stderr.string, "Database '#{@database}' already exists\n"
end
def test_db_create_with_file_does_nothing
@@ -69,6 +82,13 @@ module ActiveRecord
Pathname.stubs(:new).returns(@path)
File.stubs(:join).returns('/former/relative/path')
FileUtils.stubs(:rm).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_creates_path_from_database
@@ -103,6 +123,12 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
end
+
+ def test_when_db_dropped_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+
+ assert_equal $stdout.string, "Dropped database '#{@database}'\n"
+ end
end
class SqliteDBCharsetTest < ActiveRecord::TestCase