aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md120
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb18
-rw-r--r--activerecord/lib/active_record/connection_handling.rb60
-rw-r--r--activerecord/lib/active_record/core.rb9
-rw-r--r--activerecord/lib/active_record/enum.rb23
-rw-r--r--activerecord/lib/active_record/fixtures.rb4
-rw-r--r--activerecord/lib/active_record/inheritance.rb24
-rw-r--r--activerecord/lib/active_record/persistence.rb6
-rw-r--r--activerecord/lib/active_record/railtie.rb16
-rw-r--r--activerecord/lib/active_record/railties/databases.rake2
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb1
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb10
-rw-r--r--activerecord/lib/active_record/transactions.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/xml_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb38
-rw-r--r--activerecord/test/cases/associations/eager_singularization_test.rb195
-rw-r--r--activerecord/test/cases/autosave_association_test.rb7
-rw-r--r--activerecord/test/cases/base_test.rb7
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb127
-rw-r--r--activerecord/test/cases/enum_test.rb10
-rw-r--r--activerecord/test/cases/finder_test.rb7
-rw-r--r--activerecord/test/cases/hot_compatibility_test.rb2
-rw-r--r--activerecord/test/cases/inheritance_test.rb20
-rw-r--r--activerecord/test/cases/integration_test.rb18
-rw-r--r--activerecord/test/cases/migration_test.rb58
-rw-r--r--activerecord/test/cases/query_cache_test.rb7
-rw-r--r--activerecord/test/cases/relations_test.rb16
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb2
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb157
-rw-r--r--activerecord/test/models/car.rb1
-rw-r--r--activerecord/test/models/owner.rb17
-rw-r--r--activerecord/test/models/shop.rb5
-rw-r--r--activerecord/test/schema/schema.rb5
37 files changed, 722 insertions, 313 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index bd7dcb6167..d289f616b8 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,111 @@
+* Make `touch` fire the `after_commit` and `after_rollback` callbacks.
+
+ *Harry Brundage*
+
+* Enable partial indexes for sqlite >= 3.8.0
+
+ See http://www.sqlite.org/partialindex.html
+
+ *Cody Cutrer*
+
+* Don't try to get the subclass if the inheritance column doesn't exist
+
+ The `subclass_from_attrs` method is called even if the column specified by
+ the `inheritance_column` setting doesn't exist. This prevents setting associations
+ via the attributes hash if the association name clashes with the value of the setting,
+ typically `:type`. This worked previously in Rails 3.2.
+
+ *Ujjwal Thaakar*
+
+* Enum mappings are now exposed via class methods instead of constants.
+
+ Example:
+
+ class Conversation < ActiveRecord::Base
+ enum status: [ :active, :archived ]
+ end
+
+ Before:
+
+ Conversation::STATUS # => { "active" => 0, "archived" => 1 }
+
+ After:
+
+ Conversation.statuses # => { "active" => 0, "archived" => 1 }
+
+ *Godfrey Chan*
+
+* Set `NameError#name` when STI-class-lookup fails.
+
+ *Chulki Lee*
+
+* Fix bug in `becomes!` when changing from the base model to a STI sub-class.
+
+ Fixes #13272.
+
+ *the-web-dev*, *Yves Senn*
+
+* Currently Active Record can be configured via the environment variable
+ `DATABASE_URL` or by manually injecting a hash of values which is what Rails does,
+ reading in `database.yml` and setting Active Record appropriately. Active Record
+ expects to be able to use `DATABASE_URL` without the use of Rails, and we cannot
+ rip out this functionality without deprecating. This presents a problem though
+ when both config is set, and a `DATABASE_URL` is present. Currently the
+ `DATABASE_URL` should "win" and none of the values in `database.yml` are
+ used. This is somewhat unexpected, if one were to set values such as
+ `pool` in the `production:` group of `database.yml` they are ignored.
+
+ There are many ways that Active Record initiates a connection today:
+
+ - Stand Alone (without rails)
+ - `rake db:<tasks>`
+ - `ActiveRecord.establish_connection`
+
+ - With Rails
+ - `rake db:<tasks>`
+ - `rails <server> | <console>`
+ - `rails dbconsole`
+
+ Now all of these behave exactly the same way. The best way to do
+ this is to put all of this logic in one place so it is guaranteed to be used.
+
+ Here is the matrix of how this behavior works:
+
+ ```
+ No database.yml
+ No DATABASE_URL
+ => Error
+ ```
+
+ ```
+ database.yml present
+ No DATABASE_URL
+ => Use database.yml configuration
+ ```
+
+ ```
+ No database.yml
+ DATABASE_URL present
+ => use DATABASE_URL configuration
+ ```
+
+ ```
+ database.yml present
+ DATABASE_URL present
+ => Merged into `url` sub key. If both specify `url` sub key, the `database.yml` `url`
+ sub key "wins". If other paramaters `adapter` or `database` are specified in YAML,
+ they are discarded as the `url` sub key "wins".
+ ```
+
+ Current implementation uses `ActiveRecord::Base.configurations` to resolve and merge
+ all connection information before returning. This is achieved through a utility
+ class: `ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig`.
+
+ To understand the exact behavior of this class, it is best to review the
+ behavior in `activerecord/test/cases/connection_adapters/connection_handler_test.rb`.
+
+ *Richard Schneeman*
+
* Make `change_column_null` revertable. Fixes #13576.
*Yves Senn*, *Nishant Modak*, *Prathamesh Sonpatki*
@@ -340,7 +448,7 @@
*kostya*, *Lauro Caetano*
* `type_to_sql` returns a `String` for unmapped columns. This fixes an error
- when using unmapped array types in PG
+ when using unmapped PostgreSQL array types.
Example:
@@ -379,7 +487,7 @@
* Update counter cache on a `has_many` relationship regardless of default scope.
- Fix #12952.
+ Fixes #12952.
*Uku Taht*
@@ -390,9 +498,10 @@
*Cody Cutrer*, *Yves Senn*
-* Raise `ActiveRecord::RecordNotDestroyed` when a replaced child marked with `dependent: destroy` fails to be destroyed.
+* Raise `ActiveRecord::RecordNotDestroyed` when a replaced child
+ marked with `dependent: destroy` fails to be destroyed.
- Fix #12812
+ Fixex #12812.
*Brian Thomas Storti*
@@ -1298,6 +1407,7 @@
*Yves Senn*
* Fix the `:primary_key` option for `has_many` associations.
+
Fixes #10693.
*Yves Senn*
@@ -1422,7 +1532,7 @@
* Trigger a save on `has_one association=(associate)` when the associate contents have changed.
- Fix #8856.
+ Fixes #8856.
*Chris Thompson*
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 84e18684d8..0cd2e1a816 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -52,18 +52,16 @@ module ActiveRecord
end
end
- if reflection.type
- scope_chain_items <<
- ActiveRecord::Relation.create(klass, table)
- .where(reflection.type => foreign_klass.base_class.name)
- end
-
scope_chain_items.concat [klass.send(:build_default_scope)].compact
rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
left.merge right
end
+ if reflection.type
+ constraint = constraint.and table[reflection.type].eq foreign_klass.base_class.name
+ end
+
if rel && !rel.arel.constraints.empty?
constraint = constraint.and rel.arel.constraints
end
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 9f210c5f33..3f8b14bf67 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -123,13 +123,22 @@ module ActiveRecord
def resolve(config)
if config
resolve_connection config
- elsif defined?(Rails.env)
- resolve_env_connection Rails.env.to_sym
+ elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call
+ resolve_env_connection env.to_sym
else
raise AdapterNotSpecified
end
end
+ # Expands each key in @configurations hash into fully resolved hash
+ def resolve_all
+ config = configurations.dup
+ config.each do |key, value|
+ config[key] = resolve(value) if value
+ end
+ config
+ end
+
# Returns an instance of ConnectionSpecification for a given adapter.
# Accepts a hash one layer deep that contains all connection information.
#
@@ -219,7 +228,7 @@ module ActiveRecord
elsif spec.is_a?(String)
resolve_string_connection(spec)
else
- raise(AdapterNotSpecified, "#{spec} database is not configured")
+ raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available configuration: #{configurations.inspect}")
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 92bb70ba53..170dddb08e 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -155,6 +155,10 @@ module ActiveRecord
true
end
+ def supports_partial_index?
+ sqlite_version >= '3.8.0'
+ end
+
# Returns true, since this connection adapter supports prepared statement
# caching.
def supports_statement_cache?
@@ -397,13 +401,25 @@ module ActiveRecord
# Returns an array of indexes for the given table.
def indexes(table_name, name = nil) #:nodoc:
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row|
+ sql = <<-SQL
+ SELECT sql
+ FROM sqlite_master
+ WHERE name=#{quote(row['name'])} AND type='index'
+ UNION ALL
+ SELECT sql
+ FROM sqlite_temp_master
+ WHERE name=#{quote(row['name'])} AND type='index'
+ SQL
+ index_sql = exec_query(sql).first['sql']
+ match = /\sWHERE\s+(.+)$/i.match(index_sql)
+ where = match[1] if match
IndexDefinition.new(
table_name,
row['name'],
row['unique'] != 0,
exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
col['name']
- })
+ }, nil, nil, where)
end
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index c4afadbd9b..11f6a47158 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -1,5 +1,8 @@
module ActiveRecord
module ConnectionHandling
+ RAILS_ENV = -> { Rails.env if defined?(Rails) }
+ DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
+
# Establishes the connection to the database. Accepts a hash as input where
# the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
# example for regular databases (MySQL, Postgresql, etc):
@@ -41,9 +44,10 @@ module ActiveRecord
#
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
# may be returned on an error.
- def establish_connection(spec = ENV["DATABASE_URL"])
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
- spec = resolver.spec(spec)
+ def establish_connection(spec = nil)
+ spec ||= DEFAULT_ENV.call.to_sym
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
+ spec = resolver.spec(spec)
unless respond_to?(spec.adapter_method)
raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
@@ -53,6 +57,56 @@ module ActiveRecord
connection_handler.establish_connection self, spec
end
+ class MergeAndResolveDefaultUrlConfig # :nodoc:
+ def initialize(raw_configurations, url = ENV['DATABASE_URL'])
+ @raw_config = raw_configurations.dup
+ @url = url
+ end
+
+ # Returns fully resolved connection hashes.
+ # Merges connection information from `ENV['DATABASE_URL']` if available.
+ def resolve
+ ConnectionAdapters::ConnectionSpecification::Resolver.new(config).resolve_all
+ end
+
+ private
+ def config
+ if @url
+ raw_merged_into_default
+ else
+ @raw_config
+ end
+ end
+
+ def raw_merged_into_default
+ default = default_url_hash
+
+ @raw_config.each do |env, values|
+ default[env] = values || {}
+ default[env].merge!("url" => @url) { |h, v1, v2| v1 || v2 } if default[env].is_a?(Hash)
+ end
+ default
+ end
+
+ # When the raw configuration is not present and ENV['DATABASE_URL']
+ # is available we return a hash with the connection information in
+ # the connection URL. This hash responds to any string key with
+ # resolved connection information.
+ def default_url_hash
+ if @raw_config.blank?
+ Hash.new do |hash, key|
+ hash[key] = if key.is_a? String
+ ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(@url).to_hash
+ else
+ nil
+ end
+ end
+ else
+ {}
+ end
+ end
+ end
+
# Returns the connection currently associated with the class. This can
# also be used to "borrow" the connection to do database work unrelated
# to any of the specific Active Records.
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 18ee77f6fe..cd8690d500 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -42,9 +42,16 @@ module ActiveRecord
# 'database' => 'db/production.sqlite3'
# }
# }
- mattr_accessor :configurations, instance_writer: false
+ def self.configurations=(config)
+ @@configurations = ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
+ end
self.configurations = {}
+ # Returns fully resolved configurations hash
+ def self.configurations
+ @@configurations
+ end
+
##
# :singleton-method:
# Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 9ad718a02a..3deb2d65f8 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -54,23 +54,27 @@ module ActiveRecord
# remove unused values, the explicit +Hash+ syntax should be used.
#
# In rare circumstances you might need to access the mapping directly.
- # The mappings are exposed through a constant with the attributes name:
+ # The mappings are exposed through a class method with the pluralized attribute
+ # name:
#
- # Conversation::STATUS # => { "active" => 0, "archived" => 1 }
+ # Conversation.statuses # => { "active" => 0, "archived" => 1 }
#
- # Use that constant when you need to know the ordinal value of an enum:
+ # Use that class method when you need to know the ordinal value of an enum:
#
- # Conversation.where("status <> ?", Conversation::STATUS[:archived])
+ # Conversation.where("status <> ?", Conversation.statuses[:archived])
module Enum
def enum(definitions)
klass = self
definitions.each do |name, values|
- # STATUS = { }
- enum_values = _enum_methods_module.const_set name.to_s.upcase, ActiveSupport::HashWithIndifferentAccess.new
+ # statuses = { }
+ enum_values = ActiveSupport::HashWithIndifferentAccess.new
name = name.to_sym
+ # def self.statuses statuses end
+ klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values }
+
_enum_methods_module.module_eval do
- # def status=(value) self[:status] = STATUS[value] end
+ # def status=(value) self[:status] = statuses[value] end
define_method("#{name}=") { |value|
if enum_values.has_key?(value) || value.blank?
self[name] = enum_values[value]
@@ -84,9 +88,12 @@ module ActiveRecord
end
}
- # def status() STATUS.key self[:status] end
+ # def status() statuses.key self[:status] end
define_method(name) { enum_values.key self[name] }
+ # def status_before_type_cast() statuses.key self[:status] end
+ define_method("#{name}_before_type_cast") { enum_values.key self[name] }
+
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
pairs.each do |value, i|
enum_values[value] = i
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index fee3f51d9e..297792aeec 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -462,7 +462,7 @@ module ActiveRecord
@class_names.delete_if { |k,klass|
unless klass.is_a? Class
klass = klass.safe_constantize
- ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name will be removed in Rails 4.2, consider using the class itself instead.")
+ ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name to `set_fixture_class` will be removed in Rails 4.2. Use the class itself instead.")
end
!insert_class(@class_names, k, klass)
}
@@ -568,7 +568,7 @@ module ActiveRecord
@model_class = nil
if class_name.is_a?(String)
- ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name will be removed in Rails 4.2, consider using the class itself instead.")
+ ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name to `FixtureSet.new` will be removed in Rails 4.2. Use the class itself instead.")
end
if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 949e7678a5..da73112e90 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -18,13 +18,17 @@ module ActiveRecord
if abstract_class? || self == Base
raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
end
- if (attrs = args.first).is_a?(Hash)
- if subclass = subclass_from_attrs(attrs)
- return subclass.new(*args, &block)
- end
+
+ attrs = args.first
+ if subclass_from_attributes?(attrs)
+ subclass = subclass_from_attributes(attrs)
+ end
+
+ if subclass
+ subclass.new(*args, &block)
+ else
+ super
end
- # Delegate to the original .new
- super
end
# Returns +true+ if this does not need STI type condition. Returns
@@ -126,7 +130,7 @@ module ActiveRecord
end
end
- raise NameError, "uninitialized constant #{candidates.first}"
+ raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
end
end
@@ -172,7 +176,11 @@ module ActiveRecord
# is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
# If this is a StrongParameters hash, and access to inheritance_column is not permitted,
# this will ignore the inheritance column and return nil
- def subclass_from_attrs(attrs)
+ def subclass_from_attributes?(attrs)
+ columns_hash.include?(inheritance_column) && attrs.is_a?(Hash)
+ end
+
+ def subclass_from_attributes(attrs)
subclass_name = attrs.with_indifferent_access[inheritance_column]
if subclass_name.present? && subclass_name != self.name
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 4669c072a1..460fbdb3f8 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -196,7 +196,11 @@ module ActiveRecord
# share the same set of attributes.
def becomes!(klass)
became = becomes(klass)
- became.public_send("#{klass.inheritance_column}=", klass.sti_name) unless self.class.descends_from_active_record?
+ sti_type = nil
+ if !klass.descends_from_active_record?
+ sti_type = klass.sti_name
+ end
+ became.public_send("#{klass.inheritance_column}=", sti_type)
became
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index ec85b3c843..11b564f8f9 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -40,19 +40,7 @@ module ActiveRecord
namespace :db do
task :load_config do
- configuration = if ENV["DATABASE_URL"]
- { Rails.env => ENV["DATABASE_URL"] }
- else
- Rails.application.config.database_configuration || {}
- end
-
- resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(configuration)
-
- configuration.each do |key, value|
- configuration[key] = resolver.resolve(value) if value
- end
-
- ActiveRecord::Tasks::DatabaseTasks.database_configuration = configuration
+ ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
if engine.paths['db/migrate'].existent
@@ -137,7 +125,7 @@ module ActiveRecord
end
end
- self.configurations = app.config.database_configuration || {}
+ self.configurations = Rails.application.config.database_configuration
establish_connection
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 58dfa2c5a5..561387a179 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -2,7 +2,7 @@ require 'active_record'
db_namespace = namespace :db do
task :load_config do
- ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {}
+ ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {}
ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 3963f2b3e0..4984dbd277 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -195,6 +195,7 @@ module ActiveRecord
# Person.exists?(5)
# Person.exists?('5')
# Person.exists?(['name LIKE ?', "%#{query}%"])
+ # Person.exists?(id: [1, 4, 8])
# Person.exists?(name: 'David')
# Person.exists?(false)
# Person.exists?
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 78da6a83ec..979216bee7 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -855,11 +855,11 @@ module ActiveRecord
end
single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
- unscope_code = :"#{scope}_value#{'s' unless single_val_method}="
+ unscope_code = "#{scope}_value#{'s' unless single_val_method}="
case scope
when :order
- self.send(:reverse_order_value=, false)
+ self.reverse_order_value = false
result = []
else
result = [] unless single_val_method
@@ -869,17 +869,19 @@ module ActiveRecord
end
def where_unscoping(target_value)
- target_value_sym = target_value.to_sym
+ target_value = target_value.to_s
where_values.reject! do |rel|
case rel
when Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual
subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
- subrelation.name.to_sym == target_value_sym
+ subrelation.name == target_value
else
raise "unscope(where: #{target_value.inspect}) failed: unscoping #{rel.class} is unimplemented."
end
end
+
+ bind_values.reject! { |col,_| col.name == target_value }
end
def custom_join_ast(table, joins)
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 45313b5e75..c33ffeece0 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -6,9 +6,6 @@ module ActiveRecord
extend ActiveSupport::Concern
ACTIONS = [:create, :destroy, :update]
- class TransactionError < ActiveRecordError # :nodoc:
- end
-
included do
define_callbacks :commit, :rollback,
terminator: ->(_, result) { result == false },
@@ -243,15 +240,14 @@ module ActiveRecord
def set_options_for_callbacks!(args)
options = args.last
if options.is_a?(Hash) && options[:on]
- assert_valid_transaction_action(options[:on])
- options[:if] = Array(options[:if])
fire_on = Array(options[:on])
+ assert_valid_transaction_action(fire_on)
+ options[:if] = Array(options[:if])
options[:if] << "transaction_include_any_action?(#{fire_on})"
end
end
def assert_valid_transaction_action(actions)
- actions = Array(actions)
if (actions - ACTIONS).any?
raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}"
end
@@ -277,6 +273,10 @@ module ActiveRecord
with_transaction_returning_status { super }
end
+ def touch(*) #:nodoc:
+ with_transaction_returning_status { super }
+ end
+
# Reset id and @new_record if the transaction rolls back.
def rollback_active_record_state!
remember_transaction_record_state
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index 01e7334aad..3daef399d8 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -21,7 +21,7 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
end
end
rescue ActiveRecord::StatementInvalid
- return skip "do not test on PG without json"
+ skip "do not test on PG without json"
end
@column = JsonDataType.columns.find { |c| c.name == 'payload' }
end
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index a56b8ac791..5c2d8e1c1d 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -26,7 +26,7 @@ if ActiveRecord::Base.connection.supports_ranges?
end
end
rescue ActiveRecord::StatementInvalid
- return skip "do not test on PG without range"
+ skip "do not test on PG without range"
end
insert_range(id: 101,
diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb
index bf14b378d8..dd2a727afe 100644
--- a/activerecord/test/cases/adapters/postgresql/xml_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb
@@ -18,7 +18,7 @@ class PostgresqlXMLTest < ActiveRecord::TestCase
end
end
rescue ActiveRecord::StatementInvalid
- return skip "do not test on PG without xml"
+ skip "do not test on PG without xml"
end
@column = XmlDataType.columns.find { |c| c.name == 'payload' }
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 0598ff25f8..02834edf7b 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -34,24 +34,30 @@ module ActiveRecord
end
def test_connect_with_url
- original_connection = ActiveRecord::Base.remove_connection
- tf = Tempfile.open 'whatever'
- url = "sqlite3://#{tf.path}"
- ActiveRecord::Base.establish_connection(url)
- assert ActiveRecord::Base.connection
- ensure
- tf.close
- tf.unlink
- ActiveRecord::Base.establish_connection(original_connection)
+ skip "can't establish new connection when using memory db" if in_memory_db?
+ begin
+ original_connection = ActiveRecord::Base.remove_connection
+ tf = Tempfile.open 'whatever'
+ url = "sqlite3://#{tf.path}"
+ ActiveRecord::Base.establish_connection(url)
+ assert ActiveRecord::Base.connection
+ ensure
+ tf.close
+ tf.unlink
+ ActiveRecord::Base.establish_connection(original_connection)
+ end
end
def test_connect_memory_with_url
- original_connection = ActiveRecord::Base.remove_connection
- url = "sqlite3:///:memory:"
- ActiveRecord::Base.establish_connection(url)
- assert ActiveRecord::Base.connection
- ensure
- ActiveRecord::Base.establish_connection(original_connection)
+ skip "can't establish new connection when using memory db" if in_memory_db?
+ begin
+ original_connection = ActiveRecord::Base.remove_connection
+ url = "sqlite3:///:memory:"
+ ActiveRecord::Base.establish_connection(url)
+ assert ActiveRecord::Base.connection
+ ensure
+ ActiveRecord::Base.establish_connection(original_connection)
+ end
end
def test_valid_column
@@ -183,7 +189,7 @@ module ActiveRecord
DualEncoding.connection.execute(<<-eosql)
CREATE TABLE dual_encodings (
id integer PRIMARY KEY AUTOINCREMENT,
- name string,
+ name varchar(255),
data binary
)
eosql
diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb
index 634f6b63ba..669569a774 100644
--- a/activerecord/test/cases/associations/eager_singularization_test.rb
+++ b/activerecord/test/cases/associations/eager_singularization_test.rb
@@ -1,128 +1,133 @@
require "cases/helper"
-class Virus < ActiveRecord::Base
- belongs_to :octopus
-end
-class Octopus < ActiveRecord::Base
- has_one :virus
-end
-class Pass < ActiveRecord::Base
- belongs_to :bus
-end
-class Bus < ActiveRecord::Base
- has_many :passes
-end
-class Mess < ActiveRecord::Base
- has_and_belongs_to_many :crises
-end
-class Crisis < ActiveRecord::Base
- has_and_belongs_to_many :messes
- has_many :analyses, :dependent => :destroy
- has_many :successes, :through => :analyses
- has_many :dresses, :dependent => :destroy
- has_many :compresses, :through => :dresses
-end
-class Analysis < ActiveRecord::Base
- belongs_to :crisis
- belongs_to :success
-end
-class Success < ActiveRecord::Base
- has_many :analyses, :dependent => :destroy
- has_many :crises, :through => :analyses
-end
-class Dress < ActiveRecord::Base
- belongs_to :crisis
- has_many :compresses
-end
-class Compress < ActiveRecord::Base
- belongs_to :dress
-end
-
class EagerSingularizationTest < ActiveRecord::TestCase
+ class Virus < ActiveRecord::Base
+ belongs_to :octopus
+ end
+
+ class Octopus < ActiveRecord::Base
+ has_one :virus
+ end
+
+ class Pass < ActiveRecord::Base
+ belongs_to :bus
+ end
+
+ class Bus < ActiveRecord::Base
+ has_many :passes
+ end
+
+ class Mess < ActiveRecord::Base
+ has_and_belongs_to_many :crises
+ end
+
+ class Crisis < ActiveRecord::Base
+ has_and_belongs_to_many :messes
+ has_many :analyses, :dependent => :destroy
+ has_many :successes, :through => :analyses
+ has_many :dresses, :dependent => :destroy
+ has_many :compresses, :through => :dresses
+ end
+
+ class Analysis < ActiveRecord::Base
+ belongs_to :crisis
+ belongs_to :success
+ end
+
+ class Success < ActiveRecord::Base
+ has_many :analyses, :dependent => :destroy
+ has_many :crises, :through => :analyses
+ end
+
+ class Dress < ActiveRecord::Base
+ belongs_to :crisis
+ has_many :compresses
+ end
+
+ class Compress < ActiveRecord::Base
+ belongs_to :dress
+ end
def setup
- if ActiveRecord::Base.connection.supports_migrations?
- ActiveRecord::Base.connection.create_table :viri do |t|
- t.column :octopus_id, :integer
- t.column :species, :string
- end
- ActiveRecord::Base.connection.create_table :octopi do |t|
- t.column :species, :string
- end
- ActiveRecord::Base.connection.create_table :passes do |t|
- t.column :bus_id, :integer
- t.column :rides, :integer
- end
- ActiveRecord::Base.connection.create_table :buses do |t|
- t.column :name, :string
- end
- ActiveRecord::Base.connection.create_table :crises_messes, :id => false do |t|
- t.column :crisis_id, :integer
- t.column :mess_id, :integer
- end
- ActiveRecord::Base.connection.create_table :messes do |t|
- t.column :name, :string
- end
- ActiveRecord::Base.connection.create_table :crises do |t|
- t.column :name, :string
- end
- ActiveRecord::Base.connection.create_table :successes do |t|
- t.column :name, :string
- end
- ActiveRecord::Base.connection.create_table :analyses do |t|
- t.column :crisis_id, :integer
- t.column :success_id, :integer
- end
- ActiveRecord::Base.connection.create_table :dresses do |t|
- t.column :crisis_id, :integer
- end
- ActiveRecord::Base.connection.create_table :compresses do |t|
- t.column :dress_id, :integer
- end
- @have_tables = true
- else
- @have_tables = false
+ skip 'Does not support migrations' unless connection.supports_migrations?
+
+ connection.create_table :viri do |t|
+ t.column :octopus_id, :integer
+ t.column :species, :string
+ end
+ connection.create_table :octopi do |t|
+ t.column :species, :string
+ end
+ connection.create_table :passes do |t|
+ t.column :bus_id, :integer
+ t.column :rides, :integer
+ end
+ connection.create_table :buses do |t|
+ t.column :name, :string
+ end
+ connection.create_table :crises_messes, :id => false do |t|
+ t.column :crisis_id, :integer
+ t.column :mess_id, :integer
+ end
+ connection.create_table :messes do |t|
+ t.column :name, :string
+ end
+ connection.create_table :crises do |t|
+ t.column :name, :string
+ end
+ connection.create_table :successes do |t|
+ t.column :name, :string
+ end
+ connection.create_table :analyses do |t|
+ t.column :crisis_id, :integer
+ t.column :success_id, :integer
+ end
+ connection.create_table :dresses do |t|
+ t.column :crisis_id, :integer
+ end
+ connection.create_table :compresses do |t|
+ t.column :dress_id, :integer
end
end
def teardown
- ActiveRecord::Base.connection.drop_table :viri
- ActiveRecord::Base.connection.drop_table :octopi
- ActiveRecord::Base.connection.drop_table :passes
- ActiveRecord::Base.connection.drop_table :buses
- ActiveRecord::Base.connection.drop_table :crises_messes
- ActiveRecord::Base.connection.drop_table :messes
- ActiveRecord::Base.connection.drop_table :crises
- ActiveRecord::Base.connection.drop_table :successes
- ActiveRecord::Base.connection.drop_table :analyses
- ActiveRecord::Base.connection.drop_table :dresses
- ActiveRecord::Base.connection.drop_table :compresses
+ connection.drop_table :viri
+ connection.drop_table :octopi
+ connection.drop_table :passes
+ connection.drop_table :buses
+ connection.drop_table :crises_messes
+ connection.drop_table :messes
+ connection.drop_table :crises
+ connection.drop_table :successes
+ connection.drop_table :analyses
+ connection.drop_table :dresses
+ connection.drop_table :compresses
+ end
+
+ def connection
+ ActiveRecord::Base.connection
end
def test_eager_no_extra_singularization_belongs_to
- return unless @have_tables
assert_nothing_raised do
Virus.all.merge!(:includes => :octopus).to_a
end
end
def test_eager_no_extra_singularization_has_one
- return unless @have_tables
assert_nothing_raised do
Octopus.all.merge!(:includes => :virus).to_a
end
end
def test_eager_no_extra_singularization_has_many
- return unless @have_tables
assert_nothing_raised do
Bus.all.merge!(:includes => :passes).to_a
end
end
def test_eager_no_extra_singularization_has_and_belongs_to_many
- return unless @have_tables
assert_nothing_raised do
Crisis.all.merge!(:includes => :messes).to_a
Mess.all.merge!(:includes => :crises).to_a
@@ -130,14 +135,12 @@ class EagerSingularizationTest < ActiveRecord::TestCase
end
def test_eager_no_extra_singularization_has_many_through_belongs_to
- return unless @have_tables
assert_nothing_raised do
Crisis.all.merge!(:includes => :successes).to_a
end
end
def test_eager_no_extra_singularization_has_many_through_has_many
- return unless @have_tables
assert_nothing_raised do
Crisis.all.merge!(:includes => :compresses).to_a
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 517d2674a7..fe5de44409 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -574,6 +574,13 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
end
+ def teardown
+ # We are running without transactional fixtures and need to cleanup.
+ Bird.delete_all
+ @ship.delete
+ @pirate.delete
+ end
+
# reload
def test_a_marked_for_destruction_record_should_not_be_be_marked_after_reload
@pirate.mark_for_destruction
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index cb8e564da1..983bcd9826 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -626,6 +626,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal ["EUC-JP"], Weird.columns.map {|c| c.name.encoding.name }.uniq
ensure
silence_warnings { Encoding.default_internal = old_default_internal }
+ Weird.reset_column_information
end
end
@@ -1129,7 +1130,7 @@ class BasicsTest < ActiveRecord::TestCase
k = Class.new(ak)
k.table_name = "projects"
orig_name = k.sequence_name
- return skip "sequences not supported by db" unless orig_name
+ skip "sequences not supported by db" unless orig_name
assert_equal k.reset_sequence_name, orig_name
end
@@ -1301,9 +1302,11 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_compute_type_nonexistent_constant
- assert_raises NameError do
+ e = assert_raises NameError do
ActiveRecord::Base.send :compute_type, 'NonexistentModel'
end
+ assert_equal 'uninitialized constant ActiveRecord::Base::NonexistentModel', e.message
+ assert_equal 'ActiveRecord::Base::NonexistentModel', e.name
end
def test_compute_type_no_method_error
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 3e33b30144..318cc5a32c 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -2,6 +2,133 @@ require "cases/helper"
module ActiveRecord
module ConnectionAdapters
+
+ class MergeAndResolveDefaultUrlConfigTest < ActiveRecord::TestCase
+
+ def klass
+ ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig
+ end
+
+ def setup
+ @previous_database_url = ENV.delete("DATABASE_URL")
+ end
+
+ def teardown
+ ENV["DATABASE_URL"] = @previous_database_url
+ end
+
+ def test_string_connection
+ config = { "production" => "postgres://localhost/foo" }
+ actual = klass.new(config).resolve
+ expected = { "production" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_url_sub_key
+ config = { "production" => { "url" => "postgres://localhost/foo" } }
+ actual = klass.new(config).resolve
+ expected = { "production" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_hash
+ config = { "production" => { "adapter" => "postgres", "database" => "foo" } }
+ actual = klass.new(config).resolve
+ assert_equal config, actual
+ end
+
+ def test_blank
+ config = {}
+ actual = klass.new(config).resolve
+ assert_equal config, actual
+ end
+
+ def test_blank_with_database_url
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+
+ config = {}
+ actual = klass.new(config).resolve
+ expected = { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost" }
+ assert_equal expected, actual["production"]
+ assert_equal expected, actual["development"]
+ assert_equal expected, actual["test"]
+ assert_equal nil, actual[:production]
+ assert_equal nil, actual[:development]
+ assert_equal nil, actual[:test]
+ end
+
+ def test_sting_with_database_url
+ ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO"
+
+ config = { "production" => "postgres://localhost/foo" }
+ actual = klass.new(config).resolve
+
+ expected = { "production" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_url_sub_key_with_database_url
+ ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO"
+
+ config = { "production" => { "url" => "postgres://localhost/foo" } }
+ actual = klass.new(config).resolve
+ expected = { "production" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_merge_no_conflicts_with_database_url
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+
+ config = {"production" => { "pool" => "5" } }
+ actual = klass.new(config).resolve
+ expected = { "production" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost",
+ "pool" => "5"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_merge_conflicts_with_database_url
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+
+ config = {"production" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } }
+ actual = klass.new(config).resolve
+ expected = { "production" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost",
+ "pool" => "5"
+ }
+ }
+ assert_equal expected, actual
+ end
+ end
+
class ConnectionHandlerTest < ActiveRecord::TestCase
def setup
@klass = Class.new(Base) { def self.name; 'klass'; end }
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index c09e58fbf1..1f98801e93 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -74,9 +74,9 @@ class EnumTest < ActiveRecord::TestCase
end
test "constant to access the mapping" do
- assert_equal 0, Book::STATUS[:proposed]
- assert_equal 1, Book::STATUS["written"]
- assert_equal 2, Book::STATUS[:published]
+ assert_equal 0, Book.statuses[:proposed]
+ assert_equal 1, Book.statuses["written"]
+ assert_equal 2, Book.statuses[:published]
end
test "building new objects with enum scopes" do
@@ -88,4 +88,8 @@ class EnumTest < ActiveRecord::TestCase
assert Book.written.create.written?
assert Book.read.create.read?
end
+
+ test "_before_type_cast returns the enum label (required for form fields)" do
+ assert_equal "proposed", @book.status_before_type_cast
+ end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 5125d5df2a..9b575557de 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -891,11 +891,4 @@ class FinderTest < ActiveRecord::TestCase
ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
end
end
-
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- end
end
diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb
index 96e581ab4c..367d04a154 100644
--- a/activerecord/test/cases/hot_compatibility_test.rb
+++ b/activerecord/test/cases/hot_compatibility_test.rb
@@ -5,7 +5,7 @@ class HotCompatibilityTest < ActiveRecord::TestCase
setup do
@klass = Class.new(ActiveRecord::Base) do
- connection.create_table :hot_compatibilities do |t|
+ connection.create_table :hot_compatibilities, force: true do |t|
t.string :foo
t.string :bar
end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index cb0d374ef7..d2b5a06b55 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -1,10 +1,11 @@
-require "cases/helper"
+require 'cases/helper'
require 'models/company'
require 'models/person'
require 'models/post'
require 'models/project'
require 'models/subscriber'
require 'models/vegetables'
+require 'models/shop'
class InheritanceTest < ActiveRecord::TestCase
fixtures :companies, :projects, :subscribers, :accounts, :vegetables
@@ -128,6 +129,17 @@ class InheritanceTest < ActiveRecord::TestCase
assert_kind_of Cabbage, cabbage
end
+ def test_alt_becomes_bang_resets_inheritance_type_column
+ vegetable = Vegetable.create!(name: "Red Pepper")
+ assert_nil vegetable.custom_type
+
+ cabbage = vegetable.becomes!(Cabbage)
+ assert_equal "Cabbage", cabbage.custom_type
+
+ vegetable = cabbage.becomes!(Vegetable)
+ assert_nil cabbage.custom_type
+ end
+
def test_inheritance_find_all
companies = Company.all.merge!(:order => 'id').to_a
assert_kind_of Firm, companies[0], "37signals should be a firm"
@@ -356,4 +368,10 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
ensure
ActiveRecord::Base.store_full_sti_class = true
end
+
+ def test_sti_type_from_attributes_disabled_in_non_sti_class
+ phone = Shop::Product::Type.new(name: 'Phone')
+ product = Shop::Product.new(:type => phone)
+ assert product.save
+ end
end
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index 2e71b1a40d..dfb8a608cb 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -3,12 +3,11 @@
require 'cases/helper'
require 'models/company'
require 'models/developer'
-require 'models/car'
-require 'models/bulb'
require 'models/owner'
+require 'models/pet'
class IntegrationTest < ActiveRecord::TestCase
- fixtures :companies, :developers, :owners
+ fixtures :companies, :developers, :owners, :pets
def test_to_param_should_return_string
assert_kind_of String, Client.first.to_param
@@ -91,13 +90,14 @@ class IntegrationTest < ActiveRecord::TestCase
end
def test_cache_key_changes_when_child_touched
- car = Car.create
- Bulb.create(car: car)
+ owner = owners(:blackbeard)
+ pet = pets(:parrot)
+
+ owner.update_column :updated_at, Time.current
+ key = owner.cache_key
- key = car.cache_key
- car.bulb.touch
- car.reload
- assert_not_equal key, car.cache_key
+ assert pet.touch
+ assert_not_equal key, owner.reload.cache_key
end
def test_cache_key_format_for_existing_record_with_nil_updated_timestamps
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 0363bf1048..1bda472d23 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -328,6 +328,7 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_proper_table_name_on_migrator
+ reminder_class = new_isolated_reminder_class
assert_deprecated do
assert_equal "table", ActiveRecord::Migrator.proper_table_name('table')
end
@@ -335,30 +336,30 @@ class MigrationTest < ActiveRecord::TestCase
assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table)
end
assert_deprecated do
- assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder)
+ assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(reminder_class)
end
- Reminder.reset_table_name
+ reminder_class.reset_table_name
assert_deprecated do
- assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder)
+ assert_equal reminder_class.table_name, ActiveRecord::Migrator.proper_table_name(reminder_class)
end
# Use the model's own prefix/suffix if a model is given
ActiveRecord::Base.table_name_prefix = "ARprefix_"
ActiveRecord::Base.table_name_suffix = "_ARsuffix"
- Reminder.table_name_prefix = 'prefix_'
- Reminder.table_name_suffix = '_suffix'
- Reminder.reset_table_name
+ reminder_class.table_name_prefix = 'prefix_'
+ reminder_class.table_name_suffix = '_suffix'
+ reminder_class.reset_table_name
assert_deprecated do
- assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder)
+ assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(reminder_class)
end
- Reminder.table_name_prefix = ''
- Reminder.table_name_suffix = ''
- Reminder.reset_table_name
+ reminder_class.table_name_prefix = ''
+ reminder_class.table_name_suffix = ''
+ reminder_class.reset_table_name
# Use AR::Base's prefix/suffix if string or symbol is given
ActiveRecord::Base.table_name_prefix = "prefix_"
ActiveRecord::Base.table_name_suffix = "_suffix"
- Reminder.reset_table_name
+ reminder_class.reset_table_name
assert_deprecated do
assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table')
end
@@ -368,28 +369,29 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_proper_table_name_on_migration
+ reminder_class = new_isolated_reminder_class
migration = ActiveRecord::Migration.new
assert_equal "table", migration.proper_table_name('table')
assert_equal "table", migration.proper_table_name(:table)
- assert_equal "reminders", migration.proper_table_name(Reminder)
- Reminder.reset_table_name
- assert_equal Reminder.table_name, migration.proper_table_name(Reminder)
+ assert_equal "reminders", migration.proper_table_name(reminder_class)
+ reminder_class.reset_table_name
+ assert_equal reminder_class.table_name, migration.proper_table_name(reminder_class)
# Use the model's own prefix/suffix if a model is given
ActiveRecord::Base.table_name_prefix = "ARprefix_"
ActiveRecord::Base.table_name_suffix = "_ARsuffix"
- Reminder.table_name_prefix = 'prefix_'
- Reminder.table_name_suffix = '_suffix'
- Reminder.reset_table_name
- assert_equal "prefix_reminders_suffix", migration.proper_table_name(Reminder)
- Reminder.table_name_prefix = ''
- Reminder.table_name_suffix = ''
- Reminder.reset_table_name
+ reminder_class.table_name_prefix = 'prefix_'
+ reminder_class.table_name_suffix = '_suffix'
+ reminder_class.reset_table_name
+ assert_equal "prefix_reminders_suffix", migration.proper_table_name(reminder_class)
+ reminder_class.table_name_prefix = ''
+ reminder_class.table_name_suffix = ''
+ reminder_class.reset_table_name
# Use AR::Base's prefix/suffix if string or symbol is given
ActiveRecord::Base.table_name_prefix = "prefix_"
ActiveRecord::Base.table_name_suffix = "_suffix"
- Reminder.reset_table_name
+ reminder_class.reset_table_name
assert_equal "prefix_table_suffix", migration.proper_table_name('table', migration.table_name_options)
assert_equal "prefix_table_suffix", migration.proper_table_name(:table, migration.table_name_options)
end
@@ -532,11 +534,13 @@ class MigrationTest < ActiveRecord::TestCase
end
protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
+ # This is needed to isolate class_attribute assignments like `table_name_prefix`
+ # for each test case.
+ def new_isolated_reminder_class
+ Class.new(Reminder) {
+ def self.name; "Reminder"; end
+ def self.base_class; self; end
+ }
end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 5566563116..da8ae672fe 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -8,7 +8,7 @@ require 'rack'
class QueryCacheTest < ActiveRecord::TestCase
fixtures :tasks, :topics, :categories, :posts, :categories_posts
- def setup
+ teardown do
Task.connection.clear_query_cache
ActiveRecord::Base.connection.disable_query_cache!
end
@@ -214,7 +214,7 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
Post.find(1)
# change the column definition
- Post.connection.change_column :posts, :title, :string, :limit => 80
+ Post.connection.change_column :posts, :title, :string, limit: 80
assert_nothing_raised { Post.find(1) }
# restore the old definition
@@ -241,7 +241,6 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
def test_update
Task.connection.expects(:clear_query_cache).times(2)
-
Task.cache do
task = Task.find(1)
task.starting = Time.now.utc
@@ -251,7 +250,6 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
def test_destroy
Task.connection.expects(:clear_query_cache).times(2)
-
Task.cache do
Task.find(1).destroy
end
@@ -259,7 +257,6 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
def test_insert
ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)
-
Task.cache do
Task.create!
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 031da8e6d6..afd5a69cef 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1511,17 +1511,13 @@ class RelationTest < ActiveRecord::TestCase
assert !Post.all.respond_to?(:by_lifo)
end
- test "merge collapses wheres from the LHS only" do
- left = Post.where(title: "omg").where(comments_count: 1)
- right = Post.where(title: "wtf").where(title: "bbq")
-
- expected = [left.where_values[1]] + right.where_values
- merged = left.merge(right)
+ def test_unscope_removes_binds
+ left = Post.where(id: Arel::Nodes::BindParam.new('?'))
+ column = Post.columns_hash['id']
+ left.bind_values += [[column, 20]]
- assert_equal expected, merged.where_values
- assert !merged.to_sql.include?("omg")
- assert merged.to_sql.include?("wtf")
- assert merged.to_sql.include?("bbq")
+ relation = left.unscope(where: :id)
+ assert_equal [], relation.bind_values
end
def test_merging_removes_rhs_bind_parameters
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 741827446d..c085663efb 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -190,6 +190,8 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
elsif current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
+ elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index?
+ assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition
else
assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition
end
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 5644a35385..7e7d95841b 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -1,9 +1,11 @@
require "cases/helper"
+require 'models/owner'
+require 'models/pet'
require 'models/topic'
class TransactionCallbacksTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
- fixtures :topics
+ fixtures :topics, :owners, :pets
class ReplyWithCallbacks < ActiveRecord::Base
self.table_name = :topics
@@ -28,14 +30,14 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id"
- after_commit{|record| record.send(:do_after_commit, nil)}
- after_commit(:on => :create){|record| record.send(:do_after_commit, :create)}
- after_commit(:on => :update){|record| record.send(:do_after_commit, :update)}
- after_commit(:on => :destroy){|record| record.send(:do_after_commit, :destroy)}
- after_rollback{|record| record.send(:do_after_rollback, nil)}
- after_rollback(:on => :create){|record| record.send(:do_after_rollback, :create)}
- after_rollback(:on => :update){|record| record.send(:do_after_rollback, :update)}
- after_rollback(:on => :destroy){|record| record.send(:do_after_rollback, :destroy)}
+ after_commit { |record| record.do_after_commit(nil) }
+ after_commit(on: :create) { |record| record.do_after_commit(:create) }
+ after_commit(on: :update) { |record| record.do_after_commit(:update) }
+ after_commit(on: :destroy) { |record| record.do_after_commit(:destroy) }
+ after_rollback { |record| record.do_after_rollback(nil) }
+ after_rollback(on: :create) { |record| record.do_after_rollback(:create) }
+ after_rollback(on: :update) { |record| record.do_after_rollback(:update) }
+ after_rollback(on: :destroy) { |record| record.do_after_rollback(:destroy) }
def history
@history ||= []
@@ -65,7 +67,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def setup
- @first, @second = TopicWithCallbacks.find(1, 3).sort_by { |t| t.id }
+ @first = TopicWithCallbacks.find(1)
end
def test_call_after_commit_after_transaction_commits
@@ -77,40 +79,25 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_only_call_after_commit_on_update_after_transaction_commits_for_existing_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+ add_transaction_execution_blocks @first
@first.save!
assert_equal [:commit_on_update], @first.history
end
def test_only_call_after_commit_on_destroy_after_transaction_commits_for_destroyed_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+ add_transaction_execution_blocks @first
@first.destroy
assert_equal [:commit_on_destroy], @first.history
end
def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record
- @new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today)
- @new_record.after_commit_block(:create){|r| r.history << :commit_on_create}
- @new_record.after_commit_block(:update){|r| r.history << :commit_on_update}
- @new_record.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @new_record.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @new_record.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @new_record.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
-
- @new_record.save!
- assert_equal [:commit_on_create], @new_record.history
+ new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today)
+ add_transaction_execution_blocks new_record
+
+ new_record.save!
+ assert_equal [:commit_on_create], new_record.history
end
def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record_if_create_succeeds_creating_through_association
@@ -120,6 +107,13 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal [], reply.history
end
+ def test_only_call_after_commit_on_update_after_transaction_commits_for_existing_record_on_touch
+ add_transaction_execution_blocks @first
+
+ @first.touch
+ assert_equal [:commit_on_update], @first.history
+ end
+
def test_call_after_rollback_after_transaction_rollsback
@first.after_commit_block{|r| r.history << :after_commit}
@first.after_rollback_block{|r| r.history << :after_rollback}
@@ -133,12 +127,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_only_call_after_rollback_on_update_after_transaction_rollsback_for_existing_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+ add_transaction_execution_blocks @first
Topic.transaction do
@first.save!
@@ -148,13 +137,19 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal [:rollback_on_update], @first.history
end
+ def test_only_call_after_rollback_on_update_after_transaction_rollsback_for_existing_record_on_touch
+ add_transaction_execution_blocks @first
+
+ Topic.transaction do
+ @first.touch
+ raise ActiveRecord::Rollback
+ end
+
+ assert_equal [:rollback_on_update], @first.history
+ end
+
def test_only_call_after_rollback_on_destroy_after_transaction_rollsback_for_destroyed_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_update}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+ add_transaction_execution_blocks @first
Topic.transaction do
@first.destroy
@@ -165,20 +160,15 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_only_call_after_rollback_on_create_after_transaction_rollsback_for_new_record
- @new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today)
- @new_record.after_commit_block(:create){|r| r.history << :commit_on_create}
- @new_record.after_commit_block(:update){|r| r.history << :commit_on_update}
- @new_record.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @new_record.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @new_record.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @new_record.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+ new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today)
+ add_transaction_execution_blocks new_record
Topic.transaction do
- @new_record.save!
+ new_record.save!
raise ActiveRecord::Rollback
end
- assert_equal [:rollback_on_create], @new_record.history
+ assert_equal [:rollback_on_create], new_record.history
end
def test_call_after_rollback_when_commit_fails
@@ -205,23 +195,24 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
@first.after_rollback_block{|r| r.rollbacks(1)}
@first.after_commit_block{|r| r.commits(1)}
- def @second.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
- def @second.commits(i=0); @commits ||= 0; @commits += i if i; end
- @second.after_rollback_block{|r| r.rollbacks(1)}
- @second.after_commit_block{|r| r.commits(1)}
+ second = TopicWithCallbacks.find(3)
+ def second.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
+ def second.commits(i=0); @commits ||= 0; @commits += i if i; end
+ second.after_rollback_block{|r| r.rollbacks(1)}
+ second.after_commit_block{|r| r.commits(1)}
Topic.transaction do
@first.save!
Topic.transaction(:requires_new => true) do
- @second.save!
+ second.save!
raise ActiveRecord::Rollback
end
end
assert_equal 1, @first.commits
assert_equal 0, @first.rollbacks
- assert_equal 0, @second.commits
- assert_equal 1, @second.rollbacks
+ assert_equal 0, second.commits
+ assert_equal 1, second.rollbacks
end
def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint_when_release_savepoint_fails
@@ -252,33 +243,61 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
def @first.last_after_transaction_error; @last_transaction_error; end
@first.after_commit_block{|r| r.last_after_transaction_error = :commit; raise "fail!";}
@first.after_rollback_block{|r| r.last_after_transaction_error = :rollback; raise "fail!";}
- @second.after_commit_block{|r| r.history << :after_commit}
- @second.after_rollback_block{|r| r.history << :after_rollback}
+
+ second = TopicWithCallbacks.find(3)
+ second.after_commit_block{|r| r.history << :after_commit}
+ second.after_rollback_block{|r| r.history << :after_rollback}
Topic.transaction do
@first.save!
- @second.save!
+ second.save!
end
assert_equal :commit, @first.last_after_transaction_error
- assert_equal [:after_commit], @second.history
+ assert_equal [:after_commit], second.history
- @second.history.clear
+ second.history.clear
Topic.transaction do
@first.save!
- @second.save!
+ second.save!
raise ActiveRecord::Rollback
end
assert_equal :rollback, @first.last_after_transaction_error
- assert_equal [:after_rollback], @second.history
+ assert_equal [:after_rollback], second.history
end
def test_after_rollback_callbacks_should_validate_on_condition
- assert_raise(ArgumentError) { Topic.send(:after_rollback, :on => :save) }
+ assert_raise(ArgumentError) { Topic.after_rollback(on: :save) }
end
def test_after_commit_callbacks_should_validate_on_condition
- assert_raise(ArgumentError) { Topic.send(:after_commit, :on => :save) }
+ assert_raise(ArgumentError) { Topic.after_commit(on: :save) }
end
+
+ def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_call_callbacks_on_the_parent_object
+ pet = Pet.first
+ owner = pet.owner
+ flag = false
+
+ owner.on_after_commit do
+ flag = true
+ end
+
+ pet.name = "Fluffy the Third"
+ pet.save
+
+ assert flag
+ end
+
+ private
+
+ def add_transaction_execution_blocks(record)
+ record.after_commit_block(:create) { |r| r.history << :commit_on_create }
+ record.after_commit_block(:update) { |r| r.history << :commit_on_update }
+ record.after_commit_block(:destroy) { |r| r.history << :commit_on_destroy }
+ record.after_rollback_block(:create) { |r| r.history << :rollback_on_create }
+ record.after_rollback_block(:update) { |r| r.history << :rollback_on_update }
+ record.after_rollback_block(:destroy) { |r| r.history << :rollback_on_destroy }
+ end
end
class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index c4a15a79e2..db0f93f63b 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -15,7 +15,6 @@ class Car < ActiveRecord::Base
scope :incl_engines, -> { includes(:engines) }
scope :order_using_new_style, -> { order('name asc') }
-
end
class CoolCar < Car
diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb
index 1c7ed4aa3e..cf24502d3a 100644
--- a/activerecord/test/models/owner.rb
+++ b/activerecord/test/models/owner.rb
@@ -2,4 +2,21 @@ class Owner < ActiveRecord::Base
self.primary_key = :owner_id
has_many :pets, -> { order 'pets.name desc' }
has_many :toys, :through => :pets
+
+ after_commit :execute_blocks
+
+ def blocks
+ @blocks ||= []
+ end
+
+ def on_after_commit(&block)
+ blocks << block
+ end
+
+ def execute_blocks
+ blocks.each do |block|
+ block.call(self)
+ end
+ @blocks = []
+ end
end
diff --git a/activerecord/test/models/shop.rb b/activerecord/test/models/shop.rb
index 81414227ea..607a0a5b41 100644
--- a/activerecord/test/models/shop.rb
+++ b/activerecord/test/models/shop.rb
@@ -5,6 +5,11 @@ module Shop
class Product < ActiveRecord::Base
has_many :variants, :dependent => :delete_all
+ belongs_to :type
+
+ class Type < ActiveRecord::Base
+ has_many :products
+ end
end
class Variant < ActiveRecord::Base
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index ddfc1ac0d6..9a7d918a25 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -557,9 +557,14 @@ ActiveRecord::Schema.define do
create_table :products, force: true do |t|
t.references :collection
+ t.references :type
t.string :name
end
+ create_table :product_types, force: true do |t|
+ t.string :name
+ end
+
create_table :projects, force: true do |t|
t.string :name
t.string :type