aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md59
-rwxr-xr-xactiverecord/Rakefile16
-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
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb66
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb43
-rw-r--r--activerecord/test/cases/base_test.rb10
-rw-r--r--activerecord/test/cases/explain_test.rb16
-rw-r--r--activerecord/test/cases/finder_test.rb11
-rw-r--r--activerecord/test/cases/fixtures_test.rb4
-rw-r--r--activerecord/test/cases/helper.rb3
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb1
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb12
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb70
-rw-r--r--activerecord/test/cases/primary_keys_test.rb16
-rw-r--r--activerecord/test/cases/reflection_test.rb4
-rw-r--r--activerecord/test/cases/relations_test.rb13
-rw-r--r--activerecord/test/cases/serialization_test.rb9
-rw-r--r--activerecord/test/fixtures/developers.yml2
-rw-r--r--activerecord/test/models/company.rb1
-rw-r--r--activerecord/test/schema/schema.rb2
49 files changed, 602 insertions, 115 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index a458f9e6e4..69cf1193b6 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,51 @@
## Rails 4.0.0 (unreleased) ##
+* Implemented ActiveRecord::Relation#none method
+
+ The `none` method returns a chainable relation with zero records
+ (an instance of the NullRelation class).
+
+ Any subsequent condition chained to the returned relation will continue
+ generating an empty relation and will not fire any query to the database.
+
+ *Juanjo Bazán*
+
+* Added the `ActiveRecord::NullRelation` class implementing the null
+ object pattern for the Relation class. *Juanjo Bazán*
+
+* Added deprecation for the `:dependent => :restrict` association option.
+
+ Please note:
+
+ * Up until now `has_many` and `has_one`, `:dependent => :restrict`
+ option raised a `DeleteRestrictionError` at the time of destroying
+ the object. 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.
+
+ * New rails application would be generated with the
+ `config.active_record.dependent_restrict_raises = false` in the
+ application config.
+
+ *Manoj Kumar*
+
+* Added `create_join_table` migration helper to create HABTM join tables
+
+ create_join_table :products, :categories
+ # =>
+ # create_table :categories_products, :id => false do |td|
+ # td.integer :product_id, :null => false
+ # td.integer :category_id, :null => false
+ # end
+
+ *Rafael Mendonça França*
+
+* The primary key is always initialized in the @attributes hash to nil (unless
+ another value has been specified).
+
* In previous releases, the following would generate a single query with
an `OUTER JOIN comments`, rather than two separate queries:
@@ -70,7 +116,16 @@
* PostgreSQL hstore types are automatically deserialized from the database.
-## Rails 3.2.0 (unreleased) ##
+
+## Rails 3.2.1 (unreleased) ##
+
+* The threshold for auto EXPLAIN is ignored if there's no logger. *fxn*
+
+* Fix possible race condition when two threads try to define attribute
+ methods for the same class.
+
+
+## Rails 3.2.0 (January 20, 2012) ##
* Added a `with_lock` method to ActiveRecord objects, which starts
a transaction, locks the object (pessimistically) and yields to the block.
@@ -196,7 +251,7 @@
Client.select(:name).uniq
- This also allows you to revert the unqueness in a relation:
+ This also allows you to revert the uniqueness in a relation:
Client.select(:name).uniq.uniq(false)
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index d769a73dba..98020ad3ab 100755
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -187,15 +187,15 @@ end
task :lines do
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
- for file_name in FileList["lib/active_record/**/*.rb"]
+ FileList["lib/active_record/**/*.rb"].each do |file_name|
next if file_name =~ /vendor/
- f = File.open(file_name)
-
- while line = f.gets
- lines += 1
- next if line =~ /^\s*$/
- next if line =~ /^\s*#/
- codelines += 1
+ File.open(file_name, 'r') do |f|
+ while line = f.gets
+ lines += 1
+ next if line =~ /^\s*$/
+ next if line =~ /^\s*#/
+ codelines += 1
+ end
end
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
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]
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index f1a341437f..ab1e821aab 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1140,16 +1140,42 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_nil companies(:leetsoft).reload.client_of
assert_nil companies(:jadedpixel).reload.client_of
-
assert_equal num_accounts, Account.count
end
def test_restrict
- firm = RestrictedFirm.new(:name => 'restrict')
- firm.save!
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = true
+
+ firm = RestrictedFirm.create!(:name => 'restrict')
firm.companies.create(:name => 'child')
+
assert !firm.companies.empty?
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
+ assert RestrictedFirm.exists?(:name => 'restrict')
+ assert firm.companies.exists?(:name => 'child')
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ end
+
+ def test_restrict_when_dependent_restrict_raises_config_set_to_false
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = false
+
+ firm = RestrictedFirm.create!(:name => 'restrict')
+ firm.companies.create(:name => 'child')
+
+ assert !firm.companies.empty?
+
+ firm.destroy
+
+ assert !firm.errors.empty?
+
+ assert_equal "Cannot delete record because dependent company exists", firm.errors[:base].first
+ assert RestrictedFirm.exists?(:name => 'restrict')
+ assert firm.companies.exists?(:name => 'child')
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
end
def test_included_in_collection
@@ -1253,6 +1279,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert company.clients_using_sql.loaded?
end
+ def test_get_ids_for_ordered_association
+ assert_equal [companies(:second_client).id, companies(:first_client).id], companies(:first_firm).clients_ordered_by_name_ids
+ end
+
def test_assign_ids_ignoring_blanks
firm = Firm.create!(:name => 'Apple')
firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, '']
@@ -1401,29 +1431,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients.last
end
end
-
+
def test_custom_primary_key_on_new_record_should_fetch_with_query
author = Author.new(:name => "David")
assert !author.essays.loaded?
-
- assert_queries 1 do
+
+ assert_queries 1 do
assert_equal 1, author.essays.size
end
-
+
assert_equal author.essays, Essay.find_all_by_writer_id("David")
-
+
end
-
+
def test_has_many_custom_primary_key
david = authors(:david)
assert_equal david.essays, Essay.find_all_by_writer_id("David")
end
-
+
def test_blank_custom_primary_key_on_new_record_should_not_run_queries
author = Author.new
assert !author.essays.loaded?
-
- assert_queries 0 do
+
+ assert_queries 0 do
assert_equal 0, author.essays.size
end
end
@@ -1649,4 +1679,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [bulb2], car.bulbs
assert_equal [bulb2], car.reload.bulbs
end
+
+ def test_building_has_many_association_with_restrict_dependency
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = true
+
+ klass = Class.new(ActiveRecord::Base)
+
+ assert_deprecated { klass.has_many :companies, :dependent => :restrict }
+ assert_not_deprecated { klass.has_many :companies }
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 4612bc2618..9cc09194dc 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -738,7 +738,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_select_chosen_fields_only
author = authors(:david)
- assert_equal ['body'], author.comments.select('comments.body').first.attributes.keys
+ assert_equal ['body', 'id'].sort, author.comments.select('comments.body').first.attributes.keys.sort
end
def test_get_has_many_through_belongs_to_ids_with_conditions
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 26931e3e85..37be6a279d 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -157,11 +157,38 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
def test_dependence_with_restrict
- firm = RestrictedFirm.new(:name => 'restrict')
- firm.save!
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = true
+
+ firm = RestrictedFirm.create!(:name => 'restrict')
firm.create_account(:credit_limit => 10)
+
assert_not_nil firm.account
+
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
+ assert RestrictedFirm.exists?(:name => 'restrict')
+ assert firm.account.present?
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ end
+
+ def test_dependence_with_restrict_with_dependent_restrict_raises_config_set_to_false
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = false
+
+ firm = RestrictedFirm.create!(:name => 'restrict')
+ firm.create_account(:credit_limit => 10)
+
+ assert_not_nil firm.account
+
+ firm.destroy
+
+ assert !firm.errors.empty?
+ assert_equal "Cannot delete record because dependent account exists", firm.errors[:base].first
+ assert RestrictedFirm.exists?(:name => 'restrict')
+ assert firm.account.present?
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
end
def test_successful_build_association
@@ -456,4 +483,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal car.id, bulb.attributes_after_initialize['car_id']
end
+
+ def test_building_has_one_association_with_dependent_restrict
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = true
+
+ klass = Class.new(ActiveRecord::Base)
+
+ assert_deprecated { klass.has_one :account, :dependent => :restrict }
+ assert_not_deprecated { klass.has_one :account }
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 0ea1b824e1..6c3e4fc6d0 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1104,7 +1104,10 @@ class BasicsTest < ActiveRecord::TestCase
# TODO: extend defaults tests to other databases!
if current_adapter?(:PostgreSQLAdapter)
def test_default
+ tz = Default.default_timezone
+ Default.default_timezone = :local
default = Default.new
+ Default.default_timezone = tz
# fixed dates / times
assert_equal Date.new(2004, 1, 1), default.fixed_date
@@ -1446,6 +1449,11 @@ class BasicsTest < ActiveRecord::TestCase
end
end
+ def test_set_table_name_symbol_converted_to_string
+ Joke.table_name = :cold_jokes
+ assert_equal 'cold_jokes', Joke.table_name
+ end
+
def test_quoted_table_name_after_set_table_name
klass = Class.new(ActiveRecord::Base)
@@ -1574,7 +1582,7 @@ class BasicsTest < ActiveRecord::TestCase
Developer.find(:all)
end
assert developers.size >= 2
- for i in 1...developers.size
+ (1...developers.size).each do |i|
assert developers[i-1].salary >= developers[i].salary
end
end
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index 6ae6f83446..83c9b6e107 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -14,7 +14,7 @@ if ActiveRecord::Base.connection.supports_explain?
base.connection
end
- def test_logging_query_plan
+ def test_logging_query_plan_with_logger
base.logger.expects(:warn).with do |message|
message.starts_with?('EXPLAIN for:')
end
@@ -24,6 +24,20 @@ if ActiveRecord::Base.connection.supports_explain?
end
end
+ def test_logging_query_plan_without_logger
+ original = base.logger
+ base.logger = nil
+
+ base.logger.expects(:warn).never
+
+ with_threshold(0) do
+ car = Car.where(:name => 'honda').first
+ assert_equal 'honda', car.name
+ end
+ ensure
+ base.logger = original
+ end
+
def test_collect_queries_for_explain
base.auto_explain_threshold_in_seconds = nil
queries = Thread.current[:available_queries_for_explain] = []
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 7d80a56858..76c041397a 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -881,6 +881,17 @@ class FinderTest < ActiveRecord::TestCase
assert_equal 23, sig38.client_of
end
+ def test_find_or_create_from_two_attributes_and_hash
+ number_of_companies = Company.count
+ sig38 = Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert sig38.persisted?
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ assert_equal 23, sig38.client_of
+ end
+
def test_find_or_create_from_one_aggregate_attribute
number_of_customers = Customer.count
created_customer = Customer.find_or_create_by_balance(Money.new(123))
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 5c67cbfcce..562b370c97 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -782,7 +782,7 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase
end
def test_table_name_is_defined_in_the_model
- assert_equal :randomly_named_table, ActiveRecord::Fixtures::all_loaded_fixtures["admin/randomly_named_a9"].table_name
- assert_equal :randomly_named_table, Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name
+ assert_equal 'randomly_named_table', ActiveRecord::Fixtures::all_loaded_fixtures["admin/randomly_named_a9"].table_name
+ assert_equal 'randomly_named_table', Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name
end
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index b4598ab32a..359cabd016 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -22,6 +22,9 @@ ActiveSupport::Deprecation.debug = true
# Enable Identity Map only when ENV['IM'] is set to "true"
ActiveRecord::IdentityMap.enabled = (ENV['IM'] == "true")
+# Avoid deprecation warning setting dependent_restric_raises to false. The default is true
+ActiveRecord::Base.dependent_restrict_raises = false
+
# Connect to the database
ARTest.connect
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index a1ade59e52..f0b1f74bd3 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -173,7 +173,6 @@ module ActiveRecord
skip "not supported on #{connection.class}"
end
-
connection.create_table :testings do |t|
t.column :foo, :string
end
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 8f136bce2b..7d026961be 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -73,6 +73,18 @@ module ActiveRecord
assert_equal [:drop_table, [:people_reminders]], drop_table
end
+ def test_invert_create_join_table
+ @recorder.record :create_join_table, [:musics, :artists]
+ drop_table = @recorder.inverse.first
+ assert_equal [:drop_table, [:artists_musics]], drop_table
+ end
+
+ def test_invert_create_join_table_with_table_name
+ @recorder.record :create_join_table, [:musics, :artists, {:table_name => :catalog}]
+ drop_table = @recorder.inverse.first
+ assert_equal [:drop_table, [:catalog]], drop_table
+ end
+
def test_invert_rename_table
@recorder.record :rename_table, [:old, :new]
rename = @recorder.inverse.first
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
new file mode 100644
index 0000000000..0428d9ba76
--- /dev/null
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -0,0 +1,70 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class CreateJoinTableTest < ActiveRecord::TestCase
+ attr_reader :connection
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_create_join_table
+ connection.create_join_table :artists, :musics
+
+ assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort
+ ensure
+ connection.drop_table :artists_musics
+ end
+
+ def test_create_join_table_set_not_null_by_default
+ connection.create_join_table :artists, :musics
+
+ assert_equal [false, false], connection.columns(:artists_musics).map(&:null)
+ ensure
+ connection.drop_table :artists_musics
+ end
+
+ def test_create_join_table_with_strings
+ connection.create_join_table 'artists', 'musics'
+
+ assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort
+ ensure
+ connection.drop_table :artists_musics
+ end
+
+ def test_create_join_table_with_the_proper_order
+ connection.create_join_table :videos, :musics
+
+ assert_equal %w(music_id video_id), connection.columns(:musics_videos).map(&:name).sort
+ ensure
+ connection.drop_table :musics_videos
+ end
+
+ def test_create_join_table_with_the_table_name
+ connection.create_join_table :artists, :musics, :table_name => :catalog
+
+ assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort
+ ensure
+ connection.drop_table :catalog
+ end
+
+ def test_create_join_table_with_the_table_name_as_string
+ connection.create_join_table :artists, :musics, :table_name => 'catalog'
+
+ assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort
+ ensure
+ connection.drop_table :catalog
+ end
+
+ def test_create_join_table_with_column_options
+ connection.create_join_table :artists, :musics, :column_options => {:null => true}
+
+ assert_equal [true, true], connection.columns(:artists_musics).map(&:null)
+ ensure
+ connection.drop_table :artists_musics
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index b4d1a631fa..e6e50a4cd4 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -200,3 +200,19 @@ class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase
assert_equal 'foo', model.primary_key
end
end
+
+if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ def test_primaery_key_method_with_ansi_quotes
+ con = ActiveRecord::Base.connection
+ con.execute("SET SESSION sql_mode='ANSI_QUOTES'")
+ assert_equal "id", con.primary_key("topics")
+ ensure
+ con.reconnect!
+ end
+
+ end
+end
+
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 297fb56570..16f05f2198 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -189,8 +189,8 @@ class ReflectionTest < ActiveRecord::TestCase
def test_reflection_of_all_associations
# FIXME these assertions bust a lot
- assert_equal 38, Firm.reflect_on_all_associations.size
- assert_equal 28, Firm.reflect_on_all_associations(:has_many).size
+ assert_equal 39, Firm.reflect_on_all_associations.size
+ assert_equal 29, Firm.reflect_on_all_associations(:has_many).size
assert_equal 10, Firm.reflect_on_all_associations(:has_one).size
assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 5e19465253..0471d03f3b 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -215,6 +215,19 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [2, 4, 6, 8, 10], even_ids.sort
end
+ def test_none
+ assert_no_queries do
+ assert_equal [], Developer.none
+ assert_equal [], Developer.scoped.none
+ end
+ end
+
+ def test_none_chainable
+ assert_no_queries do
+ assert_equal [], Developer.none.where(:name => 'David')
+ end
+ end
+
def test_joins_with_nil_argument
assert_nothing_raised { DependentFirm.joins(nil).first }
end
diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb
index 61b04b3e37..a4c065e667 100644
--- a/activerecord/test/cases/serialization_test.rb
+++ b/activerecord/test/cases/serialization_test.rb
@@ -13,7 +13,8 @@ class SerializationTest < ActiveRecord::TestCase
:created_at => Time.utc(2006, 8, 1),
:awesome => false,
:preferences => { :gem => '<strong>ruby</strong>' },
- :alternative_id => nil
+ :alternative_id => nil,
+ :id => nil
}
end
@@ -24,7 +25,7 @@ class SerializationTest < ActiveRecord::TestCase
end
def test_serialize_should_be_reversible
- for format in FORMATS
+ FORMATS.each do |format|
@serialized = Contact.new.send("to_#{format}")
contact = Contact.new.send("from_#{format}", @serialized)
@@ -33,7 +34,7 @@ class SerializationTest < ActiveRecord::TestCase
end
def test_serialize_should_allow_attribute_only_filtering
- for format in FORMATS
+ FORMATS.each do |format|
@serialized = Contact.new(@contact_attributes).send("to_#{format}", :only => [ :age, :name ])
contact = Contact.new.send("from_#{format}", @serialized)
assert_equal @contact_attributes[:name], contact.name, "For #{format}"
@@ -42,7 +43,7 @@ class SerializationTest < ActiveRecord::TestCase
end
def test_serialize_should_allow_attribute_except_filtering
- for format in FORMATS
+ FORMATS.each do |format|
@serialized = Contact.new(@contact_attributes).send("to_#{format}", :except => [ :age, :name ])
contact = Contact.new.send("from_#{format}", @serialized)
assert_nil contact.name, "For #{format}"
diff --git a/activerecord/test/fixtures/developers.yml b/activerecord/test/fixtures/developers.yml
index 308bf75de2..3656564f63 100644
--- a/activerecord/test/fixtures/developers.yml
+++ b/activerecord/test/fixtures/developers.yml
@@ -8,7 +8,7 @@ jamis:
name: Jamis
salary: 150000
-<% for digit in 3..10 %>
+<% (3..10).each do |digit| %>
dev_<%= digit %>:
id: <%= digit %>
name: fixture_<%= digit %>
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index d1a8a82786..fbdfaa2c29 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -45,6 +45,7 @@ class Firm < Company
has_many :unsorted_clients_with_symbol, :class_name => :Client
has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC"
has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id"
+ has_many :clients_ordered_by_name, :order => "name", :class_name => "Client"
has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false
has_many :dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :destroy
has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index e2cd7ce9e4..f4226d4720 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -724,8 +724,6 @@ ActiveRecord::Schema.define do
create_table :countries_treaties, :force => true, :id => false do |t|
t.string :country_id, :null => false
t.string :treaty_id, :null => false
- t.datetime :created_at
- t.datetime :updated_at
end
create_table :liquid, :force => true do |t|