aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md67
-rw-r--r--activerecord/Rakefile7
-rw-r--r--activerecord/lib/active_record/attribute/user_provided_default.rb32
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb9
-rw-r--r--activerecord/lib/active_record/attributes.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb55
-rw-r--r--activerecord/lib/active_record/fixtures.rb5
-rw-r--r--activerecord/lib/active_record/persistence.rb4
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb2
-rw-r--r--activerecord/lib/active_record/relation/merger.rb24
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb16
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb1
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb9
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb9
-rw-r--r--activerecord/test/cases/adapters/sqlite3/collation_test.rb53
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb8
-rw-r--r--activerecord/test/cases/attributes_test.rb16
-rw-r--r--activerecord/test/cases/dirty_test.rb26
-rw-r--r--activerecord/test/cases/enum_test.rb60
-rw-r--r--activerecord/test/cases/fixtures_test.rb9
-rw-r--r--activerecord/test/cases/persistence_test.rb27
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb2
-rw-r--r--activerecord/test/cases/relation_test.rb22
-rw-r--r--activerecord/test/cases/relations_test.rb28
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb8
-rw-r--r--activerecord/test/cases/test_case.rb2
-rw-r--r--activerecord/test/cases/view_test.rb2
-rw-r--r--activerecord/test/fixtures/books.yml11
30 files changed, 454 insertions, 101 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index e90490f8b6..f6a0777faf 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,60 @@
+* Ensure symbols passed to `ActiveRecord::Relation#select` are always treated
+ as columns.
+
+ Fixes #20360.
+
+ *Sean Griffin*
+
+* Do not set `sql_mode` if `strict: :default` is specified.
+
+ ```
+ # database.yml
+ production:
+ adapter: mysql2
+ database: foo_prod
+ user: foo
+ strict: :default
+ ```
+
+ *Ryuta Kamizono*
+
+* Allow proc defaults to be passed to the attributes API. See documentation
+ for examples.
+
+ *Sean Griffin*, *Kir Shatrov*
+
+* SQLite: `:collation` support for string and text columns.
+
+ Example:
+
+ create_table :foo do |t|
+ t.string :string_nocase, collation: 'NOCASE'
+ t.text :text_rtrim, collation: 'RTRIM'
+ end
+
+ add_column :foo, :title, :string, collation: 'RTRIM'
+
+ change_column :foo, :title, :string, collation: 'NOCASE'
+
+ *Akshay Vishnoi*
+
+* Allow the use of symbols or strings to specify enum values in test
+ fixtures:
+
+ awdr:
+ title: "Agile Web Development with Rails"
+ status: :proposed
+
+ *George Claghorn*
+
+* Clear query cache when `ActiveRecord::Base#reload` is called.
+
+ *Shane Hender, Pierre Nespo*
+
+* Include stored procedures and function on the MySQL structure dump.
+
+ *Jonathan Worek*
+
* Pass `:extend` option for `has_and_belongs_to_many` associations to the underlying `has_many :through`.
*Jaehyun Shin*
@@ -233,7 +290,7 @@
*Josef Šimánek*
-* Fixed ActiveRecord::Relation#becomes! and changed_attributes issues for type
+* Fixed `ActiveRecord::Relation#becomes!` and `changed_attributes` issues for type
columns.
Fixes #17139.
@@ -416,8 +473,8 @@
*Henrik Nygren*
-* Fixed ActiveRecord::Relation#group method when an argument is an SQL
- reserved key word:
+* Fixed `ActiveRecord::Relation#group` method when an argument is an SQL
+ reserved keyword:
Example:
@@ -426,7 +483,7 @@
*Bogdan Gusiev*
-* Added the `#or` method on ActiveRecord::Relation, allowing use of the OR
+* Added the `#or` method on `ActiveRecord::Relation`, allowing use of the OR
operator to combine WHERE or HAVING clauses.
Example:
@@ -626,7 +683,7 @@
The preferred method to halt a callback chain from now on is to explicitly
`throw(:abort)`.
- In the past, returning `false` in an ActiveRecord `before_` callback had the
+ In the past, returning `false` in an Active Record `before_` callback had the
side effect of halting the callback chain.
This is not recommended anymore and, depending on the value of the
`config.active_support.halt_callback_chains_on_return_false` option, will
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 407f92952d..a619204e6f 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -1,5 +1,4 @@
require 'rake/testtask'
-require 'rubygems/package_task'
require File.expand_path(File.dirname(__FILE__)) + "/test/config"
require File.expand_path(File.dirname(__FILE__)) + "/test/support/config"
@@ -151,9 +150,3 @@ task :lines do
files = FileList["lib/active_record/**/*.rb"]
CodeTools::LineStatistics.new(files).print_loc
end
-
-spec = eval(File.read('activerecord.gemspec'))
-
-Gem::PackageTask.new(spec) do |p|
- p.gem_spec = spec
-end
diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb
new file mode 100644
index 0000000000..e0bee8c17e
--- /dev/null
+++ b/activerecord/lib/active_record/attribute/user_provided_default.rb
@@ -0,0 +1,32 @@
+require 'active_record/attribute'
+
+module ActiveRecord
+ class Attribute # :nodoc:
+ class UserProvidedDefault < FromUser
+ def initialize(name, value, type, database_default)
+ super(name, value, type)
+ @database_default = database_default
+ end
+
+ def type_cast(value)
+ if value.is_a?(Proc)
+ super(value.call)
+ else
+ super
+ end
+ end
+
+ def changed_in_place_from?(old_value)
+ super || changed_from?(database_default.value)
+ end
+
+ def with_type(type)
+ self.class.new(name, value_before_type_cast, type, database_default)
+ end
+
+ protected
+
+ attr_reader :database_default
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index d0d8a968c5..fd7099e0de 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -11,6 +11,15 @@ module ActiveRecord
# serialized object must be of that class on assignment and retrieval.
# Otherwise <tt>SerializationTypeMismatch</tt> will be raised.
#
+ # Keep in mind that database adapters handle certain serialization tasks
+ # for you. For instance: +json+ and +jsonb+ types in PostgreSQL will be
+ # converted between JSON object/array syntax and Ruby +Hash+ or +Array+
+ # objects transparently. There is no need to use +serialize+ in this
+ # case.
+ #
+ # For more complex cases, such as conversion to or from your application
+ # domain objects, consider using the ActiveRecord::Attributes API.
+ #
# ==== Parameters
#
# * +attr_name+ - The field name that should be serialized.
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 50339b6f69..8b2c4c7170 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -1,3 +1,5 @@
+require 'active_record/attribute/user_provided_default'
+
module ActiveRecord
# See ActiveRecord::Attributes::ClassMethods for documentation
module Attributes
@@ -80,6 +82,14 @@ module ActiveRecord
#
# StoreListing.new.my_string # => "new default"
#
+ # class Product < ActiveRecord::Base
+ # attribute :my_default_proc, :datetime, default: -> { Time.now }
+ # end
+ #
+ # Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
+ # sleep 1
+ # Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
+ #
# Attributes do not need to be backed by a database column.
#
# class MyModel < ActiveRecord::Base
@@ -202,7 +212,8 @@ module ActiveRecord
#
# +default+ The default value to use when no value is provided. If this option
# is not passed, the previous default value (if any) will be used.
- # Otherwise, the default will be +nil+.
+ # Otherwise, the default will be +nil+. A proc can also be passed, and
+ # will be called once each time a new value is needed.
#
# +user_provided_default+ Whether the default value should be cast using
# +cast+ or +deserialize+.
@@ -236,7 +247,12 @@ module ActiveRecord
if value == NO_DEFAULT_PROVIDED
default_attribute = _default_attributes[name].with_type(type)
elsif from_user
- default_attribute = Attribute.from_user(name, value, type)
+ default_attribute = Attribute::UserProvidedDefault.new(
+ name,
+ value,
+ type,
+ _default_attributes[name],
+ )
else
default_attribute = Attribute.from_database(name, value, type)
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 c3206c6045..00e3d2965b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -959,10 +959,12 @@ module ActiveRecord
wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
+ defaults = [':default', :default].to_set
+
# Make MySQL reject illegal values rather than truncating or blanking them, see
# http://dev.mysql.com/doc/refman/5.6/en/sql-mode.html#sqlmode_strict_all_tables
# If the user has provided another value for sql_mode, don't replace it.
- unless variables.has_key?('sql_mode')
+ unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict])
variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
end
@@ -977,7 +979,7 @@ module ActiveRecord
# Gather up all of the SET variables...
variable_assignments = variables.map do |k, v|
- if v == ':default' || v == :default
+ if defaults.include?(v)
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
elsif !v.nil?
"@@SESSION.#{k} = #{quote(v)}"
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
new file mode 100644
index 0000000000..fe1dcbd710
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
@@ -0,0 +1,15 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module SQLite3
+ class SchemaCreation < AbstractAdapter::SchemaCreation
+ private
+ def add_column_options!(sql, options)
+ if options[:collation]
+ sql << " COLLATE \"#{options[:collation]}\""
+ end
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 7faca9cb70..87129c42cf 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -1,5 +1,6 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/connection_adapters/statement_pool'
+require 'active_record/connection_adapters/sqlite3/schema_creation'
gem 'sqlite3', '~> 1.3.6'
require 'sqlite3'
@@ -84,6 +85,10 @@ module ActiveRecord
end
end
+ def schema_creation # :nodoc:
+ SQLite3::SchemaCreation.new self
+ end
+
def initialize(connection, logger, connection_options, config)
super(connection, logger)
@@ -344,9 +349,10 @@ module ActiveRecord
field["dflt_value"] = $1.gsub('""', '"')
end
+ collation = field['collation']
sql_type = field['type']
type_metadata = fetch_type_metadata(sql_type)
- new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0)
+ new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, nil, collation)
end
end
@@ -441,6 +447,7 @@ module ActiveRecord
self.null = options[:null] if options.include?(:null)
self.precision = options[:precision] if options.include?(:precision)
self.scale = options[:scale] if options.include?(:scale)
+ self.collation = options[:collation] if options.include?(:collation)
end
end
end
@@ -454,9 +461,9 @@ module ActiveRecord
protected
def table_structure(table_name)
- structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
+ structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA')
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
- structure
+ table_structure_with_collation(table_name, structure)
end
def alter_table(table_name, options = {}) #:nodoc:
@@ -491,7 +498,7 @@ module ActiveRecord
@definition.column(column_name, column.type,
:limit => column.limit, :default => column.default,
:precision => column.precision, :scale => column.scale,
- :null => column.null)
+ :null => column.null, collation: column.collation)
end
yield @definition if block_given?
end
@@ -553,6 +560,46 @@ module ActiveRecord
super
end
end
+
+ private
+ COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
+
+ def table_structure_with_collation(table_name, basic_structure)
+ collation_hash = {}
+ sql = "SELECT sql FROM
+ (SELECT * FROM sqlite_master UNION ALL
+ SELECT * FROM sqlite_temp_master)
+ WHERE type='table' and name='#{ table_name }' \;"
+
+ # Result will have following sample string
+ # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ # "password_digest" varchar COLLATE "NOCASE");
+ result = exec_query(sql, 'SCHEMA').first
+
+ if result
+ # Splitting with left parantheses and picking up last will return all
+ # columns separated with comma(,).
+ columns_string = result["sql"].split('(').last
+
+ columns_string.split(',').each do |column_string|
+ # This regex will match the column name and collation type and will save
+ # the value in $1 and $2 respectively.
+ collation_hash[$1] = $2 if (COLLATE_REGEX =~ column_string)
+ end
+
+ basic_structure.map! do |column|
+ column_name = column['name']
+
+ if collation_hash.has_key? column_name
+ column['collation'] = collation_hash[column_name]
+ end
+
+ column
+ end
+ else
+ basic_structure.to_hash
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 2c1771dd6c..1ec8f818cd 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -644,6 +644,11 @@ module ActiveRecord
row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type)
end
+ # Resolve enums
+ model_class.defined_enums.each do |name, values|
+ row[name] = values.fetch(row[name], row[name])
+ end
+
# If STI is used, find the correct subclass for association reflection
reflection_class =
if row.include?(inheritance_column_name)
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 466175690e..437ba31711 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -382,7 +382,7 @@ module ActiveRecord
# # => #<Account id: 1, email: 'account@example.com'>
#
# Attributes are reloaded from the database, and caches busted, in
- # particular the associations cache.
+ # particular the associations cache and the QueryCache.
#
# If the record no longer exists in the database <tt>ActiveRecord::RecordNotFound</tt>
# is raised. Otherwise, in addition to the in-place modification the method
@@ -418,6 +418,8 @@ module ActiveRecord
# end
#
def reload(options = nil)
+ self.class.connection.clear_query_cache
+
fresh_object =
if options && options[:lock]
self.class.unscoped { self.class.lock(options[:lock]).find(id) }
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index d4a8823cfe..86f2c30168 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -39,7 +39,7 @@ module ActiveRecord
BLACKLISTED_ARRAY_METHODS = [
:compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
:shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
- :keep_if, :pop, :shift, :delete_at, :compact, :select!
+ :keep_if, :pop, :shift, :delete_at, :select!
].to_set # :nodoc:
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index c3054f1fe9..dd8f0aa298 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -50,7 +50,7 @@ module ActiveRecord
NORMAL_VALUES = Relation::VALUE_METHODS -
Relation::CLAUSE_METHODS -
- [:joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
+ [:includes, :preload, :joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
def normal_values
NORMAL_VALUES
@@ -75,6 +75,7 @@ module ActiveRecord
merge_multi_values
merge_single_values
merge_clauses
+ merge_preloads
merge_joins
relation
@@ -82,6 +83,27 @@ module ActiveRecord
private
+ def merge_preloads
+ return if other.preload_values.empty? && other.includes_values.empty?
+
+ if other.klass == relation.klass
+ relation.preload! other.preload_values unless other.preload_values.empty?
+ relation.includes! other.includes_values unless other.includes_values.empty?
+ else
+ reflection = relation.klass.reflect_on_all_associations.find do |r|
+ r.class_name == other.klass.name
+ end || return
+
+ unless other.preload_values.empty?
+ relation.preload! reflection.name => other.preload_values
+ end
+
+ unless other.includes_values.empty?
+ relation.includes! reflection.name => other.includes_values
+ end
+ end
+ end
+
def merge_joins
return if other.joins_values.blank?
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index fd78db2e95..f85dc35e89 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1001,15 +1001,13 @@ module ActiveRecord
end
def arel_columns(columns)
- if from_clause.value
- columns
- else
- columns.map do |field|
- if (Symbol === field || String === field) && columns_hash.key?(field.to_s)
- arel_table[field]
- else
- field
- end
+ columns.map do |field|
+ if (Symbol === field || String === field) && columns_hash.key?(field.to_s) && !from_clause.value
+ arel_table[field]
+ elsif Symbol === field
+ connection.quote_table_name(field.to_s)
+ else
+ field
end
end
end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index bc80275a88..673386f0d9 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -59,6 +59,7 @@ module ActiveRecord
args = prepare_command_options('mysqldump')
args.concat(["--result-file", "#{filename}"])
args.concat(["--no-data"])
+ args.concat(["--routines"])
args.concat(["#{configuration['database']}"])
unless Kernel.system(*args)
$stderr.puts "Could not dump the database structure. "\
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index 4762ef43b5..9903cd3c0b 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -145,6 +145,15 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
+ def test_mysql_strict_mode_specified_default
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.merge({strict: :default}))
+ global_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.sql_mode"
+ session_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal global_sql_mode.rows, session_sql_mode.rows
+ end
+ end
+
def test_mysql_set_session_variable
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}}))
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index a8b39b21d4..c4e0278c89 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -84,6 +84,15 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
+ def test_mysql_strict_mode_specified_default
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.merge({strict: :default}))
+ global_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.sql_mode"
+ session_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal global_sql_mode.rows, session_sql_mode.rows
+ end
+ end
+
def test_mysql_set_session_variable
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}}))
diff --git a/activerecord/test/cases/adapters/sqlite3/collation_test.rb b/activerecord/test/cases/adapters/sqlite3/collation_test.rb
new file mode 100644
index 0000000000..39e4620bc9
--- /dev/null
+++ b/activerecord/test/cases/adapters/sqlite3/collation_test.rb
@@ -0,0 +1,53 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class SQLite3CollationTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :collation_table_sqlite3, force: true do |t|
+ t.string :string_nocase, collation: 'NOCASE'
+ t.text :text_rtrim, collation: 'RTRIM'
+ end
+ end
+
+ def teardown
+ @connection.drop_table :collation_table_sqlite3, if_exists: true
+ end
+
+ test "string column with collation" do
+ column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'string_nocase' }
+ assert_equal :string, column.type
+ assert_equal 'NOCASE', column.collation
+ end
+
+ test "text column with collation" do
+ column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'text_rtrim' }
+ assert_equal :text, column.type
+ assert_equal 'RTRIM', column.collation
+ end
+
+ test "add column with collation" do
+ @connection.add_column :collation_table_sqlite3, :title, :string, collation: 'RTRIM'
+
+ column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'title' }
+ assert_equal :string, column.type
+ assert_equal 'RTRIM', column.collation
+ end
+
+ test "change column with collation" do
+ @connection.add_column :collation_table_sqlite3, :description, :string
+ @connection.change_column :collation_table_sqlite3, :description, :text, collation: 'RTRIM'
+
+ column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'description' }
+ assert_equal :text, column.type
+ assert_equal 'RTRIM', column.collation
+ end
+
+ test "schema dump includes collation" do
+ output = dump_table_schema("collation_table_sqlite3")
+ assert_match %r{t.string\s+"string_nocase",\s+collation: "NOCASE"$}, output
+ assert_match %r{t.text\s+"text_rtrim",\s+collation: "RTRIM"$}, output
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 27f4ba8eb6..6be9795603 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -421,14 +421,14 @@ module ActiveRecord
end
def test_statement_closed
- db = SQLite3::Database.new(ActiveRecord::Base.
+ db = ::SQLite3::Database.new(ActiveRecord::Base.
configurations['arunit']['database'])
- statement = SQLite3::Statement.new(db,
+ statement = ::SQLite3::Statement.new(db,
'CREATE TABLE statement_test (number integer not null)')
- statement.stubs(:step).raises(SQLite3::BusyException, 'busy')
+ statement.stubs(:step).raises(::SQLite3::BusyException, 'busy')
statement.stubs(:columns).once.returns([])
statement.expects(:close).once
- SQLite3::Statement.stubs(:new).returns(statement)
+ ::SQLite3::Statement.stubs(:new).returns(statement)
assert_raises ActiveRecord::StatementInvalid do
@conn.exec_query 'select * from statement_test'
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 927d7950a5..eeda9335ad 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -125,6 +125,22 @@ module ActiveRecord
assert_equal "from user", model.wibble
end
+ test "procs for default values" do
+ klass = Class.new(OverloadedType) do
+ @@counter = 0
+ attribute :counter, :integer, default: -> { @@counter += 1 }
+ end
+
+ assert_equal 1, klass.new.counter
+ assert_equal 2, klass.new.counter
+ end
+
+ test "user provided defaults are persisted even if unchanged" do
+ model = OverloadedType.create!
+
+ assert_equal "the overloaded default", model.reload.string_with_default
+ end
+
if current_adapter?(:PostgreSQLAdapter)
test "arrays types can be specified" do
klass = Class.new(OverloadedType) do
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 3a7cc572e6..216f228142 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -623,32 +623,6 @@ class DirtyTest < ActiveRecord::TestCase
end
end
- test "defaults with type that implements `serialize`" do
- type = Class.new(ActiveRecord::Type::Value) do
- def cast(value)
- value.to_i
- end
-
- def serialize(value)
- value.to_s
- end
- end
-
- model_class = Class.new(ActiveRecord::Base) do
- self.table_name = 'numeric_data'
- attribute :foo, type.new, default: 1
- end
-
- model = model_class.new
- assert_not model.foo_changed?
-
- model = model_class.new(foo: 1)
- assert_not model.foo_changed?
-
- model = model_class.new(foo: '1')
- assert_not model.foo_changed?
- end
-
test "in place mutation detection" do
pirate = Pirate.create!(catchphrase: "arrrr")
pirate.catchphrase << " matey!"
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index eea184e530..3641826daa 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -9,49 +9,49 @@ class EnumTest < ActiveRecord::TestCase
end
test "query state by predicate" do
- assert @book.proposed?
+ assert @book.published?
assert_not @book.written?
- assert_not @book.published?
+ assert_not @book.proposed?
- assert @book.unread?
+ assert @book.read?
end
test "query state with strings" do
- assert_equal "proposed", @book.status
- assert_equal "unread", @book.read_status
+ assert_equal "published", @book.status
+ assert_equal "read", @book.read_status
end
test "find via scope" do
- assert_equal @book, Book.proposed.first
- assert_equal @book, Book.unread.first
+ assert_equal @book, Book.published.first
+ assert_equal @book, Book.read.first
end
test "find via where with values" do
- proposed, written = Book.statuses[:proposed], Book.statuses[:written]
+ published, written = Book.statuses[:published], Book.statuses[:written]
- assert_equal @book, Book.where(status: proposed).first
+ assert_equal @book, Book.where(status: published).first
refute_equal @book, Book.where(status: written).first
- assert_equal @book, Book.where(status: [proposed]).first
+ assert_equal @book, Book.where(status: [published]).first
refute_equal @book, Book.where(status: [written]).first
- refute_equal @book, Book.where("status <> ?", proposed).first
+ refute_equal @book, Book.where("status <> ?", published).first
assert_equal @book, Book.where("status <> ?", written).first
end
test "find via where with symbols" do
- assert_equal @book, Book.where(status: :proposed).first
+ assert_equal @book, Book.where(status: :published).first
refute_equal @book, Book.where(status: :written).first
- assert_equal @book, Book.where(status: [:proposed]).first
+ assert_equal @book, Book.where(status: [:published]).first
refute_equal @book, Book.where(status: [:written]).first
- refute_equal @book, Book.where.not(status: :proposed).first
+ refute_equal @book, Book.where.not(status: :published).first
assert_equal @book, Book.where.not(status: :written).first
end
test "find via where with strings" do
- assert_equal @book, Book.where(status: "proposed").first
+ assert_equal @book, Book.where(status: "published").first
refute_equal @book, Book.where(status: "written").first
- assert_equal @book, Book.where(status: ["proposed"]).first
+ assert_equal @book, Book.where(status: ["published"]).first
refute_equal @book, Book.where(status: ["written"]).first
- refute_equal @book, Book.where.not(status: "proposed").first
+ refute_equal @book, Book.where.not(status: "published").first
assert_equal @book, Book.where.not(status: "written").first
end
@@ -96,14 +96,14 @@ class EnumTest < ActiveRecord::TestCase
test "enum changed attributes" do
old_status = @book.status
- @book.status = :published
+ @book.status = :proposed
assert_equal old_status, @book.changed_attributes[:status]
end
test "enum changes" do
old_status = @book.status
- @book.status = :published
- assert_equal [old_status, 'published'], @book.changes[:status]
+ @book.status = :proposed
+ assert_equal [old_status, 'proposed'], @book.changes[:status]
end
test "enum attribute was" do
@@ -113,25 +113,25 @@ class EnumTest < ActiveRecord::TestCase
end
test "enum attribute changed" do
- @book.status = :published
+ @book.status = :proposed
assert @book.attribute_changed?(:status)
end
test "enum attribute changed to" do
- @book.status = :published
- assert @book.attribute_changed?(:status, to: 'published')
+ @book.status = :proposed
+ assert @book.attribute_changed?(:status, to: 'proposed')
end
test "enum attribute changed from" do
old_status = @book.status
- @book.status = :published
+ @book.status = :proposed
assert @book.attribute_changed?(:status, from: old_status)
end
test "enum attribute changed from old status to new status" do
old_status = @book.status
- @book.status = :published
- assert @book.attribute_changed?(:status, from: old_status, to: 'published')
+ @book.status = :proposed
+ assert @book.attribute_changed?(:status, from: old_status, to: 'proposed')
end
test "enum didn't change" do
@@ -141,7 +141,7 @@ class EnumTest < ActiveRecord::TestCase
end
test "persist changes that are dirty" do
- @book.status = :published
+ @book.status = :proposed
assert @book.attribute_changed?(:status)
@book.status = :written
assert @book.attribute_changed?(:status)
@@ -149,7 +149,7 @@ class EnumTest < ActiveRecord::TestCase
test "reverted changes that are not dirty" do
old_status = @book.status
- @book.status = :published
+ @book.status = :proposed
assert @book.attribute_changed?(:status)
@book.status = old_status
assert_not @book.attribute_changed?(:status)
@@ -210,9 +210,9 @@ class EnumTest < ActiveRecord::TestCase
test "_before_type_cast returns the enum label (required for form fields)" do
if @book.status_came_from_user?
- assert_equal "proposed", @book.status_before_type_cast
+ assert_equal "published", @book.status_before_type_cast
else
- assert_equal "proposed", @book.status
+ assert_equal "published", @book.status
end
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 97ba178b4d..47532c84e8 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -691,7 +691,7 @@ end
class FoxyFixturesTest < ActiveRecord::TestCase
fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers,
- :developers, :"admin/accounts", :"admin/users", :live_parrots, :dead_parrots
+ :developers, :"admin/accounts", :"admin/users", :live_parrots, :dead_parrots, :books
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
require 'models/uuid_parent'
@@ -841,6 +841,13 @@ class FoxyFixturesTest < ActiveRecord::TestCase
assert admin_accounts(:signals37).users.include?(admin_users(:david))
assert_equal 2, admin_accounts(:signals37).users.size
end
+
+ def test_resolves_enums
+ assert books(:awdr).published?
+ assert books(:awdr).read?
+ assert books(:rfr).proposed?
+ assert books(:ddd).published?
+ end
end
class ActiveSupportSubclassWithFixturesTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 1e93e2a05c..42e7507631 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -897,6 +897,33 @@ class PersistenceTest < ActiveRecord::TestCase
assert_not post.new_record?
end
+ def test_reload_via_querycache
+ ActiveRecord::Base.connection.enable_query_cache!
+ ActiveRecord::Base.connection.clear_query_cache
+ assert ActiveRecord::Base.connection.query_cache_enabled, 'cache should be on'
+ parrot = Parrot.create(:name => 'Shane')
+
+ # populate the cache with the SELECT result
+ found_parrot = Parrot.find(parrot.id)
+ assert_equal parrot.id, found_parrot.id
+
+ # Manually update the 'name' attribute in the DB directly
+ assert_equal 1, ActiveRecord::Base.connection.query_cache.length
+ ActiveRecord::Base.uncached do
+ found_parrot.name = 'Mary'
+ found_parrot.save
+ end
+
+ # Now reload, and verify that it gets the DB version, and not the querycache version
+ found_parrot.reload
+ assert_equal 'Mary', found_parrot.name
+
+ found_parrot = Parrot.find(parrot.id)
+ assert_equal 'Mary', found_parrot.name
+ ensure
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
+
class SaveTest < ActiveRecord::TestCase
self.use_transactional_tests = false
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index 29c9d0e2af..989f4e1e5d 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -28,7 +28,7 @@ module ActiveRecord
module DelegationWhitelistBlacklistTests
ARRAY_DELEGATES = [
:+, :-, :|, :&, :[],
- :all?, :collect, :detect, :each, :each_cons, :each_with_index,
+ :all?, :collect, :compact, :detect, :each, :each_cons, :each_with_index,
:exclude?, :find_all, :flat_map, :group_by, :include?, :length,
:map, :none?, :one?, :partition, :reject, :reverse,
:sample, :second, :sort, :sort_by, :third,
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 9353be1ba7..24ed9e6638 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -242,6 +242,13 @@ module ActiveRecord
assert_equal false, post.respond_to?(:title), "post should not respond_to?(:body) since invoking it raises exception"
end
+ def test_select_quotes_when_using_from_clause
+ ensure_sqlite3_version_doesnt_include_bug
+ quoted_join = ActiveRecord::Base.connection.quote_table_name("join")
+ selected = Post.select(:join).from(Post.select("id as #{quoted_join}")).map(&:join)
+ assert_equal Post.pluck(:id), selected
+ end
+
def test_relation_merging_with_merged_joins_as_strings
join_string = "LEFT OUTER JOIN #{Rating.quoted_table_name} ON #{SpecialComment.quoted_table_name}.id = #{Rating.quoted_table_name}.comment_id"
special_comments_with_ratings = SpecialComment.joins join_string
@@ -276,5 +283,20 @@ module ActiveRecord
assert_equal "type cast from database", UpdateAllTestModel.first.body
end
+
+ private
+
+ def ensure_sqlite3_version_doesnt_include_bug
+ if current_adapter?(:SQLite3Adapter)
+ selected_quoted_column_names = ActiveRecord::Base.connection.exec_query(
+ 'SELECT "join" FROM (SELECT id AS "join" FROM posts) subquery'
+ ).columns
+ assert_equal ["join"], selected_quoted_column_names, <<-ERROR.squish
+ You are using an outdated version of SQLite3 which has a bug in
+ quoted column names. Please update SQLite3 and rebuild the sqlite3
+ ruby gem
+ ERROR
+ end
+ end
end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index a4ca3ab637..acbf85d398 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -17,7 +17,7 @@ require 'models/tyre'
require 'models/minivan'
require 'models/aircraft'
require "models/possession"
-
+require "models/reader"
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
@@ -621,6 +621,32 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 1, query.to_a.size
end
+ def test_preloading_with_associations_and_merges
+ post = Post.create! title: 'Uhuu', body: 'body'
+ reader = Reader.create! post_id: post.id, person_id: 1
+ comment = Comment.create! post_id: post.id, body: 'body'
+
+ assert !comment.respond_to?(:readers)
+
+ post_rel = Post.preload(:readers).joins(:readers).where(title: 'Uhuu')
+ result_comment = Comment.joins(:post).merge(post_rel).to_a.first
+ assert_equal comment, result_comment
+
+ assert_no_queries do
+ assert_equal post, result_comment.post
+ assert_equal [reader], result_comment.post.readers.to_a
+ end
+
+ post_rel = Post.includes(:readers).where(title: 'Uhuu')
+ result_comment = Comment.joins(:post).merge(post_rel).first
+ assert_equal comment, result_comment
+
+ assert_no_queries do
+ assert_equal post, result_comment.post
+ assert_equal [reader], result_comment.post.readers.to_a
+ end
+ end
+
def test_loading_with_one_association
posts = Post.preload(:comments)
post = posts.find { |p| p.id == 1 }
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 8d69741a4a..d0deb4c273 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -265,14 +265,14 @@ module ActiveRecord
def test_structure_dump
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db").returns(true)
+ Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "test-db").returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
end
def test_warn_when_external_structure_dump_fails
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db").returns(false)
+ Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "test-db").returns(false)
warnings = capture(:stderr) do
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
@@ -283,7 +283,7 @@ module ActiveRecord
def test_structure_dump_with_port_number
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--port=10000", "--result-file", filename, "--no-data", "test-db").returns(true)
+ Kernel.expects(:system).with("mysqldump", "--port=10000", "--result-file", filename, "--no-data", "--routines", "test-db").returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(
@configuration.merge('port' => 10000),
@@ -292,7 +292,7 @@ module ActiveRecord
def test_structure_dump_with_ssl
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--ssl-ca=ca.crt", "--result-file", filename, "--no-data", "test-db").returns(true)
+ Kernel.expects(:system).with("mysqldump", "--ssl-ca=ca.crt", "--result-file", filename, "--no-data", "--routines", "test-db").returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(
@configuration.merge("sslca" => "ca.crt"),
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index e0b01ae8e0..a5e6738f14 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -81,7 +81,7 @@ module ActiveRecord
oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im]
mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /]
postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i]
- sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im]
+ sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im, /^\s*SELECT sql\b.*\bFROM sqlite_master/im]
[oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql|
ignored_sql.concat db_ignored_sql
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index 3aed90ba36..f9dca1e196 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -102,7 +102,7 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
end
def test_attributes
- assert_equal({"name" => "Agile Web Development with Rails", "status" => 0},
+ assert_equal({"name" => "Agile Web Development with Rails", "status" => 2},
Paperback.first.attributes)
end
diff --git a/activerecord/test/fixtures/books.yml b/activerecord/test/fixtures/books.yml
index abe56752c6..380dd3dfca 100644
--- a/activerecord/test/fixtures/books.yml
+++ b/activerecord/test/fixtures/books.yml
@@ -3,9 +3,20 @@ awdr:
id: 1
name: "Agile Web Development with Rails"
format: "paperback"
+ status: :published
+ read_status: :read
rfr:
author_id: 1
id: 2
name: "Ruby for Rails"
format: "ebook"
+ status: "proposed"
+ read_status: "reading"
+
+ddd:
+ author_id: 1
+ id: 3
+ name: "Domain-Driven Design"
+ format: "hardcover"
+ status: 2