diff options
24 files changed, 154 insertions, 68 deletions
@@ -4,7 +4,7 @@ gemspec gem 'arel', github: 'rails/arel', branch: 'master' -gem 'mocha', github: 'freerange/mocha', require: false +gem 'mocha', '~> 0.13.0', require: false gem 'rack-test', github: 'brynary/rack-test' gem 'rack-cache', '~> 1.2' gem 'bcrypt-ruby', '~> 3.0.0' @@ -27,7 +27,7 @@ group :doc do # to a bug, but the PR that fixes it has been there # for some weeks unapplied. As a temporary solution # this is our own fork with the fix. - gem 'sdoc', github: 'fxn/sdoc' + gem 'sdoc', github: 'voloko/sdoc' gem 'redcarpet', '~> 2.2.2', platforms: :ruby gem 'w3c_validators' end @@ -42,7 +42,8 @@ instance_eval File.read local_gemfile if File.exists? local_gemfile platforms :mri do group :test do gem 'ruby-prof', '~> 0.11.2' if RUBY_VERSION < '2.0' - gem 'debugger' if !ENV['TRAVIS'] && RUBY_VERSION < '2.0' + gem 'debugger' if !ENV['TRAVIS'] && RUBY_VERSION < '2.0' && RUBY_PATCHLEVEL < 327 + end end diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb index 86ba5f3b4d..ffee3f81ba 100644 --- a/actionpack/test/template/template_test.rb +++ b/actionpack/test/template/template_test.rb @@ -1,3 +1,4 @@ +# encoding: US-ASCII require "abstract_unit" require "logger" diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index dee398177c..580a580ba5 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,7 +1,16 @@ ## Rails 4.0.0 (unreleased) ## +* `#pluck` can be used on a relation with `select` clause + Fix #7551 + + Example: + + Topic.select([:approved, :id]).order(:id).pluck(:id) + + *Yves Senn* + * Do not create useless database transaction when building `has_one` association. - + Example: User.has_one :profile diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index a694a292fe..83047c1845 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -332,7 +332,6 @@ module ActiveRecord #:nodoc: extend Translation extend DynamicMatchers extend Explain - extend ConnectionHandling include Persistence include ReadonlyAttributes diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 4ba35fe513..5464ca6066 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -149,8 +149,10 @@ module ActiveRecord ActiveSupport.on_load(:active_record) do ActionDispatch::Reloader.send(hook) do - ActiveRecord::Base.clear_reloadable_connections! - ActiveRecord::Base.clear_cache! + if ActiveRecord::Base.connected? + ActiveRecord::Base.clear_reloadable_connections! + ActiveRecord::Base.clear_cache! + end end end end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index a7d2f4bd24..df27318678 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -165,7 +165,9 @@ module ActiveRecord if has_include?(column_names.first) construct_relation_for_association_calculations.pluck(*column_names) else - result = klass.connection.select_all(select(column_names).arel, nil, bind_values) + relation = spawn + relation.select_values = column_names + result = klass.connection.select_all(relation.arel, nil, bind_values) columns = result.columns.map do |key| klass.column_types.fetch(key) { result.column_types.fetch(key) { diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index ab8b36c8ab..dbfa92bbbd 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -1,3 +1,4 @@ +require 'thread' module ActiveRecord module Delegation # :nodoc: @@ -6,6 +7,8 @@ module ActiveRecord delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass + @@delegation_mutex = Mutex.new + def self.delegate_to_scoped_klass(method) if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/ module_eval <<-RUBY, __FILE__, __LINE__ + 1 @@ -32,13 +35,28 @@ module ActiveRecord def method_missing(method, *args, &block) if @klass.respond_to?(method) - ::ActiveRecord::Delegation.delegate_to_scoped_klass(method) + @@delegation_mutex.synchronize do + unless ::ActiveRecord::Delegation.method_defined?(method) + ::ActiveRecord::Delegation.delegate_to_scoped_klass(method) + end + end + scoping { @klass.send(method, *args, &block) } elsif Array.method_defined?(method) - ::ActiveRecord::Delegation.delegate method, :to => :to_a + @@delegation_mutex.synchronize do + unless ::ActiveRecord::Delegation.method_defined?(method) + ::ActiveRecord::Delegation.delegate method, :to => :to_a + end + end + to_a.send(method, *args, &block) elsif arel.respond_to?(method) - ::ActiveRecord::Delegation.delegate method, :to => :arel + @@delegation_mutex.synchronize do + unless ::ActiveRecord::Delegation.method_defined?(method) + ::ActiveRecord::Delegation.delegate method, :to => :arel + end + end + arel.send(method, *args, &block) else super 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 42f5b69d4e..1b1b479f1a 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 @@ -799,12 +799,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal 1, developer.projects.count end - def test_counting_should_not_fire_sql_if_parent_is_unsaved - assert_no_queries do - assert_equal 0, Developer.new.projects.count - end - end - unless current_adapter?(:PostgreSQLAdapter) def test_count_with_finder_sql assert_equal 3, projects(:active_record).developers_with_finder_sql.count @@ -862,4 +856,15 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def klass.name; 'Foo'; end assert_deprecated { klass.has_and_belongs_to_many :posts, :delete_sql => 'lol' } end + + test "has and belongs to many associations on new records use null relations" do + projects = Developer.new.projects + assert_no_queries do + assert_equal [], projects + assert_equal [], projects.where(title: 'omg') + assert_equal [], projects.pluck(:title) + assert_equal 0, projects.count + end + end + end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 45b0a76f5a..b1066fb24c 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -262,12 +262,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal firm.limited_clients.length, firm.limited_clients.count end - def test_counting_should_not_fire_sql_if_parent_is_unsaved - assert_no_queries do - assert_equal 0, Person.new.readers.count - end - end - def test_finding assert_equal 2, Firm.all.merge!(:order => "id").first.clients.length end @@ -1665,6 +1659,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [], post.comments.where(body: 'omg') assert_equal [], post.comments.pluck(:body) assert_equal 0, post.comments.sum(:id) + assert_equal 0, post.comments.count end end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index b2a5d9d6f7..8e52ce1d91 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -766,12 +766,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal 1, authors(:mary).categories.general.count end - def test_counting_should_not_fire_sql_if_parent_is_unsaved - assert_no_queries do - assert_equal 0, Person.new.posts.count - end - end - def test_has_many_through_belongs_to_should_update_when_the_through_foreign_key_changes post = posts(:eager_other) @@ -876,4 +870,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase post = tags(:general).tagged_posts.create! :title => "foo", :body => "bar" assert_equal [tags(:general)], post.reload.tags end + + test "has many through associations on new records use null relations" do + person = Person.new + + assert_no_queries do + assert_equal [], person.posts + assert_equal [], person.posts.where(body: 'omg') + assert_equal [], person.posts.pluck(:body) + assert_equal 0, person.posts.sum(:tags_count) + assert_equal 0, person.posts.count + end + end + end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 203e44857a..c02d8f7760 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1442,6 +1442,13 @@ class BasicsTest < ActiveRecord::TestCase assert_match(/\/#{dev.id}$/, dev.cache_key) end + def test_cache_key_format_is_precise_enough + dev = Developer.first + key = dev.cache_key + dev.touch + assert_not_equal key, dev.cache_key + end + def test_uniq_delegates_to_scoped scope = stub Bird.stubs(:all).returns(mock(:uniq => scope)) diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index abbf2a765e..65d28ea028 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -580,4 +580,10 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal ["Over There"], Possession.pluck(:where) end + + def test_pluck_replaces_select_clause + taks_relation = Topic.select(:approved, :id).order(:id) + assert_equal [1,2,3,4], taks_relation.pluck(:id) + assert_equal [false, true, true, true], taks_relation.pluck(:approved) + end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index d44ac21b05..7db7953313 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -610,6 +610,11 @@ class FinderTest < ActiveRecord::TestCase assert_nil Topic.find_by_heading("The First Topic!") end + def test_find_by_one_attribute_bang_with_blank_defined + blank_topic = BlankTopic.create(title: "The Blank One") + assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One") + end + def test_find_by_one_attribute_with_conditions assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50) end diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index ab31d85e46..f7f4cebc5a 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -107,6 +107,12 @@ class ImportantTopic < Topic serialize :important, Hash end +class BlankTopic < Topic + def blank? + true + end +end + module Web class Topic < ActiveRecord::Base has_many :replies, :dependent => :destroy, :foreign_key => "parent_id", :class_name => 'Web::Reply' diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index dc84b44dec..2bbc41296f 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,12 @@ ## Rails 4.0.0 (unreleased) ## +* Simplify mocha integration and remove monkey-patches, bumping mocha to 0.13.0. *James Mead* + +* `#as_json` isolates options when encoding a hash. + Fix #8182 + + *Yves Senn* + * Deprecate Hash#diff in favor of MiniTest's #diff. *Steve Klabnik* * Kernel#capture can catch output from subprocesses *Dmitry Vorotilin* diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 0e7bb179bf..85b0e10be2 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -94,17 +94,17 @@ class Hash private def typecast_xml_value(value) - case value.class.to_s - when 'Hash' + case value + when Hash if value['type'] == 'array' _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) }) if entries.nil? || (c = value['__content__'] && c.blank?) [] else - case entries.class.to_s # something weird with classes not matching here. maybe singleton methods breaking is_a? - when 'Array' + case entries # something weird with classes not matching here. maybe singleton methods breaking is_a? + when Array entries.collect { |v| typecast_xml_value(v) } - when 'Hash' + when Hash [typecast_xml_value(entries)] else raise "can't typecast #{entries.inspect}" @@ -135,10 +135,10 @@ class Hash # how multipart uploaded files from HTML appear xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value end - when 'Array' + when Array value.map! { |i| typecast_xml_value(i) } value.length > 1 ? value : value.first - when 'String' + when String value else raise "can't typecast #{value.class.name} - #{value.inspect}" @@ -146,10 +146,10 @@ class Hash end def unrename_keys(params) - case params.class.to_s - when 'Hash' + case params + when Hash Hash[params.map { |k,v| [k.to_s.tr('-', '_'), unrename_keys(v)] } ] - when 'Array' + when Array params.map { |v| unrename_keys(v) } else params diff --git a/activesupport/lib/active_support/core_ext/struct.rb b/activesupport/lib/active_support/core_ext/struct.rb new file mode 100644 index 0000000000..c2c30044f2 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/struct.rb @@ -0,0 +1,6 @@ +# Backport of Struct#to_h from Ruby 2.0 +class Struct # :nodoc: + def to_h + Hash[members.zip(values)] + end +end unless Struct.instance_methods.include?(:to_h) diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index f65c831e04..7a5c351ca8 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -65,7 +65,7 @@ module ActiveSupport # they can detect circular references. options.merge(:encoder => self) else - options + options.dup end end diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 8b06739b7f..c96ad17aba 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -5,16 +5,18 @@ require 'active_support/testing/setup_and_teardown' require 'active_support/testing/assertions' require 'active_support/testing/deprecation' require 'active_support/testing/isolation' -require 'active_support/testing/mocha_module' require 'active_support/testing/constant_lookup' require 'active_support/core_ext/kernel/reporting' require 'active_support/deprecation' +begin + silence_warnings { require 'mocha/setup' } +rescue LoadError +end + module ActiveSupport class TestCase < ::MiniTest::Spec - include ActiveSupport::Testing::MochaModule - # Use AS::TestCase for the base class when describing a model register_spec_type(self) do |desc| Class === desc && desc < ActiveRecord::Base diff --git a/activesupport/lib/active_support/testing/mocha_module.rb b/activesupport/lib/active_support/testing/mocha_module.rb deleted file mode 100644 index 833dc867f0..0000000000 --- a/activesupport/lib/active_support/testing/mocha_module.rb +++ /dev/null @@ -1,22 +0,0 @@ -module ActiveSupport - module Testing - module MochaModule - begin - require 'mocha/api' - include Mocha::API - - def before_setup - mocha_setup - super - end - - def after_teardown - super - mocha_verify - mocha_teardown - end - rescue LoadError - end - end - end -end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 6746b58cd3..c378dcd01d 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -656,7 +656,9 @@ class HashExtTest < ActiveSupport::TestCase end def test_diff - assert_equal({ :a => 2 }, { :a => 2, :b => 5 }.diff({ :a => 1, :b => 5 })) + assert_deprecated do + assert_equal({ :a => 2 }, { :a => 2, :b => 5 }.diff({ :a => 1, :b => 5 })) + end end def test_slice diff --git a/activesupport/test/core_ext/struct_test.rb b/activesupport/test/core_ext/struct_test.rb new file mode 100644 index 0000000000..0dff7b32d2 --- /dev/null +++ b/activesupport/test/core_ext/struct_test.rb @@ -0,0 +1,10 @@ +require 'abstract_unit' +require 'active_support/core_ext/struct' + +class StructExt < ActiveSupport::TestCase + def test_to_h + x = Struct.new(:foo, :bar) + z = x.new(1, 2) + assert_equal({ foo: 1, bar: 2 }, z.to_h) + end +end diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 7ed71f9abc..5bb2a45c87 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -22,6 +22,15 @@ class TestJSONEncoding < ActiveSupport::TestCase end end + class CustomWithOptions + attr_accessor :foo, :bar + + def as_json(options={}) + options[:only] = %w(foo bar) + super(options) + end + end + TrueTests = [[ true, %(true) ]] FalseTests = [[ false, %(false) ]] NilTests = [[ nil, %(null) ]] @@ -248,6 +257,15 @@ class TestJSONEncoding < ActiveSupport::TestCase assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end + def test_to_json_should_not_keep_options_around + f = CustomWithOptions.new + f.foo = "hello" + f.bar = "world" + + hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}} + assert_equal(%({"foo":{"foo":"hello","bar":"world"},"other_hash":{"foo":"other_foo","test":"other_test"}}), hash.to_json) + end + def test_struct_encoding Struct.new('UserNameAndEmail', :name, :email) Struct.new('UserNameAndDate', :name, :date) diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 528bec58ef..40d1655c9b 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -219,7 +219,7 @@ module ApplicationTests orig_rails_env, Rails.env = Rails.env, 'development' ActiveRecord::Base.establish_connection assert ActiveRecord::Base.connection - assert_match /#{ActiveRecord::Base.configurations[Rails.env]['database']}/, ActiveRecord::Base.connection_config[:database] + assert_match(/#{ActiveRecord::Base.configurations[Rails.env]['database']}/, ActiveRecord::Base.connection_config[:database]) ensure ActiveRecord::Base.remove_connection ENV["DATABASE_URL"] = orig_database_url if orig_database_url @@ -236,7 +236,7 @@ module ApplicationTests ENV["DATABASE_URL"] = "sqlite3://:@localhost/#{database_url_db_name}" ActiveRecord::Base.establish_connection assert ActiveRecord::Base.connection - assert_match /#{database_url_db_name}/, ActiveRecord::Base.connection_config[:database] + assert_match(/#{database_url_db_name}/, ActiveRecord::Base.connection_config[:database]) ensure ActiveRecord::Base.remove_connection ENV["DATABASE_URL"] = orig_database_url if orig_database_url |