aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md48
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb30
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_helper.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb4
-rw-r--r--activerecord/lib/active_record/errors.rb3
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb7
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb16
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb44
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb38
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb5
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb12
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb12
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb4
-rw-r--r--activerecord/test/cases/calculations_test.rb29
-rw-r--r--activerecord/test/cases/database_tasks_test.rb170
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb10
-rw-r--r--activerecord/test/cases/json_serialization_test.rb47
-rw-r--r--activerecord/test/cases/relations_test.rb27
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb73
-rw-r--r--activerecord/test/fixtures/reserved_words/distinct_select.yml (renamed from activerecord/test/fixtures/reserved_words/distincts_selects.yml)6
-rw-r--r--activerecord/test/models/contact.rb49
-rw-r--r--activerecord/test/models/member.rb5
-rw-r--r--activerecord/test/schema/schema.rb5
27 files changed, 389 insertions, 265 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 10bfe1945a..ed5f29cfe4 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,53 @@
## Rails 4.0.0 (unreleased) ##
+* Allow ActiveRecord::Relation#pluck to accept multiple columns. Returns an
+ array of arrays containing the typecasted values:
+
+ Person.pluck(:id, :name)
+ # SELECT people.id, people.name FROM people
+ # [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
+
+ *Jeroen van Ingen & Carlos Antonio da Silva*
+
+* Improve the derivation of HABTM join table name to take account of nesting.
+ It now takes the table names of the two models, sorts them lexically and
+ then joins them, stripping any common prefix from the second table name.
+
+ Some examples:
+
+ Top level models (Category <=> Product)
+ Old: categories_products
+ New: categories_products
+
+ Top level models with a global table_name_prefix (Category <=> Product)
+ Old: site_categories_products
+ New: site_categories_products
+
+ Nested models in a module without a table_name_prefix method (Admin::Category <=> Admin::Product)
+ Old: categories_products
+ New: categories_products
+
+ Nested models in a module with a table_name_prefix method (Admin::Category <=> Admin::Product)
+ Old: categories_products
+ New: admin_categories_products
+
+ Nested models in a parent model (Catalog::Category <=> Catalog::Product)
+ Old: categories_products
+ New: catalog_categories_products
+
+ Nested models in different parent models (Catalog::Category <=> Content::Page)
+ Old: categories_pages
+ New: catalog_categories_content_pages
+
+ *Andrew White*
+
+* Move HABTM validity checks to ActiveRecord::Reflection. One side effect of
+ this is to move when the exceptions are raised from the point of declaration
+ to when the association is built. This is consistant with other association
+ validity checks.
+
+ *Andrew White*
+
* Added `stored_attributes` hash which contains the attributes stored using
ActiveRecord::Store. This allows you to retrieve the list of attributes
you've defined.
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 30fc44b4c2..f7656ecd47 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -6,7 +6,6 @@ module ActiveRecord::Associations::Builder
def build
reflection = super
- check_validity(reflection)
define_destroy_hook
reflection
end
@@ -24,34 +23,5 @@ module ActiveRecord::Associations::Builder
RUBY
})
end
-
- # TODO: These checks should probably be moved into the Reflection, and we should not be
- # redefining the options[:join_table] value - instead we should define a
- # reflection.join_table method.
- def check_validity(reflection)
- if reflection.association_foreign_key == reflection.foreign_key
- raise ActiveRecord::HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
- end
-
- reflection.options[:join_table] ||= join_table_name(
- model.send(:undecorated_table_name, model.to_s),
- model.send(:undecorated_table_name, reflection.class_name)
- )
- end
-
- # Generates a join table name from two provided table names.
- # The names in the join table names end up in lexicographic order.
- #
- # join_table_name("members", "clubs") # => "clubs_members"
- # join_table_name("members", "special_clubs") # => "members_special_clubs"
- def join_table_name(first_table_name, second_table_name)
- if first_table_name < second_table_name
- join_table = "#{first_table_name}_#{second_table_name}"
- else
- join_table = "#{second_table_name}_#{first_table_name}"
- end
-
- model.table_name_prefix + join_table + model.table_name_suffix
- end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 2447914640..2f6ddfeeb3 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -186,7 +186,7 @@ module ActiveRecord
if options[:uniq]
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
column_name ||= reflection.klass.primary_key
- count_options.merge!(:distinct => true)
+ count_options[:distinct] = true
end
value = scoped.count(column_name, count_options)
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index 58d041ec1d..93618721bb 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -5,7 +5,7 @@ module ActiveRecord
attr_reader :join_table
def initialize(owner, reflection)
- @join_table = Arel::Table.new(reflection.options[:join_table])
+ @join_table = Arel::Table.new(reflection.join_table)
super
end
diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb
index cea6ad6944..5a41b40c8f 100644
--- a/activerecord/lib/active_record/associations/join_helper.rb
+++ b/activerecord/lib/active_record/associations/join_helper.rb
@@ -19,7 +19,7 @@ module ActiveRecord
if reflection.source_macro == :has_and_belongs_to_many
tables << alias_tracker.aliased_table_for(
- (reflection.source_reflection || reflection).options[:join_table],
+ (reflection.source_reflection || reflection).join_table,
table_alias_for(reflection, true)
)
end
diff --git a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
index b77b667219..8e8925f0a9 100644
--- a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
@@ -6,7 +6,7 @@ module ActiveRecord
def initialize(klass, records, reflection, preload_options)
super
- @join_table = Arel::Table.new(options[:join_table]).alias('t0')
+ @join_table = Arel::Table.new(reflection.join_table).alias('t0')
end
# Unlike the other associations, we want to get a raw array of rows so that we can
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index a85b6b3b82..a24b4b7839 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -78,7 +78,7 @@ module ActiveRecord
def _field_changed?(attr, old, value)
if column = column_for_attribute(attr)
if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
- changes_from_zero_to_string?(column, old, value))
+ changes_from_zero_to_string?(old, value))
value = nil
else
value = column.type_cast(value)
@@ -96,7 +96,7 @@ module ActiveRecord
column.null && (old.nil? || old == 0) && value.blank?
end
- def changes_from_zero_to_string?(column, old, value)
+ def changes_from_zero_to_string?(old, value)
# For columns with old 0 and value non-empty string
old == 0 && value.present? && value != '0'
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 9b88bb8178..858b667e22 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -196,4 +196,7 @@ module ActiveRecord
"Unknown primary key for table #{model.table_name} in model #{model}."
end
end
+
+ class ImmutableRelation < ActiveRecordError
+ end
end
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index 1f8c4fc203..d5ba343b4c 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -2,9 +2,12 @@ require 'active_support/notifications'
module ActiveRecord
class ExplainSubscriber # :nodoc:
- def call(*args)
+ def start(name, id, payload)
+ # unused
+ end
+
+ def finish(name, id, payload)
if queries = Thread.current[:available_queries_for_explain]
- payload = args.last
queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload)
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 7e6512501c..96d24b72b3 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -594,7 +594,7 @@ module ActiveRecord
when :has_and_belongs_to_many
if (targets = row.delete(association.name.to_s))
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
- table_name = association.options[:join_table]
+ table_name = association.join_table
rows[table_name].concat targets.map { |target|
{ association.foreign_key => row[primary_key_name],
association.association_foreign_key => ActiveRecord::Fixtures.identify(target) }
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index e9a8e90538..0d9534acd6 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -161,6 +161,10 @@ module ActiveRecord
@quoted_table_name ||= klass.quoted_table_name
end
+ def join_table
+ @join_table ||= options[:join_table] || derive_join_table
+ end
+
def foreign_key
@foreign_key ||= options[:foreign_key] || derive_foreign_key
end
@@ -208,6 +212,10 @@ module ActiveRecord
def check_validity!
check_validity_of_inverse!
+
+ if has_and_belongs_to_many? && association_foreign_key == foreign_key
+ raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(self)
+ end
end
def check_validity_of_inverse!
@@ -290,6 +298,10 @@ module ActiveRecord
macro == :belongs_to
end
+ def has_and_belongs_to_many?
+ macro == :has_and_belongs_to_many
+ end
+
def association_class
case macro
when :belongs_to
@@ -332,6 +344,10 @@ module ActiveRecord
end
end
+ def derive_join_table
+ [active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
+ end
+
def primary_key(klass)
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 22c3e6a324..86eb8f35b5 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -107,7 +107,6 @@ module ActiveRecord
relation = with_default_scope
if relation.equal?(self)
-
if has_include?(column_name)
construct_relation_for_association_calculations.calculate(operation, column_name, options)
else
@@ -139,6 +138,10 @@ module ActiveRecord
# # SELECT people.id FROM people
# # => [1, 2, 3]
#
+ # Person.pluck(:id, :name)
+ # # SELECT people.id, people.name FROM people
+ # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
+ #
# Person.uniq.pluck(:role)
# # SELECT DISTINCT role FROM people
# # => ['admin', 'member', 'guest']
@@ -151,30 +154,35 @@ module ActiveRecord
# # SELECT DATEDIFF(updated_at, created_at) FROM people
# # => ['0', '27761', '173']
#
- def pluck(column_name)
- if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
- column_name = "#{table_name}.#{column_name}"
+ def pluck(*column_names)
+ column_names.map! do |column_name|
+ if column_name.is_a?(Symbol) && self.column_names.include?(column_name.to_s)
+ "#{table_name}.#{column_name}"
+ else
+ column_name
+ end
end
- if has_include?(column_name)
- construct_relation_for_association_calculations.pluck(column_name)
+ if has_include?(column_names.first)
+ construct_relation_for_association_calculations.pluck(*column_names)
else
- result = klass.connection.select_all(select(column_name).arel, nil, bind_values)
-
- key = result.columns.first
- column = klass.column_types.fetch(key) {
- result.column_types.fetch(key) {
- Class.new { def type_cast(v); v; end }.new
+ result = klass.connection.select_all(select(column_names).arel, nil, bind_values)
+ columns = result.columns.map do |key|
+ klass.column_types.fetch(key) {
+ result.column_types.fetch(key) {
+ Class.new { def type_cast(v); v; end }.new
+ }
}
- }
-
- result.map do |attributes|
- raise ArgumentError, "Pluck expects to select just one attribute: #{attributes.inspect}" unless attributes.one?
+ end
- value = klass.initialize_attributes(attributes).values.first
+ result = result.map do |attributes|
+ values = klass.initialize_attributes(attributes).values
- column.type_cast(value)
+ columns.zip(values).map do |column, value|
+ column.type_cast(value)
+ end
end
+ columns.one? ? result.map!(&:first) : result
end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 529ddb5e31..e394acfaf2 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -7,34 +7,36 @@ module ActiveRecord
Relation::MULTI_VALUE_METHODS.each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_values # def select_values
- @values[:#{name}] || [] # @values[:select] || []
- end # end
- #
- def #{name}_values=(values) # def select_values=(values)
- @values[:#{name}] = values # @values[:select] = values
- end # end
+ def #{name}_values # def select_values
+ @values[:#{name}] || [] # @values[:select] || []
+ end # end
+ #
+ def #{name}_values=(values) # def select_values=(values)
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
+ @values[:#{name}] = values # @values[:select] = values
+ end # end
CODE
end
(Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_value # def readonly_value
- @values[:#{name}] # @values[:readonly]
- end # end
- #
- def #{name}_value=(value) # def readonly_value=(value)
- @values[:#{name}] = value # @values[:readonly] = value
- end # end
+ def #{name}_value # def readonly_value
+ @values[:#{name}] # @values[:readonly]
+ end # end
CODE
end
- def create_with_value
- @values[:create_with] || {}
+ Relation::SINGLE_VALUE_METHODS.each do |name|
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}_value=(value) # def readonly_value=(value)
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
+ @values[:#{name}] = value # @values[:readonly] = value
+ end # end
+ CODE
end
- def create_with_value=(value)
- @values[:create_with] = value
+ def create_with_value
+ @values[:create_with] || {}
end
alias extensions extending_values
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index b833af64fe..834d01a1e8 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -177,11 +177,6 @@ module ActiveRecord #:nodoc:
end
class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
- def initialize(*args)
- super
- options[:except] = Array(options[:except]) | Array(@serializable.class.inheritance_column)
- end
-
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
def compute_type
klass = @serializable.class
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index 6faceaf7c0..8e3eeac81e 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -34,11 +34,11 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
'select'=>'id int auto_increment primary key',
'values'=>'id int auto_increment primary key, group_id int',
'distinct'=>'id int auto_increment primary key',
- 'distincts_selects'=>'distinct_id int, select_id int'
+ 'distinct_select'=>'distinct_id int, select_id int'
end
def teardown
- drop_tables_directly ['group', 'select', 'values', 'distinct', 'distincts_selects', 'order']
+ drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order']
end
# create tables with reserved-word names and columns
@@ -80,7 +80,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
#activerecord model class with reserved-word table name
def test_activerecord_model
- create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
x = nil
assert_nothing_raised { x = Group.new }
x.order = 'x'
@@ -94,7 +94,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# has_one association with reserved-word table name
def test_has_one_associations
- create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
v = nil
assert_nothing_raised { v = Group.find(1).values }
assert_equal 2, v.id
@@ -102,7 +102,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# belongs_to association with reserved-word table name
def test_belongs_to_associations
- create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
gs = nil
assert_nothing_raised { gs = Select.find(2).groups }
assert_equal gs.length, 2
@@ -111,7 +111,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# has_and_belongs_to_many with reserved-word table name
def test_has_and_belongs_to_many
- create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
s = nil
assert_nothing_raised { s = Distinct.find(1).selects }
assert_equal s.length, 2
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 32d4282623..5c2c113c78 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -34,11 +34,11 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
'select'=>'id int auto_increment primary key',
'values'=>'id int auto_increment primary key, group_id int',
'distinct'=>'id int auto_increment primary key',
- 'distincts_selects'=>'distinct_id int, select_id int'
+ 'distinct_select'=>'distinct_id int, select_id int'
end
def teardown
- drop_tables_directly ['group', 'select', 'values', 'distinct', 'distincts_selects', 'order']
+ drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order']
end
# create tables with reserved-word names and columns
@@ -80,7 +80,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
#activerecord model class with reserved-word table name
def test_activerecord_model
- create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
x = nil
assert_nothing_raised { x = Group.new }
x.order = 'x'
@@ -94,7 +94,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# has_one association with reserved-word table name
def test_has_one_associations
- create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
v = nil
assert_nothing_raised { v = Group.find(1).values }
assert_equal 2, v.id
@@ -102,7 +102,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# belongs_to association with reserved-word table name
def test_belongs_to_associations
- create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
gs = nil
assert_nothing_raised { gs = Select.find(2).groups }
assert_equal gs.length, 2
@@ -111,7 +111,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# has_and_belongs_to_many with reserved-word table name
def test_has_and_belongs_to_many
- create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
s = nil
assert_nothing_raised { s = Distinct.find(1).selects }
assert_equal s.length, 2
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 9d693bae0c..24bb4adf0a 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -773,9 +773,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_self_referential_habtm_without_foreign_key_set_should_raise_exception
assert_raise(ActiveRecord::HasAndBelongsToManyAssociationForeignKeyNeeded) {
- Member.class_eval do
- has_and_belongs_to_many :friends, :class_name => "Member", :join_table => "member_friends"
- end
+ SelfMember.new.friends
}
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index f748b897ee..4df613488a 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -532,10 +532,6 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [50 + 53 + 55 + 60], Account.pluck('SUM(DISTINCT(credit_limit)) as credit_limit')
end
- def test_pluck_expects_a_single_selection
- assert_raise(ArgumentError) { Account.pluck 'id, credit_limit' }
- end
-
def test_plucks_with_ids
assert_equal Company.all.map(&:id).sort, Company.ids.sort
end
@@ -546,4 +542,29 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal Company.count, ids.length
assert_equal [7], ids.compact
end
+
+ def test_pluck_multiple_columns
+ assert_equal [
+ [1, "The First Topic"], [2, "The Second Topic of the day"],
+ [3, "The Third Topic of the day"], [4, "The Fourth Topic of the day"]
+ ], Topic.order(:id).pluck(:id, :title)
+ assert_equal [
+ [1, "The First Topic", "David"], [2, "The Second Topic of the day", "Mary"],
+ [3, "The Third Topic of the day", "Carl"], [4, "The Fourth Topic of the day", "Carl"]
+ ], Topic.order(:id).pluck(:id, :title, :author_name)
+ end
+
+ def test_pluck_with_multiple_columns_and_selection_clause
+ assert_equal [[1, 50], [2, 50], [3, 50], [4, 60], [5, 55], [6, 53]],
+ Account.pluck('id, credit_limit')
+ end
+
+ def test_pluck_with_multiple_columns_and_includes
+ Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+ companies_and_developers = Company.order('companies.id').includes(:contracts).pluck(:name, :developer_id)
+
+ assert_equal Company.count, companies_and_developers.length
+ assert_equal ["37signals", nil], companies_and_developers.first
+ assert_equal ["test", 7], companies_and_developers.last
+ end
end
diff --git a/activerecord/test/cases/database_tasks_test.rb b/activerecord/test/cases/database_tasks_test.rb
index bf38abc337..5f36b2c841 100644
--- a/activerecord/test/cases/database_tasks_test.rb
+++ b/activerecord/test/cases/database_tasks_test.rb
@@ -10,31 +10,21 @@ module ActiveRecord
end
end
+ ADAPTERS_TASKS = {
+ :mysql => :mysql_tasks,
+ :mysql2 => :mysql_tasks,
+ :postgresql => :postgresql_tasks,
+ :sqlite3 => :sqlite_tasks
+ }
+
class DatabaseTasksCreateTest < ActiveRecord::TestCase
include DatabaseTasksSetupper
- def test_mysql_create
- @mysql_tasks.expects(:create)
-
- ActiveRecord::Tasks::DatabaseTasks.create 'adapter' => 'mysql'
- end
-
- def test_mysql2_create
- @mysql_tasks.expects(:create)
-
- ActiveRecord::Tasks::DatabaseTasks.create 'adapter' => 'mysql2'
- end
-
- def test_postgresql_create
- @postgresql_tasks.expects(:create)
-
- ActiveRecord::Tasks::DatabaseTasks.create 'adapter' => 'postgresql'
- end
-
- def test_sqlite_create
- @sqlite_tasks.expects(:create)
-
- ActiveRecord::Tasks::DatabaseTasks.create 'adapter' => 'sqlite3'
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_create") do
+ eval("@#{v}").expects(:create)
+ ActiveRecord::Tasks::DatabaseTasks.create 'adapter' => k
+ end
end
end
@@ -141,28 +131,11 @@ module ActiveRecord
class DatabaseTasksDropTest < ActiveRecord::TestCase
include DatabaseTasksSetupper
- def test_mysql_create
- @mysql_tasks.expects(:drop)
-
- ActiveRecord::Tasks::DatabaseTasks.drop 'adapter' => 'mysql'
- end
-
- def test_mysql2_create
- @mysql_tasks.expects(:drop)
-
- ActiveRecord::Tasks::DatabaseTasks.drop 'adapter' => 'mysql2'
- end
-
- def test_postgresql_create
- @postgresql_tasks.expects(:drop)
-
- ActiveRecord::Tasks::DatabaseTasks.drop 'adapter' => 'postgresql'
- end
-
- def test_sqlite_create
- @sqlite_tasks.expects(:drop)
-
- ActiveRecord::Tasks::DatabaseTasks.drop 'adapter' => 'sqlite3'
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_drop") do
+ eval("@#{v}").expects(:drop)
+ ActiveRecord::Tasks::DatabaseTasks.drop 'adapter' => k
+ end
end
end
@@ -255,115 +228,48 @@ module ActiveRecord
end
end
+
class DatabaseTasksPurgeTest < ActiveRecord::TestCase
include DatabaseTasksSetupper
- def test_mysql_create
- @mysql_tasks.expects(:purge)
-
- ActiveRecord::Tasks::DatabaseTasks.purge 'adapter' => 'mysql'
- end
-
- def test_mysql2_create
- @mysql_tasks.expects(:purge)
-
- ActiveRecord::Tasks::DatabaseTasks.purge 'adapter' => 'mysql2'
- end
-
- def test_postgresql_create
- @postgresql_tasks.expects(:purge)
-
- ActiveRecord::Tasks::DatabaseTasks.purge 'adapter' => 'postgresql'
- end
-
- def test_sqlite_create
- @sqlite_tasks.expects(:purge)
-
- ActiveRecord::Tasks::DatabaseTasks.purge 'adapter' => 'sqlite3'
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_purge") do
+ eval("@#{v}").expects(:purge)
+ ActiveRecord::Tasks::DatabaseTasks.purge 'adapter' => k
+ end
end
end
class DatabaseTasksCharsetTest < ActiveRecord::TestCase
include DatabaseTasksSetupper
- def test_mysql_charset
- @mysql_tasks.expects(:charset)
-
- ActiveRecord::Tasks::DatabaseTasks.charset 'adapter' => 'mysql'
- end
-
- def test_mysql2_charset
- @mysql_tasks.expects(:charset)
-
- ActiveRecord::Tasks::DatabaseTasks.charset 'adapter' => 'mysql2'
- end
-
- def test_postgresql_charset
- @postgresql_tasks.expects(:charset)
-
- ActiveRecord::Tasks::DatabaseTasks.charset 'adapter' => 'postgresql'
- end
-
- def test_sqlite_charset
- @sqlite_tasks.expects(:charset)
-
- ActiveRecord::Tasks::DatabaseTasks.charset 'adapter' => 'sqlite3'
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_charset") do
+ eval("@#{v}").expects(:charset)
+ ActiveRecord::Tasks::DatabaseTasks.charset 'adapter' => k
+ end
end
end
class DatabaseTasksStructureDumpTest < ActiveRecord::TestCase
include DatabaseTasksSetupper
- def test_mysql_structure_dump
- @mysql_tasks.expects(:structure_dump).with("awesome-file.sql")
-
- ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => 'mysql'}, "awesome-file.sql")
- end
-
- def test_mysql2_structure_dump
- @mysql_tasks.expects(:structure_dump).with("awesome-file.sql")
-
- ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => 'mysql2'}, "awesome-file.sql")
- end
-
- def test_postgresql_structure_dump
- @postgresql_tasks.expects(:structure_dump).with("awesome-file.sql")
-
- ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => 'postgresql'}, "awesome-file.sql")
- end
-
- def test_sqlite_structure_dump
- @sqlite_tasks.expects(:structure_dump).with("awesome-file.sql")
-
- ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => 'sqlite3'}, "awesome-file.sql")
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_structure_dump") do
+ eval("@#{v}").expects(:structure_dump).with("awesome-file.sql")
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => k}, "awesome-file.sql")
+ end
end
end
class DatabaseTasksStructureLoadTest < ActiveRecord::TestCase
include DatabaseTasksSetupper
- def test_mysql_structure_load
- @mysql_tasks.expects(:structure_load).with("awesome-file.sql")
-
- ActiveRecord::Tasks::DatabaseTasks.structure_load({'adapter' => 'mysql'}, "awesome-file.sql")
- end
-
- def test_mysql2_structure_load
- @mysql_tasks.expects(:structure_load).with("awesome-file.sql")
-
- ActiveRecord::Tasks::DatabaseTasks.structure_load({'adapter' => 'mysql2'}, "awesome-file.sql")
- end
-
- def test_postgresql_structure_load
- @postgresql_tasks.expects(:structure_load).with("awesome-file.sql")
-
- ActiveRecord::Tasks::DatabaseTasks.structure_load({'adapter' => 'postgresql'}, "awesome-file.sql")
- end
-
- def test_sqlite_structure_load
- @sqlite_tasks.expects(:structure_load).with("awesome-file.sql")
-
- ActiveRecord::Tasks::DatabaseTasks.structure_load({'adapter' => 'sqlite3'}, "awesome-file.sql")
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_structure_load") do
+ eval("@#{v}").expects(:structure_load).with("awesome-file.sql")
+ ActiveRecord::Tasks::DatabaseTasks.structure_load({'adapter' => k}, "awesome-file.sql")
+ end
end
end
end
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index e118add44c..91e1df91cd 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -6,14 +6,14 @@ if ActiveRecord::Base.connection.supports_explain?
def test_collects_nothing_if_available_queries_for_explain_is_nil
with_queries(nil) do
- SUBSCRIBER.call
+ SUBSCRIBER.finish(nil, nil, {})
assert_nil Thread.current[:available_queries_for_explain]
end
end
def test_collects_nothing_if_the_payload_has_an_exception
with_queries([]) do |queries|
- SUBSCRIBER.call(:exception => Exception.new)
+ SUBSCRIBER.finish(nil, nil, :exception => Exception.new)
assert queries.empty?
end
end
@@ -21,7 +21,7 @@ if ActiveRecord::Base.connection.supports_explain?
def test_collects_nothing_for_ignored_payloads
with_queries([]) do |queries|
ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
- SUBSCRIBER.call(:name => ip)
+ SUBSCRIBER.finish(nil, nil, :name => ip)
end
assert queries.empty?
end
@@ -31,7 +31,7 @@ if ActiveRecord::Base.connection.supports_explain?
sql = 'select 1 from users'
binds = [1, 2]
with_queries([]) do |queries|
- SUBSCRIBER.call(:name => 'SQL', :sql => sql, :binds => binds)
+ SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => sql, :binds => binds)
assert_equal 1, queries.size
assert_equal sql, queries[0][0]
assert_equal binds, queries[0][1]
@@ -45,4 +45,4 @@ if ActiveRecord::Base.connection.supports_explain?
Thread.current[:available_queries_for_explain] = nil
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index d9e350abc0..a86b165c78 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -83,6 +83,53 @@ class JsonSerializationTest < ActiveRecord::TestCase
assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json
end
+ def test_uses_serializable_hash_with_only_option
+ def @contact.serializable_hash(options=nil)
+ super(only: %w(name))
+ end
+
+ json = @contact.to_json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_no_match %r{awesome}, json
+ assert_no_match %r{age}, json
+ end
+
+ def test_uses_serializable_hash_with_except_option
+ def @contact.serializable_hash(options=nil)
+ super(except: %w(age))
+ end
+
+ json = @contact.to_json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_match %r{"awesome":true}, json
+ assert_no_match %r{age}, json
+ end
+
+ def test_does_not_include_inheritance_column_from_sti
+ @contact = ContactSti.new(@contact.attributes)
+ assert_equal 'ContactSti', @contact.type
+
+ json = @contact.to_json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_no_match %r{type}, json
+ assert_no_match %r{ContactSti}, json
+ end
+
+ def test_serializable_hash_with_default_except_option_and_excluding_inheritance_column_from_sti
+ @contact = ContactSti.new(@contact.attributes)
+ assert_equal 'ContactSti', @contact.type
+
+ def @contact.serializable_hash(options={})
+ super({ except: %w(age) }.merge!(options))
+ end
+
+ json = @contact.to_json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_no_match %r{age}, json
+ assert_no_match %r{type}, json
+ assert_no_match %r{ContactSti}, json
+ end
+
def test_serializable_hash_should_not_modify_options_in_argument
options = { :only => :name }
@contact.serializable_hash(options)
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 6c5bee7382..8544d36aa8 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1284,4 +1284,31 @@ class RelationTest < ActiveRecord::TestCase
Post.scoped.find_by!("1 = 0")
end
end
+
+ test "loaded relations cannot be mutated by multi value methods" do
+ relation = Post.scoped
+ relation.to_a
+
+ assert_raises(ActiveRecord::ImmutableRelation) do
+ relation.where! 'foo'
+ end
+ end
+
+ test "loaded relations cannot be mutated by single value methods" do
+ relation = Post.scoped
+ relation.to_a
+
+ assert_raises(ActiveRecord::ImmutableRelation) do
+ relation.limit! 5
+ end
+ end
+
+ test "loaded relations cannot be mutated by merge!" do
+ relation = Post.scoped
+ relation.to_a
+
+ assert_raises(ActiveRecord::ImmutableRelation) do
+ relation.merge! where: 'foo'
+ end
+ end
end
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index 12373333b0..7249ae9e4d 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -74,33 +74,88 @@ end
class DefaultXmlSerializationTest < ActiveRecord::TestCase
def setup
- @xml = Contact.new(:name => 'aaron stack', :age => 25, :avatar => 'binarydata', :created_at => Time.utc(2006, 8, 1), :awesome => false, :preferences => { :gem => 'ruby' }).to_xml
+ @contact = Contact.new(
+ :name => 'aaron stack',
+ :age => 25,
+ :avatar => 'binarydata',
+ :created_at => Time.utc(2006, 8, 1),
+ :awesome => false,
+ :preferences => { :gem => 'ruby' }
+ )
end
def test_should_serialize_string
- assert_match %r{<name>aaron stack</name>}, @xml
+ assert_match %r{<name>aaron stack</name>}, @contact.to_xml
end
def test_should_serialize_integer
- assert_match %r{<age type="integer">25</age>}, @xml
+ assert_match %r{<age type="integer">25</age>}, @contact.to_xml
end
def test_should_serialize_binary
- assert_match %r{YmluYXJ5ZGF0YQ==\n</avatar>}, @xml
- assert_match %r{<avatar(.*)(type="binary")}, @xml
- assert_match %r{<avatar(.*)(encoding="base64")}, @xml
+ xml = @contact.to_xml
+ assert_match %r{YmluYXJ5ZGF0YQ==\n</avatar>}, xml
+ assert_match %r{<avatar(.*)(type="binary")}, xml
+ assert_match %r{<avatar(.*)(encoding="base64")}, xml
end
def test_should_serialize_datetime
- assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @xml
+ assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml
end
def test_should_serialize_boolean
- assert_match %r{<awesome type=\"boolean\">false</awesome>}, @xml
+ assert_match %r{<awesome type=\"boolean\">false</awesome>}, @contact.to_xml
end
def test_should_serialize_hash
- assert_match %r{<preferences>\s*<gem>ruby</gem>\s*</preferences>}m, @xml
+ assert_match %r{<preferences>\s*<gem>ruby</gem>\s*</preferences>}m, @contact.to_xml
+ end
+
+ def test_uses_serializable_hash_with_only_option
+ def @contact.serializable_hash(options=nil)
+ super(only: %w(name))
+ end
+
+ xml = @contact.to_xml
+ assert_match %r{<name>aaron stack</name>}, xml
+ assert_no_match %r{age}, xml
+ assert_no_match %r{awesome}, xml
+ end
+
+ def test_uses_serializable_hash_with_except_option
+ def @contact.serializable_hash(options=nil)
+ super(except: %w(age))
+ end
+
+ xml = @contact.to_xml
+ assert_match %r{<name>aaron stack</name>}, xml
+ assert_match %r{<awesome type=\"boolean\">false</awesome>}, xml
+ assert_no_match %r{age}, xml
+ end
+
+ def test_does_not_include_inheritance_column_from_sti
+ @contact = ContactSti.new(@contact.attributes)
+ assert_equal 'ContactSti', @contact.type
+
+ xml = @contact.to_xml
+ assert_match %r{<name>aaron stack</name>}, xml
+ assert_no_match %r{<type}, xml
+ assert_no_match %r{ContactSti}, xml
+ end
+
+ def test_serializable_hash_with_default_except_option_and_excluding_inheritance_column_from_sti
+ @contact = ContactSti.new(@contact.attributes)
+ assert_equal 'ContactSti', @contact.type
+
+ def @contact.serializable_hash(options={})
+ super({ except: %w(age) }.merge!(options))
+ end
+
+ xml = @contact.to_xml
+ assert_match %r{<name>aaron stack</name>}, xml
+ assert_no_match %r{age}, xml
+ assert_no_match %r{<type}, xml
+ assert_no_match %r{ContactSti}, xml
end
end
diff --git a/activerecord/test/fixtures/reserved_words/distincts_selects.yml b/activerecord/test/fixtures/reserved_words/distinct_select.yml
index 90e8c95fef..d96779ade4 100644
--- a/activerecord/test/fixtures/reserved_words/distincts_selects.yml
+++ b/activerecord/test/fixtures/reserved_words/distinct_select.yml
@@ -1,11 +1,11 @@
-distincts_selects1:
+distinct_select1:
distinct_id: 1
select_id: 1
-distincts_selects2:
+distinct_select2:
distinct_id: 1
select_id: 2
-distincts_selects3:
+distinct_select3:
distinct_id: 2
select_id: 3
diff --git a/activerecord/test/models/contact.rb b/activerecord/test/models/contact.rb
index 3d15c7fbed..a1cb8d62b6 100644
--- a/activerecord/test/models/contact.rb
+++ b/activerecord/test/models/contact.rb
@@ -1,25 +1,40 @@
-class Contact < ActiveRecord::Base
- establish_connection(:adapter => 'fake')
+module ContactFakeColumns
+ def self.extended(base)
+ base.class_eval do
+ establish_connection(:adapter => 'fake')
+
+ connection.tables = [table_name]
+ connection.primary_keys = {
+ table_name => 'id'
+ }
+
+ column :name, :string
+ column :age, :integer
+ column :avatar, :binary
+ column :created_at, :datetime
+ column :awesome, :boolean
+ column :preferences, :string
+ column :alternative_id, :integer
+
+ serialize :preferences
- connection.tables = ['contacts']
- connection.primary_keys = {
- 'contacts' => 'id'
- }
+ belongs_to :alternative, :class_name => 'Contact'
+ end
+ end
# mock out self.columns so no pesky db is needed for these tests
- def self.column(name, sql_type = nil, options = {})
- connection.merge_column('contacts', name, sql_type, options)
+ def column(name, sql_type = nil, options = {})
+ connection.merge_column(table_name, name, sql_type, options)
end
+end
- column :name, :string
- column :age, :integer
- column :avatar, :binary
- column :created_at, :datetime
- column :awesome, :boolean
- column :preferences, :string
- column :alternative_id, :integer
+class Contact < ActiveRecord::Base
+ extend ContactFakeColumns
+end
- serialize :preferences
+class ContactSti < ActiveRecord::Base
+ extend ContactFakeColumns
+ column :type, :string
- belongs_to :alternative, :class_name => 'Contact'
+ def type; 'ContactSti' end
end
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index 11a0f4ff63..1f719b0858 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -30,3 +30,8 @@ class Member < ActiveRecord::Base
has_many :current_memberships, :conditions => { :favourite => true }
has_many :clubs, :through => :current_memberships
end
+
+class SelfMember < ActiveRecord::Base
+ self.table_name = "members"
+ has_and_belongs_to_many :friends, :class_name => "SelfMember", :join_table => "member_friends"
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 5bcb9652cd..ba01ef35f0 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -361,6 +361,11 @@ ActiveRecord::Schema.define do
t.string :extra_data
end
+ create_table :member_friends, :force => true, :id => false do |t|
+ t.integer :member_id
+ t.integer :friend_id
+ end
+
create_table :memberships, :force => true do |t|
t.datetime :joined_on
t.integer :club_id, :member_id