aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/associations.rb8
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb29
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb8
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb12
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb2
-rw-r--r--activerecord/lib/active_record/core.rb15
-rw-r--r--activerecord/lib/active_record/dynamic_finder_match.rb82
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb2
-rw-r--r--activerecord/lib/active_record/dynamic_scope_match.rb4
-rw-r--r--activerecord/lib/active_record/explain.rb2
-rw-r--r--activerecord/lib/active_record/locale/en.yml1
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb12
-rw-r--r--activerecord/lib/active_record/migration/join_table.rb17
-rw-r--r--activerecord/lib/active_record/model_schema.rb2
-rw-r--r--activerecord/lib/active_record/null_relation.rb10
-rw-r--r--activerecord/lib/active_record/querying.rb5
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb33
-rw-r--r--activerecord/lib/active_record/result.rb25
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb3
29 files changed, 281 insertions, 76 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 78e958442f..73c8a06ab7 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -43,6 +43,7 @@ module ActiveRecord
autoload :AutosaveAssociation
autoload :Relation
+ autoload :NullRelation
autoload_under 'relation' do
autoload :QueryMethods
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 58725246c8..958821add6 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1097,8 +1097,8 @@ module ActiveRecord
# alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated
# objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
# objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. If set to
- # <tt>:restrict</tt> this object raises an <tt>ActiveRecord::DeleteRestrictionError</tt> exception and
- # cannot be deleted if it has any associated objects.
+ # <tt>:restrict</tt> an error will be added to the object, preventing its deletion, if any associated
+ # objects are present.
#
# If using with the <tt>:through</tt> option, the association on the join model must be
# a +belongs_to+, and the records which get deleted are the join records, rather than
@@ -1251,8 +1251,8 @@ module ActiveRecord
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
# If set to <tt>:nullify</tt>, the associated object's foreign key is set to +NULL+.
- # Also, association is assigned. If set to <tt>:restrict</tt> this object raises an
- # <tt>ActiveRecord::DeleteRestrictionError</tt> exception and cannot be deleted if it has any associated object.
+ # If set to <tt>:restrict</tt>, an error will be added to the object, preventing its deletion, if an
+ # associated object is present.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 6e2e5f9de0..c3fa4a05fd 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -51,5 +51,34 @@ module ActiveRecord::Associations::Builder
association(name).writer(value)
end
end
+
+ def dependent_restrict_raises?
+ ActiveRecord::Base.dependent_restrict_raises == true
+ end
+
+ def dependent_restrict_deprecation_warning
+ if dependent_restrict_raises?
+ msg = "In the next release, `:dependent => :restrict` will not raise a `DeleteRestrictionError`. "\
+ "Instead, it will add an error on the model. To fix this warning, make sure your code " \
+ "isn't relying on a `DeleteRestrictionError` and then add " \
+ "`config.active_record.dependent_restrict_raises = false` to your application config."
+ ActiveSupport::Deprecation.warn msg
+ end
+ end
+
+ def define_restrict_dependency_method
+ name = self.name
+ mixin.redefine_method(dependency_method_name) do
+ # has_many or has_one associations
+ if send(name).respond_to?(:exists?) ? send(name).exists? : !send(name).nil?
+ if dependent_restrict_raises?
+ raise ActiveRecord::DeleteRestrictionError.new(name)
+ else
+ errors.add(:base, :restrict_dependent_destroy, :model => name.to_s.singularize)
+ return false
+ end
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index fc6799fb15..9ddfd433e4 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -21,6 +21,7 @@ module ActiveRecord::Associations::Builder
":nullify or :restrict (#{options[:dependent].inspect})"
end
+ dependent_restrict_deprecation_warning if options[:dependent] == :restrict
send("define_#{options[:dependent]}_dependency_method")
model.before_destroy dependency_method_name
end
@@ -52,13 +53,6 @@ module ActiveRecord::Associations::Builder
end
end
- def define_restrict_dependency_method
- name = self.name
- mixin.redefine_method(dependency_method_name) do
- raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).empty?
- end
- end
-
def dependency_method_name
"has_many_dependent_for_#{name}"
end
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index 7a6cd3890f..bc8a212bee 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -34,15 +34,12 @@ module ActiveRecord::Associations::Builder
":nullify or :restrict (#{options[:dependent].inspect})"
end
+ dependent_restrict_deprecation_warning if options[:dependent] == :restrict
send("define_#{options[:dependent]}_dependency_method")
model.before_destroy dependency_method_name
end
end
- def dependency_method_name
- "has_one_dependent_#{options[:dependent]}_for_#{name}"
- end
-
def define_destroy_dependency_method
name = self.name
mixin.redefine_method(dependency_method_name) do
@@ -52,11 +49,8 @@ module ActiveRecord::Associations::Builder
alias :define_delete_dependency_method :define_destroy_dependency_method
alias :define_nullify_dependency_method :define_destroy_dependency_method
- def define_restrict_dependency_method
- name = self.name
- mixin.redefine_method(dependency_method_name) do
- raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).nil?
- end
+ def dependency_method_name
+ "has_one_dependent_#{options[:dependent]}_for_#{name}"
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 7aed64d48c..b2136605e1 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -58,7 +58,7 @@ module ActiveRecord
end
end
- relation.uniq.pluck(column)
+ relation.pluck(column)
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 3549cbb090..c129dc8c52 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -92,11 +92,7 @@ module ActiveRecord
end
def internal_attribute_access_code(attr_name, cast_code)
- if attr_name == primary_key
- access_code = "v = @attributes[attr_name];"
- else
- access_code = "v = @attributes.fetch(attr_name) { missing_attribute(attr_name, caller) };"
- end
+ access_code = "v = @attributes.fetch(attr_name) { missing_attribute(attr_name, caller) };"
access_code << "v && #{cast_code};"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 27ff13ad89..6ba64bb88f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -65,18 +65,24 @@ module ActiveRecord
end
private
- def cache_sql(sql, binds)
- result =
- if @query_cache[sql].key?(binds)
- ActiveSupport::Notifications.instrument("sql.active_record",
- :sql => sql, :name => "CACHE", :connection_id => object_id)
- @query_cache[sql][binds]
- else
- @query_cache[sql][binds] = yield
- end
+ def cache_sql(sql, binds)
+ result =
+ if @query_cache[sql].key?(binds)
+ ActiveSupport::Notifications.instrument("sql.active_record",
+ :sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id)
+ @query_cache[sql][binds]
+ else
+ @query_cache[sql][binds] = yield
+ end
+ # FIXME: we should guarantee that all cached items are Result
+ # objects. Then we can avoid this conditional
+ if ActiveRecord::Result === result
+ result.dup
+ else
result.collect { |row| row.dup }
end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 0cac6d1391..84c340770a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1,9 +1,12 @@
require 'active_support/deprecation/reporting'
require 'active_record/schema_migration'
+require 'active_record/migration/join_table'
module ActiveRecord
module ConnectionAdapters # :nodoc:
module SchemaStatements
+ include ActiveRecord::Migration::JoinTable
+
# Returns a Hash of mappings from the abstract data types to the native
# database types. See TableDefinition#column for details on the recognized
# abstract data types.
@@ -170,6 +173,45 @@ module ActiveRecord
execute create_sql
end
+ # Creates a new join table with the name created using the lexical order of the first two
+ # arguments. These arguments can be be a String or a Symbol.
+ #
+ # # Creates a table called 'assemblies_parts' with no id.
+ # create_join_table(:assemblies, :parts)
+ #
+ # You can pass a +options+ hash can include the following keys:
+ # [<tt>:table_name</tt>]
+ # Sets the table name overriding the default
+ # [<tt>:column_options</tt>]
+ # Any extra options you want appended to the columns definition.
+ # [<tt>:options</tt>]
+ # Any extra options you want appended to the table definition.
+ # [<tt>:temporary</tt>]
+ # Make a temporary table.
+ # [<tt>:force</tt>]
+ # Set to true to drop the table before creating it.
+ # Defaults to false.
+ #
+ # ===== Examples
+ # ====== Add a backend specific option to the generated SQL (MySQL)
+ # create_join_table(:assemblies, :parts, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
+ # generates:
+ # CREATE TABLE assemblies_parts (
+ # assembly_id int NOT NULL,
+ # part_id int NOT NULL,
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
+ def create_join_table(table_1, table_2, options = {})
+ join_table_name = find_join_table_name(table_1, table_2, options)
+
+ column_options = options.delete(:column_options) || {}
+ column_options.reverse_merge!({:null => false})
+
+ create_table(join_table_name, options.merge!(:id => false)) do |td|
+ td.integer :"#{table_1.to_s.singularize}_id", column_options
+ td.integer :"#{table_2.to_s.singularize}_id", column_options
+ end
+ end
+
# A block for changing columns in +table+.
#
# === Example
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 201c05d8f5..9d9dbcc355 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -505,7 +505,7 @@ module ActiveRecord
execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
create_table = each_hash(result).first[:"Create Table"]
if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/
- keys = $1.split(",").map { |key| key.gsub(/`/, "") }
+ keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") }
keys.length == 1 ? [keys.first, nil] : nil
else
nil
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 6086c32dbe..c1332fde1a 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -225,7 +225,7 @@ module ActiveRecord
# column values as values.
def select(sql, name = nil, binds = [])
binds = binds.dup
- exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a
+ exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name)
end
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index e432c5af32..5905242747 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -408,7 +408,7 @@ module ActiveRecord
def select(sql, name = nil, binds = [])
@connection.query_with_result = true
- rows = exec_query(sql, name, binds).to_a
+ rows = exec_query(sql, name, binds)
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
rows
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index dbfb375ba8..1d8e5d813a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1249,7 +1249,7 @@ module ActiveRecord
# Executes a SELECT query and returns the results, performing any data type
# conversions that are required to be performed here instead of in PostgreSQLColumn.
def select(sql, name = nil, binds = [])
- exec_query(sql, name, binds).to_a
+ exec_query(sql, name, binds)
end
def select_raw(sql, name = nil)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 69750a911d..0520fc8b62 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -454,7 +454,7 @@ module ActiveRecord
protected
def select(sql, name = nil, binds = []) #:nodoc:
- exec_query(sql, name, binds).to_a
+ exec_query(sql, name, binds)
end
def table_structure(table_name)
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index adba3f710c..a2ce620354 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -72,6 +72,16 @@ module ActiveRecord
# The connection handler
config_attribute :connection_handler
self.connection_handler = ConnectionAdapters::ConnectionHandler.new
+
+ ##
+ # :singleton-method:
+ # Specifies wether or not has_many or has_one association option
+ # :dependent => :restrict raises an exception. If set to true, the
+ # ActiveRecord::DeleteRestrictionError exception will be raised
+ # along with a DEPRECATION WARNING. If set to false, an error would
+ # be added to the model instead.
+ config_attribute :dependent_restrict_raises, :global => true
+ self.dependent_restrict_raises = true
end
module ClassMethods
@@ -202,6 +212,7 @@ module ActiveRecord
cloned_attributes.delete(self.class.primary_key)
@attributes = cloned_attributes
+ @attributes[self.class.primary_key] = nil
run_callbacks(:initialize) if _initialize_callbacks.any?
@@ -326,6 +337,10 @@ module ActiveRecord
end
def init_internals
+ pk = self.class.primary_key
+
+ @attributes[pk] = nil unless @attributes.key?(pk)
+
@relation = nil
@aggregation_cache = {}
@association_cache = {}
diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb
index b309df9b1b..38dbbef5fc 100644
--- a/activerecord/lib/active_record/dynamic_finder_match.rb
+++ b/activerecord/lib/active_record/dynamic_finder_match.rb
@@ -6,33 +6,23 @@ module ActiveRecord
#
class DynamicFinderMatch
def self.match(method)
- finder = :first
- bang = false
- instantiator = nil
-
- case method.to_s
- when /^find_(all_|last_)?by_([_a-zA-Z]\w*)$/
- finder = :last if $1 == 'last_'
- finder = :all if $1 == 'all_'
- names = $2
- when /^find_by_([_a-zA-Z]\w*)\!$/
- bang = true
- names = $1
- when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
- instantiator = $1 == 'initialize' ? :new : :create
- names = $2
- else
- return nil
+ method = method.to_s
+ klass = [FindBy, FindByBang, FindOrInitializeCreateBy].find do |_klass|
+ _klass.matches?(method)
end
+ klass.new(method) if klass
+ end
- new(finder, instantiator, bang, names.split('_and_'))
+ def self.matches?(method)
+ method =~ self::METHOD_PATTERN
end
- def initialize(finder, instantiator, bang, attribute_names)
- @finder = finder
- @instantiator = instantiator
- @bang = bang
- @attribute_names = attribute_names
+ def initialize(method)
+ @finder = :first
+ @instantiator = nil
+ match_data = method.match(self.class::METHOD_PATTERN)
+ @attribute_names = match_data[-1].split("_and_")
+ initialize_from_match_data(match_data)
end
attr_reader :finder, :attribute_names, :instantiator
@@ -41,16 +31,54 @@ module ActiveRecord
@finder && !@instantiator
end
+ def creator?
+ @finder == :first && @instantiator == :create
+ end
+
def instantiator?
- @finder == :first && @instantiator
+ @instantiator
end
- def creator?
- @finder == :first && @instantiator == :create
+ def bang?
+ false
+ end
+
+ def valid_arguments?(arguments)
+ arguments.size >= @attribute_names.size
end
+ private
+
+ def initialize_from_match_data(match_data)
+ end
+ end
+
+ class FindBy < DynamicFinderMatch
+ METHOD_PATTERN = /^find_(all_|last_)?by_([_a-zA-Z]\w*)$/
+
+ def initialize_from_match_data(match_data)
+ @finder = :last if match_data[1] == 'last_'
+ @finder = :all if match_data[1] == 'all_'
+ end
+ end
+
+ class FindByBang < DynamicFinderMatch
+ METHOD_PATTERN = /^find_by_([_a-zA-Z]\w*)\!$/
+
def bang?
- @bang
+ true
+ end
+ end
+
+ class FindOrInitializeCreateBy < DynamicFinderMatch
+ METHOD_PATTERN = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
+
+ def initialize_from_match_data(match_data)
+ @instantiator = match_data[1] == 'initialize' ? :new : :create
+ end
+
+ def valid_arguments?(arguments)
+ arguments.size == 1 && arguments.first.is_a?(Hash) || super
end
end
end
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index e9068089f0..60ce3dd4f1 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -25,7 +25,7 @@ module ActiveRecord
if match = (DynamicFinderMatch.match(method_id) || DynamicScopeMatch.match(method_id))
attribute_names = match.attribute_names
super unless all_attributes_exists?(attribute_names)
- if arguments.size < attribute_names.size
+ unless match.valid_arguments?(arguments)
method_trace = "#{__FILE__}:#{__LINE__}:in `#{method_id}'"
backtrace = [method_trace] + caller
raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{attribute_names.size})", backtrace
diff --git a/activerecord/lib/active_record/dynamic_scope_match.rb b/activerecord/lib/active_record/dynamic_scope_match.rb
index c832e927d6..a502155aac 100644
--- a/activerecord/lib/active_record/dynamic_scope_match.rb
+++ b/activerecord/lib/active_record/dynamic_scope_match.rb
@@ -19,5 +19,9 @@ module ActiveRecord
attr_reader :scope, :attribute_names
alias :scope? :scope
+
+ def valid_arguments?(arguments)
+ arguments.size >= @attribute_names.size
+ end
end
end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index e502d7e52b..01cacf6153 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -19,6 +19,8 @@ module ActiveRecord
# currently collected. A false value indicates collecting is turned
# off. Otherwise it is an array of queries.
def logging_query_plan # :nodoc:
+ return yield unless logger
+
threshold = auto_explain_threshold_in_seconds
current = Thread.current
if threshold && current[:available_queries_for_explain].nil?
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index 44328f63b6..8892f7ef2f 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -10,6 +10,7 @@ en:
messages:
taken: "has already been taken"
record_invalid: "Validation failed: %{errors}"
+ restrict_dependent_destroy: "Cannot delete record because dependent %{model} exists"
# Append your own errors here or at the model/attributes scope.
# You can define own errors for models or model attributes.
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 4e27293cb4..96b62fdd61 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -8,11 +8,14 @@ module ActiveRecord
# * add_index
# * add_timestamps
# * create_table
+ # * create_join_table
# * remove_timestamps
# * rename_column
# * rename_index
# * rename_table
class CommandRecorder
+ include JoinTable
+
attr_accessor :commands, :delegate
def initialize(delegate = nil)
@@ -48,7 +51,7 @@ module ActiveRecord
super || delegate.respond_to?(*args)
end
- [:create_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method|
+ [:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method|
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{method}(*args) # def create_table(*args)
record(:"#{method}", args) # record(:create_table, args)
@@ -62,6 +65,12 @@ module ActiveRecord
[:drop_table, [args.first]]
end
+ def invert_create_join_table(args)
+ table_name = find_join_table_name(*args)
+
+ [:drop_table, [table_name]]
+ end
+
def invert_rename_table(args)
[:rename_table, args.reverse]
end
@@ -99,7 +108,6 @@ module ActiveRecord
rescue NoMethodError => e
raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@delegate}")
end
-
end
end
end
diff --git a/activerecord/lib/active_record/migration/join_table.rb b/activerecord/lib/active_record/migration/join_table.rb
new file mode 100644
index 0000000000..01a580781b
--- /dev/null
+++ b/activerecord/lib/active_record/migration/join_table.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ class Migration
+ module JoinTable #:nodoc:
+ private
+
+ def find_join_table_name(table_1, table_2, options = {})
+ options.delete(:table_name) { join_table_name(table_1, table_2) }
+ end
+
+ def join_table_name(table_1, table_2)
+ tables_names = [table_1, table_2].map(&:to_s).sort
+
+ tables_names.join("_").to_sym
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 261f6fa974..61f82af0c3 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -115,7 +115,7 @@ module ActiveRecord
# the documentation for ActiveRecord::Base#table_name.
def table_name=(value)
@original_table_name = @table_name if defined?(@table_name)
- @table_name = value
+ @table_name = value && value.to_s
@quoted_table_name = nil
@arel_table = nil
@relation = Relation.new(self, arel_table)
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
new file mode 100644
index 0000000000..60c37ac2b7
--- /dev/null
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+
+module ActiveRecord
+ # = Active Record Null Relation
+ class NullRelation < Relation
+ def exec_queries
+ @records = []
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 94e34e1bd4..5945b05190 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -8,7 +8,7 @@ module ActiveRecord
delegate :find_each, :find_in_batches, :to => :scoped
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
:where, :preload, :eager_load, :includes, :from, :lock, :readonly,
- :having, :create_with, :uniq, :references, :to => :scoped
+ :having, :create_with, :uniq, :references, :none, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
@@ -35,7 +35,8 @@ module ActiveRecord
# > [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
def find_by_sql(sql, binds = [])
logging_query_plan do
- connection.select_all(sanitize_sql(sql), "#{name} Load", binds).collect! { |record| instantiate(record) }
+ result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
+ result_set.map { |record| instantiate(record) }
end
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index bf9b4bf1c9..6bf3050af9 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -333,7 +333,7 @@ module ActiveRecord
def select_for_count
if @select_values.present?
select = @select_values.join(", ")
- select if select !~ /(,|\*)/
+ select if select !~ /[,*]/
end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index a8ae7208fc..6a28d7b155 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -196,6 +196,39 @@ module ActiveRecord
relation
end
+ # Returns a chainable relation with zero records, specifically an
+ # instance of the NullRelation class.
+ #
+ # The returned NullRelation inherits from Relation and implements the
+ # Null Object pattern so it is an object with defined null behavior:
+ # it always returns an empty array of records and avoids any database query.
+ #
+ # Any subsequent condition chained to the returned relation will continue
+ # generating an empty relation and will not fire any query to the database.
+ #
+ # Used in cases where is needed a method or a scope that could return zero
+ # results but the response has to be chainable.
+ #
+ # For example:
+ #
+ # @posts = current_user.visible_posts.where(:name => params[:name])
+ # # => the visible_post method response has to be a chainable Relation
+ #
+ # def visible_posts
+ # case role
+ # if 'Country Manager'
+ # Post.where(:country => country)
+ # if 'Reviewer'
+ # Post.published
+ # if 'Bad User'
+ # Post.none # => returning [] instead breaks the previous code
+ # end
+ # end
+ #
+ def none
+ NullRelation.new(@klass, @table)
+ end
+
def readonly(value = true)
relation = clone
relation.readonly_value = value
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 9ceab2eabc..60a2e90e23 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -24,6 +24,31 @@ module ActiveRecord
hash_rows
end
+ alias :map! :map
+ alias :collect! :map
+
+ def empty?
+ rows.empty?
+ end
+
+ def to_ary
+ hash_rows
+ end
+
+ def [](idx)
+ hash_rows[idx]
+ end
+
+ def last
+ hash_rows.last
+ end
+
+ def initialize_copy(other)
+ @columns = columns.dup
+ @rows = rows.dup
+ @hash_rows = nil
+ end
+
private
def hash_rows
@hash_rows ||= @rows.map { |row|
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 3a741ba600..7fd5d76ba5 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -57,8 +57,7 @@ module ActiveRecord
def build_relation(klass, table, attribute, value) #:nodoc:
reflection = klass.reflect_on_association(attribute)
- column = nil
- if(reflection)
+ if reflection
column = klass.columns_hash[reflection.foreign_key]
attribute = reflection.foreign_key
value = value.attributes[reflection.primary_key_column.name]