diff options
-rw-r--r-- | .travis.yml | 6 | ||||
-rw-r--r-- | Gemfile | 2 | ||||
-rw-r--r-- | Gemfile.lock | 8 | ||||
-rw-r--r-- | actionpack/CHANGELOG.md | 2 | ||||
-rw-r--r-- | actionpack/test/controller/rescue_test.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/associations/association_scope.rb | 63 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/reflection.rb | 71 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation/query_methods.rb | 31 | ||||
-rw-r--r-- | activerecord/test/cases/adapters/postgresql/uuid_test.rb | 13 | ||||
-rw-r--r-- | activerecord/test/cases/associations/has_many_through_associations_test.rb | 4 | ||||
-rw-r--r-- | activerecord/test/models/author.rb | 1 | ||||
-rw-r--r-- | guides/source/active_support_core_extensions.md | 6 |
13 files changed, 88 insertions, 123 deletions
diff --git a/.travis.yml b/.travis.yml index a32e90b3eb..2c27c6bab7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -94,17 +94,17 @@ matrix: - "GEM=ar:postgresql POSTGRES=9.2" addons: postgresql: "9.2" - - rvm: jruby-9.1.12.0 + - rvm: jruby-9.1.13.0 jdk: oraclejdk8 env: - "GEM=ap" - - rvm: jruby-9.1.12.0 + - rvm: jruby-9.1.13.0 jdk: oraclejdk8 env: - "GEM=am,amo,aj" allow_failures: - rvm: ruby-head - - rvm: jruby-9.1.12.0 + - rvm: jruby-9.1.13.0 - env: "GEM=ac:integration" fast_finish: true @@ -74,7 +74,7 @@ group :job do gem "backburner", require: false #TODO: add qu after it support Rails 5.1 # gem 'qu-rails', github: "bkeepers/qu", branch: "master", require: false - gem "qu-redis", require: false + # gem "qu-redis", require: false gem "delayed_job_active_record", require: false gem "sequel", require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 9da1aef756..387bd608f9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -336,12 +336,6 @@ GEM psych (2.2.4) public_suffix (2.0.5) puma (3.9.1) - qu (0.2.0) - multi_json - qu-redis (0.2.0) - qu (= 0.2.0) - redis-namespace - simple_uuid que (0.14.0) racc (1.4.14) rack (2.0.3) @@ -415,7 +409,6 @@ GEM faraday (~> 0.9) jwt (~> 1.5) multi_json (~> 1.10) - simple_uuid (0.4.0) sinatra (2.0.0) mustermann (~> 1.0) rack (~> 2.0) @@ -515,7 +508,6 @@ DEPENDENCIES pg (>= 0.18.0) psych (~> 2.0) puma - qu-redis que queue_classic! racc (>= 1.4.6) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index ad80bb26a7..932968fa35 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,5 @@ * Cookies `:expires` option supports `ActiveSupport::Duration` object. - + cookies[:user_name] = { value: "assain", expires: 1.hour } cookies[:key] = { value: "a yummy cookie", expires: 6.months } diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index 07f8c9dd8a..4ed79073e5 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -33,7 +33,7 @@ class RescueController < ActionController::Base class ResourceUnavailableToRescueAsString < StandardError end - # We use a fully-qualified name in some strings, and a relative constant + # We use a fully qualified name in some strings, and a relative constant # name in some other to test correct handling of both cases. rescue_from NotAuthorized, with: :deny_access diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 0e849c06ef..b2dc044312 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -24,14 +24,10 @@ module ActiveRecord scope = klass.unscoped owner = association.owner alias_tracker = AliasTracker.create(klass.connection, klass.table_name) - chain_head, chain_tail = get_chain(reflection, association, alias_tracker) + chain = get_chain(reflection, association, alias_tracker) scope.extending! reflection.extensions - add_constraints(scope, owner, chain_head, chain_tail) - end - - def join_type - Arel::Nodes::InnerJoin + add_constraints(scope, owner, chain) end def self.get_bind_values(owner, chain) @@ -59,14 +55,15 @@ module ActiveRecord private def join(table, constraint) - table.create_join(table, table.create_on(constraint), join_type) + table.create_join(table, table.create_on(constraint)) end - def last_chain_scope(scope, table, reflection, owner) + def last_chain_scope(scope, reflection, owner) join_keys = reflection.join_keys key = join_keys.key foreign_key = join_keys.foreign_key + table = reflection.aliased_table value = transform_value(owner[foreign_key]) scope = apply_scope(scope, table, key, value) @@ -82,11 +79,13 @@ module ActiveRecord value_transformation.call(value) end - def next_chain_scope(scope, table, reflection, foreign_table, next_reflection) + def next_chain_scope(scope, reflection, next_reflection) join_keys = reflection.join_keys key = join_keys.key foreign_key = join_keys.foreign_key + table = reflection.aliased_table + foreign_table = next_reflection.aliased_table constraint = table[key].eq(foreign_table[foreign_key]) if reflection.type @@ -98,12 +97,11 @@ module ActiveRecord end class ReflectionProxy < SimpleDelegator # :nodoc: - attr_accessor :next - attr_reader :alias_name + attr_reader :aliased_table - def initialize(reflection, alias_name) + def initialize(reflection, aliased_table) super(reflection) - @alias_name = alias_name + @aliased_table = aliased_table end def all_includes; nil; end @@ -111,40 +109,31 @@ module ActiveRecord def get_chain(reflection, association, tracker) name = reflection.name - runtime_reflection = Reflection::RuntimeReflection.new(reflection, association) - previous_reflection = runtime_reflection + chain = [Reflection::RuntimeReflection.new(reflection, association)] reflection.chain.drop(1).each do |refl| - alias_name = tracker.aliased_table_for( + aliased_table = tracker.aliased_table_for( refl.table_name, refl.alias_candidate(name), refl.klass.type_caster ) - proxy = ReflectionProxy.new(refl, alias_name) - previous_reflection.next = proxy - previous_reflection = proxy + chain << ReflectionProxy.new(refl, aliased_table) end - [runtime_reflection, previous_reflection] + chain end - def add_constraints(scope, owner, chain_head, chain_tail) - owner_reflection = chain_tail - table = owner_reflection.alias_name - scope = last_chain_scope(scope, table, owner_reflection, owner) - - reflection = chain_head - while reflection - table = reflection.alias_name - next_reflection = reflection.next + def add_constraints(scope, owner, chain) + scope = last_chain_scope(scope, chain.last, owner) - unless reflection == chain_tail - foreign_table = next_reflection.alias_name - scope = next_chain_scope(scope, table, reflection, foreign_table, next_reflection) - end + chain.each_cons(2) do |reflection, next_reflection| + scope = next_chain_scope(scope, reflection, next_reflection) + end + chain_head = chain.first + chain.reverse_each do |reflection| # Exclude the scope of the association itself, because that # was already merged in the #scope method. reflection.constraints.each do |scope_chain_item| - item = eval_scope(reflection, table, scope_chain_item, owner) + item = eval_scope(reflection, scope_chain_item, owner) if scope_chain_item == chain_head.scope scope.merge! item.except(:where, :includes) @@ -158,8 +147,6 @@ module ActiveRecord scope.where_clause += item.where_clause scope.order_values |= item.order_values end - - reflection = next_reflection end scope @@ -173,8 +160,8 @@ module ActiveRecord end end - def eval_scope(reflection, table, scope, owner) - relation = reflection.build_scope(table) + def eval_scope(reflection, scope, owner) + relation = reflection.build_scope(reflection.aliased_table) relation.instance_exec(owner, &scope) || relation end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index fc458d0c73..a0a22ba0f1 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -64,7 +64,7 @@ module ActiveRecord def quote_default_expression(value, column) # :nodoc: if value.is_a?(Proc) value.call - elsif column.type == :uuid && /\(\)/.match?(value) + elsif column.type == :uuid && value.is_a?(String) && /\(\)/.match?(value) value # Does not quote function default values for UUID columns elsif column.respond_to?(:array?) value = type_cast_from_column(column, value) diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 49ddcaecdf..889e24dd1a 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -138,7 +138,7 @@ module ActiveRecord # HasAndBelongsToManyReflection # ThroughReflection # PolymorphicReflection - # RuntimeReflection + # RuntimeReflection class AbstractReflection # :nodoc: def through_reflection? false @@ -154,14 +154,6 @@ module ActiveRecord klass.new(attributes, &block) end - def quoted_table_name - klass.quoted_table_name - end - - def primary_key_type - klass.type_for_attribute(klass.primary_key) - end - # Returns the class name for the macro. # # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt> @@ -373,6 +365,17 @@ module ActiveRecord # # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class # <tt>has_many :clients</tt> returns the Client class + # + # class Company < ActiveRecord::Base + # has_many :clients + # end + # + # Company.reflect_on_association(:clients).klass + # # => Client + # + # <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate + # a new association object. Use +build_association+ or +create_association+ + # instead. This allows plugins to hook into association object creation. def klass @klass ||= compute_class(class_name) end @@ -413,22 +416,6 @@ module ActiveRecord # Holds all the metadata about an association as it was specified in the # Active Record class. class AssociationReflection < MacroReflection #:nodoc: - # Returns the target association's class. - # - # class Author < ActiveRecord::Base - # has_many :books - # end - # - # Author.reflect_on_association(:books).klass - # # => Book - # - # <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate - # a new association object. Use +build_association+ or +create_association+ - # instead. This allows plugins to hook into association object creation. - def klass - @klass ||= compute_class(class_name) - end - def compute_class(name) active_record.send(:compute_type, name) end @@ -1029,6 +1016,8 @@ module ActiveRecord end class PolymorphicReflection < AbstractReflection # :nodoc: + delegate :klass, :scope, :plural_name, :type, :get_join_keys, to: :@reflection + def initialize(reflection, previous_reflection) @reflection = reflection @previous_reflection = previous_reflection @@ -1044,30 +1033,10 @@ module ActiveRecord scopes << @previous_reflection.source_type_scope end - def klass - @reflection.klass - end - - def scope - @reflection.scope - end - - def plural_name - @reflection.plural_name - end - - def type - @reflection.type - end - def constraints @reflection.constraints + [source_type_info] end - def get_join_keys(association_klass) - @reflection.get_join_keys(association_klass) - end - private def source_type_info type = @previous_reflection.foreign_type @@ -1076,8 +1045,8 @@ module ActiveRecord end end - class RuntimeReflection < PolymorphicReflection # :nodoc: - attr_accessor :next + class RuntimeReflection < AbstractReflection # :nodoc: + delegate :scope, :type, :constraints, :get_join_keys, to: :@reflection def initialize(reflection, association) @reflection = reflection @@ -1088,12 +1057,8 @@ module ActiveRecord @association.klass end - def constraints - @reflection.constraints - end - - def alias_name - Arel::Table.new(table_name, type_caster: klass.type_caster) + def aliased_table + @aliased_table ||= Arel::Table.new(table_name, type_caster: klass.type_caster) end def all_includes; yield; end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index bdc5c27328..c88603fde2 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1172,21 +1172,24 @@ module ActiveRecord end alias having_clause_factory where_clause_factory + DEFAULT_VALUES = { + create_with: FROZEN_EMPTY_HASH, + readonly: false, + where: Relation::WhereClause.empty, + having: Relation::WhereClause.empty, + from: Relation::FromClause.empty + } + + Relation::MULTI_VALUE_METHODS.each do |value| + DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY + end + + Relation::SINGLE_VALUE_METHODS.each do |value| + DEFAULT_VALUES[value] = nil if DEFAULT_VALUES[value].nil? + end + def default_value_for(name) - case name - when :create_with - FROZEN_EMPTY_HASH - when :readonly - false - when :where, :having - Relation::WhereClause.empty - when :from - Relation::FromClause.empty - when *Relation::MULTI_VALUE_METHODS - FROZEN_EMPTY_ARRAY - when *Relation::SINGLE_VALUE_METHODS - nil - else + DEFAULT_VALUES.fetch(name) do raise ArgumentError, "unknown relation value #{name.inspect}" end end diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 76cb1bc354..466d281e85 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -76,6 +76,19 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase assert_nil column.default end + def test_add_column_with_default_array + connection.add_column :uuid_data_type, :thingy, :uuid, array: true, default: [] + + UUIDType.reset_column_information + column = UUIDType.columns_hash["thingy"] + + assert column.array? + assert_equal "{}", column.default + + schema = dump_table_schema "uuid_data_type" + assert_match %r{t\.uuid "thingy", default: \[\], array: true$}, schema + end + def test_data_type_of_uuid_types column = UUIDType.columns_hash["guid"] assert_equal :uuid, column.type 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 f8ea51225a..521b388cee 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -1250,6 +1250,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase TenantMembership.current_member = nil end + def test_has_many_through_with_unscope_should_affect_to_through_scope + assert_equal [comments(:eager_other_comment1)], authors(:mary).unordered_comments + end + def test_has_many_through_with_scope_should_respect_table_alias family = Family.create! users = 3.times.map { User.create! } diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 0a52cc5390..3371fcbfcc 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -40,6 +40,7 @@ class Author < ActiveRecord::Base class_name: "Post" has_many :comments_desc, -> { order("comments.id DESC") }, through: :posts, source: :comments + has_many :unordered_comments, -> { unscope(:order).distinct }, through: :posts_sorted_by_id_limited, source: :comments has_many :funky_comments, through: :posts, source: :comments has_many :ordered_uniq_comments, -> { distinct.order("comments.id") }, through: :posts, source: :comments has_many :ordered_uniq_comments_desc, -> { distinct.order("comments.id DESC") }, through: :posts, source: :comments diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index b43d13b804..1438245f9c 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -634,7 +634,7 @@ NOTE: Defined in `active_support/core_ext/module/introspection.rb`. #### `parent_name` -The `parent_name` method on a nested named module returns the fully-qualified name of the module that contains its corresponding constant: +The `parent_name` method on a nested named module returns the fully qualified name of the module that contains its corresponding constant: ```ruby module X @@ -3708,9 +3708,9 @@ Extensions to `NameError` Active Support adds `missing_name?` to `NameError`, which tests whether the exception was raised because of the name passed as argument. -The name may be given as a symbol or string. A symbol is tested against the bare constant name, a string is against the fully-qualified constant name. +The name may be given as a symbol or string. A symbol is tested against the bare constant name, a string is against the fully qualified constant name. -TIP: A symbol can represent a fully-qualified constant name as in `:"ActiveRecord::Base"`, so the behavior for symbols is defined for convenience, not because it has to be that way technically. +TIP: A symbol can represent a fully qualified constant name as in `:"ActiveRecord::Base"`, so the behavior for symbols is defined for convenience, not because it has to be that way technically. For example, when an action of `ArticlesController` is called Rails tries optimistically to use `ArticlesHelper`. It is OK that the helper module does not exist, so if an exception for that constant name is raised it should be silenced. But it could be the case that `articles_helper.rb` raises a `NameError` due to an actual unknown constant. That should be reraised. The method `missing_name?` provides a way to distinguish both cases: |