aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionmailer/CHANGELOG.md4
-rw-r--r--actionmailer/lib/action_mailer/base.rb22
-rw-r--r--actionmailer/test/base_test.rb7
-rw-r--r--actionmailer/test/fixtures/base_mailer/attachment_with_hash.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_mailer/attachment_with_hash_default_encoding.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_mailer/welcome_with_headers.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_test/default_inline_attachment_mailer/welcome.html.erb0
-rw-r--r--actionmailer/test/fixtures/mail_delivery_test/delivery_mailer/welcome.html.erb0
-rw-r--r--actionmailer/test/fixtures/proc_mailer/welcome.html.erb0
-rw-r--r--actionpack/lib/action_view/template/resolver.rb105
-rw-r--r--actionpack/test/controller/render_test.rb6
-rw-r--r--actionpack/test/template/lookup_context_test.rb8
-rw-r--r--actionpack/test/template/testing/null_resolver_test.rb2
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb4
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb2
-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
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb26
-rw-r--r--activesupport/test/inflector_test.rb10
-rw-r--r--activesupport/test/inflector_test_cases.rb2
-rw-r--r--guides/source/active_record_querying.textile22
48 files changed, 552 insertions, 322 deletions
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index e6021939ff..a822412090 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,7 @@
+## Rails 4.0.0 (unreleased) ##
+
+* Raise an `ActionView::MissingTemplate` exception when no implicit template could be found. *Damien Mathieu*
+
## Rails 3.2.5 (Jun 1, 2012) ##
* No changes.
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 4f0cff0612..1899b13e71 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -184,6 +184,16 @@ module ActionMailer #:nodoc:
# and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book
# with the filename +free_book.pdf+.
#
+ # If you need to send attachments with no content, you need to create an empty view for it,
+ # or add an empty body parameter like this:
+ #
+ # class ApplicationMailer < ActionMailer::Base
+ # def welcome(recipient)
+ # attachments['free_book.pdf'] = File.read('path/to/file.pdf')
+ # mail(:to => recipient, :subject => "New account information", :body => "")
+ # end
+ # end
+ #
# = Inline Attachments
#
# You can also specify that a file should be displayed inline with other HTML. This is useful
@@ -598,8 +608,10 @@ module ActionMailer #:nodoc:
# end
# end
#
- # Will look for all templates at "app/views/notifier" with name "welcome". However, those
- # can be customized:
+ # Will look for all templates at "app/views/notifier" with name "welcome".
+ # If no welcome template exists, it will raise an ActionView::MissingTemplate error.
+ #
+ # However, those can be customized:
#
# mail(:template_path => 'notifications', :template_name => 'another')
#
@@ -733,7 +745,11 @@ module ActionMailer #:nodoc:
def each_template(paths, name, &block) #:nodoc:
templates = lookup_context.find_all(name, Array(paths))
- templates.uniq { |t| t.formats }.each(&block)
+ if templates.empty?
+ raise ActionView::MissingTemplate.new([paths], name, [paths], false, 'mailer')
+ else
+ templates.uniq { |t| t.formats }.each(&block)
+ end
end
def create_parts_from_responses(m, responses) #:nodoc:
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 1d747ed18a..1b2e39b3f7 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -433,6 +433,13 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("TEXT Implicit Multipart", mail.text_part.body.decoded)
end
+ test "should raise if missing template in implicit render" do
+ assert_raises ActionView::MissingTemplate do
+ BaseMailer.implicit_different_template('missing_template').deliver
+ end
+ assert_equal(0, BaseMailer.deliveries.length)
+ end
+
test "you can specify a different template for explicit render" do
mail = BaseMailer.explicit_different_template('explicit_multipart_templates').deliver
assert_equal("HTML Explicit Multipart Templates", mail.html_part.body.decoded)
diff --git a/actionmailer/test/fixtures/base_mailer/attachment_with_hash.html.erb b/actionmailer/test/fixtures/base_mailer/attachment_with_hash.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/attachment_with_hash.html.erb
diff --git a/actionmailer/test/fixtures/base_mailer/attachment_with_hash_default_encoding.html.erb b/actionmailer/test/fixtures/base_mailer/attachment_with_hash_default_encoding.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/attachment_with_hash_default_encoding.html.erb
diff --git a/actionmailer/test/fixtures/base_mailer/welcome_with_headers.html.erb b/actionmailer/test/fixtures/base_mailer/welcome_with_headers.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/welcome_with_headers.html.erb
diff --git a/actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb b/actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb
diff --git a/actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb b/actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb
diff --git a/actionmailer/test/fixtures/base_test/default_inline_attachment_mailer/welcome.html.erb b/actionmailer/test/fixtures/base_test/default_inline_attachment_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_test/default_inline_attachment_mailer/welcome.html.erb
diff --git a/actionmailer/test/fixtures/mail_delivery_test/delivery_mailer/welcome.html.erb b/actionmailer/test/fixtures/mail_delivery_test/delivery_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/mail_delivery_test/delivery_mailer/welcome.html.erb
diff --git a/actionmailer/test/fixtures/proc_mailer/welcome.html.erb b/actionmailer/test/fixtures/proc_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/proc_mailer/welcome.html.erb
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index fa2038f78d..2bb656fac9 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -2,12 +2,14 @@ require "pathname"
require "active_support/core_ext/class"
require "active_support/core_ext/class/attribute_accessors"
require "action_view/template"
+require "thread"
+require "mutex_m"
module ActionView
# = Action View Resolver
class Resolver
# Keeps all information about view path and builds virtual path.
- class Path < String
+ class Path
attr_reader :name, :prefix, :partial, :virtual
alias_method :partial?, :partial
@@ -19,8 +21,77 @@ module ActionView
end
def initialize(name, prefix, partial, virtual)
- @name, @prefix, @partial = name, prefix, partial
- super(virtual)
+ @name = name
+ @prefix = prefix
+ @partial = partial
+ @virtual = virtual
+ end
+
+ def to_str
+ @virtual
+ end
+ alias :to_s :to_str
+ end
+
+ # Threadsafe template cache
+ class Cache #:nodoc:
+ class CacheEntry
+ include Mutex_m
+
+ attr_accessor :templates
+ end
+
+ def initialize
+ @data = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2|
+ h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } }
+ @mutex = Mutex.new
+ end
+
+ # Cache the templates returned by the block
+ def cache(key, name, prefix, partial, locals)
+ cache_entry = nil
+
+ # first obtain a lock on the main data structure to create the cache entry
+ @mutex.synchronize do
+ cache_entry = @data[key][name][prefix][partial][locals] ||= CacheEntry.new
+ end
+
+ # then to avoid a long lasting global lock, obtain a more granular lock
+ # on the CacheEntry itself
+ cache_entry.synchronize do
+ if Resolver.caching?
+ cache_entry.templates ||= yield
+ else
+ fresh_templates = yield
+
+ if templates_have_changed?(cache_entry.templates, fresh_templates)
+ cache_entry.templates = fresh_templates
+ else
+ cache_entry.templates ||= []
+ end
+ end
+ end
+ end
+
+ def clear
+ @mutex.synchronize do
+ @data.clear
+ end
+ end
+
+ private
+
+ def templates_have_changed?(cached_templates, fresh_templates)
+ # if either the old or new template list is empty, we don't need to (and can't)
+ # compare modification times, and instead just check whether the lists are different
+ if cached_templates.blank? || fresh_templates.blank?
+ return fresh_templates.blank? != cached_templates.blank?
+ end
+
+ cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
+
+ # if a template has changed, it will be now be newer than all the cached templates
+ fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
end
end
@@ -32,12 +103,11 @@ module ActionView
end
def initialize
- @cached = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2|
- h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } }
+ @cache = Cache.new
end
def clear_cache
- @cached.clear
+ @cache.clear
end
# Normalizes the arguments and passes it on to find_template.
@@ -65,27 +135,18 @@ module ActionView
# Handles templates caching. If a key is given and caching is on
# always check the cache before hitting the resolver. Otherwise,
- # it always hits the resolver but check if the resolver is fresher
- # before returning it.
+ # it always hits the resolver but if the key is present, check if the
+ # resolver is fresher before returning it.
def cached(key, path_info, details, locals) #:nodoc:
name, prefix, partial = path_info
locals = locals.map { |x| x.to_s }.sort!
- if key && caching?
- @cached[key][name][prefix][partial][locals] ||= decorate(yield, path_info, details, locals)
- else
- fresh = decorate(yield, path_info, details, locals)
- return fresh unless key
-
- scope = @cached[key][name][prefix][partial]
- cache = scope[locals]
- mtime = cache && cache.map(&:updated_at).max
-
- if !mtime || fresh.empty? || fresh.any? { |t| t.updated_at > mtime }
- scope[locals] = fresh
- else
- cache
+ if key
+ @cache.cache(key, name, prefix, partial, locals) do
+ decorate(yield, path_info, details, locals)
end
+ else
+ decorate(yield, path_info, details, locals)
end
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 4a795de5a2..6bebe7e1ed 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -1526,10 +1526,10 @@ class ExpiresInRenderTest < ActionController::TestCase
assert_equal "no-cache", @response.headers["Cache-Control"]
end
- def test_expires_now
+ def test_expires_now_with_cache_control_headers
get :conditional_hello_with_cache_control_headers
- assert_match /no-cache/, @response.headers["Cache-Control"]
- assert_match /no-transform/, @response.headers["Cache-Control"]
+ assert_match(/no-cache/, @response.headers["Cache-Control"])
+ assert_match(/no-transform/, @response.headers["Cache-Control"])
end
def test_date_header_when_expires_in
diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb
index 96b14a0acd..ef9c5ce10c 100644
--- a/actionpack/test/template/lookup_context_test.rb
+++ b/actionpack/test/template/lookup_context_test.rb
@@ -169,7 +169,7 @@ class LookupContextTest < ActiveSupport::TestCase
assert_not_equal template, old_template
end
-
+
test "responds to #prefixes" do
assert_equal [], @lookup_context.prefixes
@lookup_context.prefixes = ["foo"]
@@ -180,7 +180,7 @@ end
class LookupContextWithFalseCaching < ActiveSupport::TestCase
def setup
@resolver = ActionView::FixtureResolver.new("test/_foo.erb" => ["Foo", Time.utc(2000)])
- @resolver.stubs(:caching?).returns(false)
+ ActionView::Resolver.stubs(:caching?).returns(false)
@lookup_context = ActionView::LookupContext.new(@resolver, {})
end
@@ -247,6 +247,6 @@ class TestMissingTemplate < ActiveSupport::TestCase
@lookup_context.view_paths.find("foo", "parent", true, details)
end
assert_match %r{Missing partial parent/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
- end
-
+ end
+
end
diff --git a/actionpack/test/template/testing/null_resolver_test.rb b/actionpack/test/template/testing/null_resolver_test.rb
index 535ad3ab14..55ec36e753 100644
--- a/actionpack/test/template/testing/null_resolver_test.rb
+++ b/actionpack/test/template/testing/null_resolver_test.rb
@@ -6,7 +6,7 @@ class NullResolverTest < ActiveSupport::TestCase
templates = resolver.find_all("path.erb", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []})
assert_equal 1, templates.size, "expected one template"
assert_equal "Template generated by Null Resolver", templates.first.source
- assert_equal "arbitrary/path.erb", templates.first.virtual_path
+ assert_equal "arbitrary/path.erb", templates.first.virtual_path.to_s
assert_equal [:html], templates.first.formats
end
end
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 30f51e041f..16fcf14009 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -260,7 +260,7 @@ module ActiveModel
end
protected
- def instance_method_already_implemented?(method_name)
+ def instance_method_already_implemented?(method_name) #:nodoc:
generated_attribute_methods.method_defined?(method_name)
end
@@ -316,7 +316,7 @@ module ActiveModel
RUBY
end
- class AttributeMethodMatcher
+ class AttributeMethodMatcher #:nodoc:
attr_reader :prefix, :suffix, :method_missing_target
AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name)
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index 7eb48abc3c..a93323a3a8 100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -104,7 +104,7 @@ class XmlSerializationTest < ActiveModel::TestCase
assert_match %r{<createdAt}, @xml
end
- test "should use serialiable hash" do
+ test "should use serializable hash" do
@contact = SerializableContact.new
@contact.name = 'aaron stack'
@contact.age = 25
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
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index 8c3921c423..e5b4ca2738 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -58,7 +58,6 @@ module ActiveSupport
def attach_to(namespace, log_subscriber=new, notifier=ActiveSupport::Notifications)
log_subscribers << log_subscriber
- @@flushable_loggers = nil
log_subscriber.public_methods(false).each do |event|
next if %w{ start finish }.include?(event.to_s)
@@ -71,23 +70,14 @@ module ActiveSupport
@@log_subscribers ||= []
end
- def flushable_loggers
- @@flushable_loggers ||= begin
- loggers = log_subscribers.map(&:logger)
- loggers.uniq!
- loggers.select! { |l| l.respond_to?(:flush) }
- loggers
- end
- end
-
# Flush all log_subscribers' logger.
def flush_all!
- flushable_loggers.each { |log| log.flush }
+ logger.flush if logger.respond_to?(:flush)
end
end
def initialize
- @event_stack = []
+ @queue_key = [self.class.name, object_id].join "-"
super
end
@@ -99,17 +89,17 @@ module ActiveSupport
return unless logger
e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload)
- parent = @event_stack.last
+ parent = event_stack.last
parent << e if parent
- @event_stack.push e
+ event_stack.push e
end
def finish(name, id, payload)
return unless logger
finished = Time.now
- event = @event_stack.pop
+ event = event_stack.pop
event.end = finished
event.payload.merge!(payload)
@@ -142,5 +132,11 @@ module ActiveSupport
bold = bold ? BOLD : ""
"#{bold}#{color}#{text}#{CLEAR}"
end
+
+ private
+
+ def event_stack
+ Thread.current[@queue_key] ||= []
+ end
end
end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 91ae6bc189..1f32e4ff92 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -413,6 +413,16 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ Irregularities.each do |irregularity|
+ singular, plural = *irregularity
+ ActiveSupport::Inflector.inflections do |inflect|
+ define_method("test_singularize_of_irregularity_#{singular}_should_be_the_same") do
+ inflect.irregular(singular, plural)
+ assert_equal singular, ActiveSupport::Inflector.singularize(singular)
+ end
+ end
+ end
+
[ :all, [] ].each do |scope|
ActiveSupport::Inflector.inflections do |inflect|
define_method("test_clear_inflections_with_#{scope.kind_of?(Array) ? "no_arguments" : scope}") do
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 9fa1f417e4..ca4efd2e59 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -308,5 +308,7 @@ module InflectorTestCases
'child' => 'children',
'sex' => 'sexes',
'move' => 'moves',
+ 'cow' => 'kine',
+ 'zombie' => 'zombies',
}
end
diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile
index 4b14671efc..101988c59e 100644
--- a/guides/source/active_record_querying.textile
+++ b/guides/source/active_record_querying.textile
@@ -609,8 +609,8 @@ And this will give you a single +Order+ object for each date where there are ord
The SQL that would be executed would be something like this:
<sql>
-SELECT date(created_at) as ordered_date, sum(price) as total_price
-FROM orders
+SELECT date(created_at) as ordered_date, sum(price) as total_price
+FROM orders
GROUP BY date(created_at)
</sql>
@@ -627,9 +627,9 @@ Order.select("date(created_at) as ordered_date, sum(price) as total_price").grou
The SQL that would be executed would be something like this:
<sql>
-SELECT date(created_at) as ordered_date, sum(price) as total_price
-FROM orders
-GROUP BY date(created_at)
+SELECT date(created_at) as ordered_date, sum(price) as total_price
+FROM orders
+GROUP BY date(created_at)
HAVING sum(price) > 100
</sql>
@@ -1286,26 +1286,36 @@ Client.connection.select_all("SELECT * FROM clients WHERE id = '1'")
h3. +pluck+
-<tt>pluck</tt> can be used to query a single column from the underlying table of a model. It accepts a column name as argument and returns an array of values of the specified column with the corresponding data type.
+<tt>pluck</tt> can be used to query a single or multiple columns from the underlying table of a model. It accepts a list of column names as argument and returns an array of values of the specified columns with the corresponding data type.
<ruby>
Client.where(:active => true).pluck(:id)
# SELECT id FROM clients WHERE active = 1
+# => [1, 2, 3]
Client.uniq.pluck(:role)
# SELECT DISTINCT role FROM clients
+# => ['admin', 'member', 'guest']
+
+Client.pluck(:id, :name)
+# SELECT clients.id, clients.name FROM clients
+# => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
</ruby>
+pluck+ makes it possible to replace code like
<ruby>
Client.select(:id).map { |c| c.id }
+# or
+Client.select(:id).map { |c| [c.id, c.name] }
</ruby>
with
<ruby>
Client.pluck(:id)
+# or
+Client.pluck(:id, :name)
</ruby>
h3. +ids+